Documentation Index Fetch the complete documentation index at: https://mintlify.com/CopilotKit/CopilotKit/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The useHumanInTheLoop hook creates interactive tools that pause agent execution to request user input, approval, or interaction before continuing. This enables human oversight and collaboration in AI workflows.
Usage
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
function ApprovalFlow () {
useHumanInTheLoop ({
name: "requestApproval" ,
description: "Request user approval for an action" ,
parameters: z . object ({
action: z . string (),
details: z . string (),
}),
render : ({ args , status , respond }) => {
if ( status === "executing" ) {
return (
< div >
< p > Approve: { args . action } </ p >
< p > { args . details } </ p >
< button onClick = { () => respond ({ approved: true }) } >
Approve
</ button >
< button onClick = { () => respond ({ approved: false }) } >
Reject
</ button >
</ div >
);
}
return < div > Waiting for approval... </ div > ;
},
});
return < div > ... </ div > ;
}
Type Signature
function useHumanInTheLoop < T extends Record < string , unknown >>(
tool : ReactHumanInTheLoop < T >,
deps ?: ReadonlyArray < unknown >
) : void
Parameters
tool
ReactHumanInTheLoop<T>
required
The human-in-the-loop tool definition with the following properties: Unique identifier for the tool. Description of what user action is needed. Helps the agent understand when to use this tool. description : "Request user confirmation before deleting data"
Zod schema defining the parameters passed from the agent to the user. parameters : z . object ({
itemId: z . string (),
itemName: z . string (),
})
render
React.ComponentType<RenderProps>
required
React component that renders the interactive UI. Receives props based on the current status. Render Props by Status: When the agent is still streaming parameters: {
name : string ;
description : string ;
args : Partial < T > ;
status : "inProgress" ;
result : undefined ;
respond : undefined ;
}
When waiting for user interaction: {
name : string ;
description : string ;
args : T ; // Complete parameters
status : "executing" ;
result : undefined ;
respond : ( result : unknown ) => Promise < void > ;
}
Call respond() with the user’s response to continue agent execution. After user has responded: {
name : string ;
description : string ;
args : T ;
status : "complete" ;
result : string ;
respond : undefined ;
}
Whether the agent should generate a follow-up message after user responds.
Constrain this tool to a specific agent. agentId : "approval-agent"
Whether this tool is currently available. Set to false to temporarily disable.
Optional dependency array. When dependencies change, the tool is re-registered. useHumanInTheLoop ( tool , [ userId , permissions ]);
Return Value
This hook does not return a value. The interactive tool is registered and automatically unregistered on unmount.
Examples
Simple Approval
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
function ApprovalTool () {
useHumanInTheLoop ({
name: "requestApproval" ,
description: "Get user approval for an action" ,
parameters: z . object ({
action: z . string (),
}),
render : ({ args , status , respond }) => {
if ( status === "executing" ) {
return (
< div className = "approval-dialog" >
< h3 > Approval Required </ h3 >
< p > Action: { args . action } </ p >
< div className = "buttons" >
< button
onClick = { () => respond ({ approved: true }) }
className = "approve"
>
Approve
</ button >
< button
onClick = { () => respond ({ approved: false }) }
className = "reject"
>
Reject
</ button >
</ div >
</ div >
);
}
if ( status === "complete" ) {
const result = JSON . parse ( result );
return (
< div >
✓ { result . approved ? "Approved" : "Rejected" }
</ div >
);
}
return < div > Waiting for parameters... </ div > ;
},
});
return null ;
}
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
import { useState } from "react" ;
function UserInputTool () {
useHumanInTheLoop ({
name: "getUserInput" ,
description: "Request specific information from the user" ,
parameters: z . object ({
prompt: z . string (),
fieldName: z . string (),
}),
render : ({ args , status , respond }) => {
const [ input , setInput ] = useState ( "" );
if ( status === "executing" ) {
return (
< div className = "input-dialog" >
< label > { args . prompt } </ label >
< input
type = "text"
value = { input }
onChange = { ( e ) => setInput ( e . target . value ) }
placeholder = { args . fieldName }
/>
< button
onClick = { () => respond ({ [args.fieldName]: input }) }
disabled = { ! input }
>
Submit
</ button >
</ div >
);
}
if ( status === "complete" ) {
return < div > ✓ Input received </ div > ;
}
return < div > Loading... </ div > ;
},
});
return null ;
}
Multi-Step Confirmation
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
function DeleteConfirmation () {
useHumanInTheLoop ({
name: "confirmDelete" ,
description: "Confirm deletion of important data" ,
parameters: z . object ({
itemType: z . string (),
itemName: z . string (),
itemId: z . string (),
consequences: z . array ( z . string ()),
}),
render : ({ args , status , respond }) => {
if ( status === "executing" ) {
return (
< div className = "delete-confirmation" >
< h3 > ⚠️ Confirm Deletion </ h3 >
< p >
You are about to delete { args . itemType } : < strong > { args . itemName } </ strong >
</ p >
< div className = "consequences" >
< p > This will: </ p >
< ul >
{ args . consequences . map (( consequence , idx ) => (
< li key = { idx } > { consequence } </ li >
)) }
</ ul >
</ div >
< p className = "warning" >
This action cannot be undone.
</ p >
< div className = "buttons" >
< button
onClick = { () => respond ({ confirmed: false }) }
className = "secondary"
>
Cancel
</ button >
< button
onClick = { () => respond ({
confirmed: true ,
itemId: args . itemId
}) }
className = "danger"
>
Delete Permanently
</ button >
</ div >
</ div >
);
}
if ( status === "complete" ) {
const result = JSON . parse ( result );
return result . confirmed ? (
< div className = "success" > ✓ Item deleted </ div >
) : (
< div className = "info" > Deletion cancelled </ div >
);
}
return < div > Preparing... </ div > ;
},
});
return null ;
}
Selection Dialog
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
function OptionSelector () {
useHumanInTheLoop ({
name: "selectOption" ,
description: "Present options for user to choose from" ,
parameters: z . object ({
question: z . string (),
options: z . array ( z . object ({
id: z . string (),
label: z . string (),
description: z . string (). optional (),
})),
}),
render : ({ args , status , respond }) => {
if ( status === "executing" ) {
return (
< div className = "option-selector" >
< h3 > { args . question } </ h3 >
< div className = "options" >
{ args . options . map (( option ) => (
< button
key = { option . id }
className = "option-card"
onClick = { () => respond ({
selectedId: option . id ,
selectedLabel: option . label
}) }
>
< div className = "label" > { option . label } </ div >
{ option . description && (
< div className = "description" > { option . description } </ div >
) }
</ button >
)) }
</ div >
</ div >
);
}
if ( status === "complete" ) {
const result = JSON . parse ( result );
return < div > Selected: { result . selectedLabel } </ div > ;
}
return < div > Loading options... </ div > ;
},
});
return null ;
}
File Upload Request
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
import { useState } from "react" ;
function FileUploadTool () {
useHumanInTheLoop ({
name: "requestFile" ,
description: "Request a file upload from the user" ,
parameters: z . object ({
fileType: z . string (),
purpose: z . string (),
}),
render : ({ args , status , respond }) => {
const [ uploading , setUploading ] = useState ( false );
const handleFileSelect = async ( e : React . ChangeEvent < HTMLInputElement >) => {
const file = e . target . files ?.[ 0 ];
if ( ! file ) return ;
setUploading ( true );
try {
// Upload file
const formData = new FormData ();
formData . append ( "file" , file );
const response = await fetch ( "/api/upload" , {
method: "POST" ,
body: formData ,
});
const data = await response . json ();
// Respond with file info
await respond ({
fileName: file . name ,
fileSize: file . size ,
fileUrl: data . url ,
});
} catch ( error ) {
console . error ( "Upload failed:" , error );
} finally {
setUploading ( false );
}
};
if ( status === "executing" ) {
return (
< div className = "file-upload" >
< h3 > File Upload Required </ h3 >
< p > Purpose: { args . purpose } </ p >
< p > Expected type: { args . fileType } </ p >
< input
type = "file"
onChange = { handleFileSelect }
accept = { args . fileType }
disabled = { uploading }
/>
{ uploading && < div > Uploading... </ div > }
</ div >
);
}
if ( status === "complete" ) {
const result = JSON . parse ( result );
return < div > ✓ Uploaded: { result . fileName } </ div > ;
}
return < div > Preparing upload... </ div > ;
},
});
return null ;
}
Conditional Availability
import { useHumanInTheLoop } from "@copilotkit/react" ;
import { z } from "zod" ;
function AdminApproval ({ user }) {
useHumanInTheLoop ({
name: "adminApproval" ,
description: "Request admin approval" ,
parameters: z . object ({
action: z . string (),
}),
render : ({ args , status , respond }) => {
if ( status === "executing" ) {
return (
< div >
< p > Admin approval needed: { args . action } </ p >
< button onClick = { () => respond ({ approved: true }) } >
Approve (Admin)
</ button >
</ div >
);
}
return < div > Complete </ div > ;
},
available: user . role === "admin" , // Only available to admins
}, [ user . role ]);
return null ;
}
Behavior
Execution Flow
inProgress : Agent is streaming the tool call parameters
executing : Parameters complete, waiting for user interaction via respond()
complete : User responded, agent continues with the result
Respond Function
The respond() function:
Accepts any value (typically an object)
Returns a Promise that resolves when the response is sent
Can only be called once per tool execution
Resumes agent execution with the provided result
Cleanup
The render component is removed from the registry on unmount
This prevents ghost interactions after the component is gone
Different from useFrontendTool, which keeps renderers for history
Use Cases
Approvals Request user approval before executing sensitive operations like deletions, payments, or data modifications.
Data Input Collect specific information from users that the agent needs but doesn’t have access to.
Confirmations Confirm important decisions or actions with the user before proceeding.
Selections Present options and let users make choices that guide the agent’s next steps.
File Uploads Request files from users that the agent needs to process or analyze.
Notes
The render component must handle all three status states (inProgress, executing, complete) to provide a complete user experience.
Human-in-the-loop tools automatically pause agent execution. The agent will not continue until respond() is called.
If your component unmounts while a human-in-the-loop tool is in the executing state, the interaction will be lost. Consider hoisting these components to a stable parent.