---
title: "useHumanInTheLoop"
description: "React hook for interactive tools that pause agent execution and wait for user input"
---

## Overview

`useHumanInTheLoop` registers an interactive tool that pauses agent execution until the user responds through your custom UI. Unlike `useFrontendTool`, there is no `handler` function. Instead, the hook provides an internal status machine (`InProgress` -> `Executing` -> `Complete`) and supplies a `respond` callback to the render component while the tool is in the `Executing` state. The agent remains paused until `respond` is called with the user's input.

This hook is built on top of `useFrontendTool` with an internally managed handler that resolves the tool call promise when `respond` is invoked. Use it for confirmation dialogs, approval workflows, form collection, or any scenario where a human must provide input before the agent can continue.

## Signature

```tsx
import { useHumanInTheLoop } from "@copilotkit/react-core/v2";

function useHumanInTheLoop<T extends Record<string, unknown>>(
  tool: ReactHumanInTheLoop<T>,
  deps?: ReadonlyArray<unknown>,
): void;
```

## Parameters

<PropertyReference name="tool" type="ReactHumanInTheLoop<T>" required>
  The interactive tool definition.

<PropertyReference name="name" type="string" required>
  A unique name for the tool. The agent references this name when it needs human
  input.
</PropertyReference>

<PropertyReference name="description" type="string" required>
  A natural-language description that tells the agent what the tool does and
  when to invoke it (e.g., "Ask the user to confirm before deleting records").
</PropertyReference>

<PropertyReference name="parameters" type="z.ZodSchema" required>
  A Zod schema defining the arguments the agent passes to the tool. These
  arguments are forwarded to the render component so you can build context-aware
  UI.
</PropertyReference>

  <PropertyReference name="render" type="React.ComponentType<RenderProps>" required>
    A React component that drives the human interaction. The component receives different props depending on the current status:

    **When `status` is `ToolCallStatus.InProgress`:**
    - `args: Partial<T>` -- partially streamed arguments
    - `respond: undefined` -- not yet available
    - `result: undefined`

    **When `status` is `ToolCallStatus.Executing`:**
    - `args: T` -- fully resolved arguments
    - `respond: (result: unknown) => Promise<void>` -- call this to send the user's response back to the agent and resume execution
    - `result: undefined`

    **When `status` is `ToolCallStatus.Complete`:**
    - `args: T` -- the original arguments
    - `respond: undefined` -- no longer available
    - `result: string` -- the serialized result

  </PropertyReference>

  <PropertyReference name="available" type='"enabled" | "disabled" | "remote"' default='"enabled"'>
    Controls tool availability. Set to `"disabled"` to temporarily prevent the agent from requesting human input through this tool.
  </PropertyReference>
</PropertyReference>

<PropertyReference name="deps" type="ReadonlyArray<unknown>">
  An optional dependency array. When provided, the tool registration is
  refreshed whenever any value in the array changes.
</PropertyReference>

## Usage

### Confirmation Dialog

A common pattern where the agent asks the user to confirm a destructive action.

```tsx
function DeleteConfirmation() {
  useHumanInTheLoop(
    {
      name: "confirmDeletion",
      description: "Ask the user to confirm before deleting items",
      parameters: z.object({
        itemName: z.string().describe("Name of the item to delete"),
        itemCount: z.number().describe("Number of items to delete"),
      }),
      render: ({ args, status, respond, result }) => {
        if (status === ToolCallStatus.InProgress) {
          return (
            <div className="p-4 text-gray-500">Preparing confirmation...</div>
          );
        }

        if (status === ToolCallStatus.Executing && respond) {
          return (
            <div className="p-4 border rounded">
              <p>
                Are you sure you want to delete {args.itemCount} {args.itemName}
                (s)?
              </p>
              <div className="flex gap-2 mt-4">
                <button
                  onClick={() => respond({ confirmed: true })}
                  className="bg-red-500 text-white px-4 py-2 rounded"
                >
                  Delete
                </button>
                <button
                  onClick={() => respond({ confirmed: false })}
                  className="bg-gray-300 px-4 py-2 rounded"
                >
                  Cancel
                </button>
              </div>
            </div>
          );
        }

        if (status === ToolCallStatus.Complete && result) {
          const parsed = JSON.parse(result);
          return (
            <div className="p-2 text-sm text-gray-600">
              {parsed.confirmed ? "Items deleted." : "Deletion cancelled."}
            </div>
          );
        }

        return null;
      },
    },
    [],
  );

  return null;
}
```

### Form Input Collection

Collect structured input from the user before the agent proceeds.

```tsx
function ShippingAddressForm() {
  useHumanInTheLoop(
    {
      name: "collectShippingAddress",
      description:
        "Collect shipping address from the user before placing an order",
      parameters: z.object({
        orderSummary: z
          .string()
          .describe("A summary of the order being placed"),
      }),
      render: ({ args, status, respond }) => {
        const [address, setAddress] = useState({
          street: "",
          city: "",
          zip: "",
        });

        if (status === ToolCallStatus.Executing && respond) {
          return (
            <div className="p-4 border rounded space-y-3">
              <p className="font-medium">Order: {args.orderSummary}</p>
              <p>Please enter your shipping address:</p>
              <input
                placeholder="Street address"
                value={address.street}
                onChange={(e) =>
                  setAddress({ ...address, street: e.target.value })
                }
                className="w-full border p-2 rounded"
              />
              <input
                placeholder="City"
                value={address.city}
                onChange={(e) =>
                  setAddress({ ...address, city: e.target.value })
                }
                className="w-full border p-2 rounded"
              />
              <input
                placeholder="ZIP code"
                value={address.zip}
                onChange={(e) =>
                  setAddress({ ...address, zip: e.target.value })
                }
                className="w-full border p-2 rounded"
              />
              <button
                onClick={() => respond(address)}
                className="bg-blue-500 text-white px-4 py-2 rounded"
              >
                Submit Address
              </button>
            </div>
          );
        }

        if (status === ToolCallStatus.Complete) {
          return (
            <div className="p-2 text-green-600">
              Shipping address submitted.
            </div>
          );
        }

        return null;
      },
    },
    [],
  );

  return null;
}
```

### Approval Workflow with Context

```tsx
function ExpenseApproval() {
  useHumanInTheLoop(
    {
      name: "approveExpense",
      description: "Request manager approval for an expense report",
      parameters: z.object({
        employeeName: z.string().describe("Name of the employee"),
        amount: z.number().describe("Expense amount in dollars"),
        category: z.string().describe("Expense category"),
        description: z.string().describe("Description of the expense"),
      }),
      render: ({ args, status, respond, result }) => {
        if (status === ToolCallStatus.Executing && respond) {
          return (
            <div className="p-4 border rounded">
              <h3 className="font-bold">Expense Approval Required</h3>
              <div className="mt-2 space-y-1 text-sm">
                <p>Employee: {args.employeeName}</p>
                <p>Amount: ${args.amount.toFixed(2)}</p>
                <p>Category: {args.category}</p>
                <p>Description: {args.description}</p>
              </div>
              <div className="flex gap-2 mt-4">
                <button
                  onClick={() => respond({ approved: true })}
                  className="bg-green-500 text-white px-4 py-2 rounded"
                >
                  Approve
                </button>
                <button
                  onClick={() =>
                    respond({ approved: false, reason: "Needs more detail" })
                  }
                  className="bg-red-500 text-white px-4 py-2 rounded"
                >
                  Reject
                </button>
              </div>
            </div>
          );
        }

        if (status === ToolCallStatus.Complete && result) {
          const parsed = JSON.parse(result);
          return (
            <div
              className={`p-2 text-sm ${parsed.approved ? "text-green-600" : "text-red-600"}`}
            >
              {parsed.approved
                ? "Expense approved."
                : `Expense rejected: ${parsed.reason}`}
            </div>
          );
        }

        return null;
      },
    },
    [],
  );

  return null;
}
```

## Behavior

- **Blocks agent execution**: The agent pauses when this tool is called and waits for the `respond` callback to be invoked before continuing.
- **Internal status machine**: The hook manages three states -- `InProgress` (arguments streaming in), `Executing` (waiting for user response), and `Complete` (user has responded). Your render component receives the appropriate props for each state.
- **Single response**: The `respond` callback should only be called once per tool invocation. Calling it resolves the tool call promise with the provided value.
- **Built on `useFrontendTool`**: Under the hood, this hook wraps `useFrontendTool` with an internally generated handler, so all the same lifecycle behavior (mount/unmount registration, duplicate warnings) applies.
- **Mount/Unmount lifecycle**: The tool and its render component are registered on mount and removed on unmount.
- **No return value**: The hook returns `void`.

## Related

- [`useFrontendTool`](/reference/hooks/useFrontendTool) -- for tools with automated handlers that do not require user interaction
- [`useRenderToolCall`](/reference/hooks/useRenderToolCall) -- for rendering backend tool calls
- [`ToolCallStatus`](#toolcallstatus) -- the status enum used across tool hooks

## ToolCallStatus

The `ToolCallStatus` enum is exported from `@copilotkit/react-core` and defines the three phases of tool execution:

| Value                       | Description                                                                                 |
| --------------------------- | ------------------------------------------------------------------------------------------- |
| `ToolCallStatus.InProgress` | Arguments are being streamed from the agent. The tool has not started executing yet.        |
| `ToolCallStatus.Executing`  | Arguments are fully resolved. For `useHumanInTheLoop`, the `respond` callback is available. |
| `ToolCallStatus.Complete`   | Execution is finished. The `result` string is available.                                    |
