packages/core/src/tools/hitl.md
Agents frequently invoke tools that may require human approval before execution. This requirement stems from security protocols, privacy regulations, compliance mandates, or simply organizational preference for human oversight.
Additionally, tools may need to be suspended mid-execution due to dynamic conditions that arise during their operation. This commonly occurs when a tool initiates complex workflows or depends on external actions that require intervention or validation.
In the latest release, we've introduced a few new concepts for such use cases.
Enforcing approval on all tools
const stream = await myAgent.streamVNext('Find the user with name - John Smith', {
requireToolApproval: true,
});
Passing requireToolApproval to your generateVNext or streamVNext call will close the stream when the agent encounters a tool call. To continue execution of the agent stream call, we need to call .approveToolCall() with the runId of the original stream.
const resumedStream = await myAgent.approveToolCall({ runId: stream.runId });
If the tool call should not be executed, we call .declineToolCall() with the runId of the original stream to cancel the tool.
await myAgent.declineToolCall({ runId: stream.runId });
Enforcing on individual tools
const findUserTool = createTool({
id: 'Find user tool',
description: 'This is a test tool that returns the name and email',
inputSchema: z.object({
name: z.string(),
}),
execute: async inputData => {
return mockFindUser(inputData) as Promise<Record<string, any>>;
},
requireApproval: true,
});
When the agentic loop attempts to execute the findUserTool it will close the stream allowing you to either .approveToolCall() or .declineToolCall()
You may want to control the logic for suspending tool calls. A tool can be suspended manually by calling the suspend function. Similar to Mastra workflows, calling suspend gives you control. You define the shape of the suspension with suspendSchema and define the shape of how to resume with resumeSchema.
const findUserTool = createTool({
id: 'Find user tool',
description: 'This is a test tool that returns the name and email',
inputSchema: z.object({
name: z.string(),
}),
suspendSchema: z.object({
message: z.string(),
}),
resumeSchema: z.object({
name: z.string(),
}),
execute: async (inputData, { workflow }) => {
if (!workflow.resumeData) {
return await workflow.suspend({ message: 'Please provide the name of the user' });
}
return {
name: workflow?.resumeData?.name,
email: '[email protected]',
};
},
});
In this case, by calling the suspend() function, the tool call will be suspended and the agent will wait for the tool to be resumed.
You can resume with the .resumeStreamVNext() method. You pass resumeData to continue execution from the point of suspension. This resumeData will be available in the tool execution context, and if specified in the tool options, should match the schema of the resumeSchema.
const resumedStream = await myAgent.resumeStreamVNext({ name: 'John Smith' }, { runId: stream.runId });
for await (const chunk of resumedStream.fullStream) {
console.log('stream chunk', chunk);
}