A2UI Launched: Full CopilotKit support at launch!

A2UI Launched: CopilotKit has partnered with Google to deliver full support in both CopilotKit and AG-UI!

Check it out
LogoLogo
  • Overview
  • Integrations
  • API Reference
  • Copilot Cloud
Slanted end borderSlanted end border
Slanted start borderSlanted start border
  • API Reference
  • UI Components
  • CopilotTextarea
  • CopilotKit
  • Hooks
  • useAgent
  • useDefaultTool
  • useFrontendTool
  • useRenderToolCall
  • useHumanInTheLoop
  • useCopilotReadable
  • useCopilotAdditionalInstructions
  • useCopilotChat
  • useCopilotChatHeadless_c
  • useCopilotChatSuggestions
  • useCoAgent
  • useCoAgentStateRender
  • useLangGraphInterrupt
  • useCopilotAction
  • Classes
  • CopilotRuntime
  • CopilotTask
  • SDKs

useHumanInTheLoop

The useHumanInTheLoop hook enables human approval and interaction workflows.

useHumanInTheLoop pauses AI execution to request human input or approval. When the AI calls this tool, it stops and waits for the user to respond through your custom UI before continuing. This is essential for sensitive operations, confirmations, or collecting information that only the user can provide.

Unlike useFrontendTool, there's no handler function—instead, your render function receives a respond callback that sends the user's input back to the AI. The AI execution remains paused until respond is called, making this a true blocking interaction.

Usage

Simple Confirmation Example

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

useHumanInTheLoop({
  name: "confirmDeletion",
  description: "Ask user to confirm before deleting items",
  parameters: [
    {
      name: "itemName",
      type: "string",
      description: "Name of the item to delete",
      required: true,
    },
    {
      name: "itemCount",
      type: "number",
      description: "Number of items to delete",
      required: true,
    },
  ],
  render: ({ args, status, respond, result }) => {
    if (status === "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 === "complete" && result) {
      return (
        <div className="p-2 text-sm text-gray-600">
          {result.confirmed ? "Items deleted" : "Deletion cancelled"}
        </div>
      );
    }

    return null;
  },
});

Complex Input Collection Example

import { useHumanInTheLoop } from "@copilotkit/react-core";
import { useState } from "react";

useHumanInTheLoop({
  name: "collectUserPreferences",
  description: "Collect detailed preferences from the user",
  parameters: [
    {
      name: "context",
      type: "string",
      description: "Context for why preferences are needed",
      required: true,
    },
    {
      name: "requiredFields",
      type: "string[]",
      description: "Fields to collect",
      required: true,
    },
  ],
  render: ({ args, status, respond }) => {
    const [preferences, setPreferences] = useState({
      theme: "light",
      notifications: true,
      language: "en",
    });

    if (status === "executing" && respond) {
      return (
        <div className="p-4 border rounded">
          <h3 className="font-bold mb-2">{args.context}</h3>
          <form onSubmit={(e) => {
            e.preventDefault();
            respond(preferences);
          }}>
            <button
              type="submit"
              className="bg-blue-500 text-white px-4 py-2 rounded"
            >
              Save Preferences
            </button>
          </form>
        </div>
      );
    }

    return null;
  },
});

Best Practices

  1. Always check for the respond function before rendering interactive elements
  2. Handle all status states to provide good user feedback
  3. Validate user input before calling respond
  4. Provide clear instructions in your UI about what input is expected
  5. Consider timeout scenarios for time-sensitive operations

Migration from useCopilotAction

If you're migrating from useCopilotAction with renderAndWaitForResponse:

// Before with useCopilotAction
useCopilotAction({
  name: "confirmAction",
  parameters: [
    { name: "message", type: "string", required: true },
  ],
  renderAndWaitForResponse: ({ args, respond, status }) => {
    return (
      <ConfirmDialog
        message={args.message}
        onConfirm={() => respond(true)}
        onCancel={() => respond(false)}
        isActive={status === "executing"}
      />
    );
  },
});

// After with useHumanInTheLoop
useHumanInTheLoop({
  name: "confirmAction",
  parameters: [
    {
      name: "message",
      type: "string",
      description: "The message to display",
      required: true,
    },
  ],
  render: ({ args, respond, status }) => {
    if (status === "executing" && respond) {
      return (
        <ConfirmDialog
          message={args.message}
          onConfirm={() => respond(true)}
          onCancel={() => respond(false)}
          isActive={true}
        />
      );
    }
    return null;
  },
});

The main differences are:

  1. The property is called render instead of renderAndWaitForResponse
  2. You need to check for the respond function's existence

Parameters

namestringrequired

The name of the tool.

descriptionstring

A description of the tool. This is used to instruct the Copilot on when to request human input.

parametersT

Array of parameter definitions that will be passed to the render function. Each parameter object should have:

  • name (string): The parameter name
  • type (string): The parameter type (e.g., "string", "number", "boolean", "string[]", "object")
  • description (string): A description of what the parameter is for
  • required (boolean): Whether the parameter is required
  • properties (array, optional): For object types, define nested properties using the same schema

Simple example: [{ name: "itemName", type: "string", description: "Name of the item", required: true }]

Nested example:

[
  {
    name: "approval",
    type: "object",
    description: "Approval request details",
    required: true,
    properties: [
      { name: "action", type: "string", description: "Action requiring approval", required: true },
      { name: "reason", type: "string", description: "Reason for the action", required: false }
    ]
  }
]
renderFrontendAction<T>['renderAndWaitForResponse']required

A React component that renders the interactive UI for human input. The component receives props including args, status, and respond function.

available'disabled' | 'enabled'

Whether the tool is available. Set to "disabled" to prevent the tool from being called.

PREV
useRenderToolCall
Slanted end borderSlanted end border
Slanted start borderSlanted start border
NEXT
useCopilotReadable

On this page

Usage
Simple Confirmation Example
Complex Input Collection Example
Best Practices
Migration from useCopilotAction
Parameters