Human-in-the-Loop¶
Human-in-the-Loop lets you insert human review checkpoints during agent execution. When the agent is about to call tools, you can pause for user confirmation before proceeding.
Two Pause Points¶
Agent execution has two phases: “reasoning” and “acting”. You can pause at either point:
Pause after reasoning: After the model decides which tools to call, but before execution. You can see the tool names and parameters, letting users decide whether to allow execution.
Pause after acting: After tool execution completes, but before the next reasoning iteration. You can see the results, letting users decide whether to continue.
Example: Confirming Sensitive Operations¶
This example shows how to require user confirmation before executing sensitive operations like deleting files or sending emails:
// 1. Create confirmation hook
Hook confirmationHook = new Hook() {
private static final List<String> SENSITIVE_TOOLS = List.of("delete_file", "send_email");
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
if (event instanceof PostReasoningEvent e) {
Msg reasoningMsg = e.getReasoningMessage();
List<ToolUseBlock> toolCalls = reasoningMsg.getContentBlocks(ToolUseBlock.class);
// Pause if sensitive tools are involved
boolean hasSensitive = toolCalls.stream()
.anyMatch(t -> SENSITIVE_TOOLS.contains(t.getName()));
if (hasSensitive) {
e.stopAgent();
}
}
return Mono.just(event);
}
};
// 2. Create agent
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.toolkit(toolkit)
.hook(confirmationHook)
.build();
Handling Pause and Resume¶
When the agent pauses, the returned message contains the pending tool information. Display it to the user and decide next steps based on their choice:
Msg response = agent.call(userMsg).block();
// Check for pending tool calls
while (response.hasContentBlocks(ToolUseBlock.class)) {
// Display pending tools
List<ToolUseBlock> pending = response.getContentBlocks(ToolUseBlock.class);
for (ToolUseBlock tool : pending) {
System.out.println("Tool: " + tool.getName());
System.out.println("Input: " + tool.getInput());
}
if (userConfirms()) {
// User confirmed, continue execution
response = agent.call().block();
} else {
// User declined, return cancellation
Msg cancelResult = Msg.builder()
.role(MsgRole.TOOL)
.content(pending.stream()
.map(t -> ToolResultBlock.of(t.getId(), t.getName(),
TextBlock.builder().text("Operation cancelled").build()))
.toArray(ToolResultBlock[]::new))
.build();
response = agent.call(cancelResult).block();
}
}
// Final response
System.out.println(response.getTextContent());
Quick Reference¶
Pause methods:
PostReasoningEvent.stopAgent()— Pause after reasoningPostActingEvent.stopAgent()— Pause after acting
Resume methods:
agent.call()— Continue executing pending toolsagent.call(toolResultMsg)— Provide custom tool result and continue
Check pause reason:
response.getGenerateReason()returnsREASONING_STOP_REQUESTEDorACTING_STOP_REQUESTED