| name | deepagents-hitl |
| description | Implementing human-in-the-loop approval workflows with interruptOn parameter for sensitive tool operations in Deep Agents. |
| language | js |
deepagents-hitl (JavaScript/TypeScript)
Overview
HITL middleware adds human oversight to tool calls. Execution pauses for human decision: approve, edit, or reject.
Requires checkpointer to save state during interrupts.
Basic Setup
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
const agent = await createDeepAgent({
interruptOn: {
write_file: true,
execute_sql: { allowedDecisions: ["approve", "reject"] },
read_file: false,
},
checkpointer: new MemorySaver()
});
Decision Table
| Tool Type | Config | Decisions | Use Case |
|---|
| Destructive | true | approve/edit/reject | write_file, delete |
| Critical | {allowedDecisions: [...]} | approve/reject only | deploy, SQL |
| Safe | false | none | read_file |
Code Examples
Example 1: Basic Approval
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { Command } from "@langchain/langgraph";
const agent = await createDeepAgent({
interruptOn: { write_file: true },
checkpointer: new MemorySaver()
});
const config = { configurable: { thread_id: "session-1" } };
let result = await agent.invoke({
messages: [{ role: "user", content: "Write config to /prod.yaml" }]
}, config);
const state = await agent.getState(config);
if (state.next) {
const interrupt = state.tasks[0];
console.log("Interrupt:", interrupt);
}
await agent.updateState(config, {
messages: [
new Command({
resume: {
decisions: [{ type: "approve" }]
}
})
]
});
result = await agent.invoke(null, config);
Example 2: Edit Before Execution
const agent = await createDeepAgent({
interruptOn: { execute_sql: true },
checkpointer: new MemorySaver()
});
const config = { configurable: { thread_id: "session-1" } };
await agent.invoke({
messages: [{ role: "user", content: "Delete old users" }]
}, config);
await agent.updateState(config, {
messages: [
new Command({
resume: {
decisions: [{
type: "edit",
args: {
query: "DELETE FROM users WHERE last_login < '2020-01-01' LIMIT 100"
}
}]
}
})
]
});
await agent.invoke(null, config);
Example 3: Reject with Feedback
const agent = await createDeepAgent({
interruptOn: { deploy_code: true },
checkpointer: new MemorySaver()
});
const config = { configurable: { thread_id: "session-1" } };
await agent.invoke({
messages: [{ role: "user", content: "Deploy to production" }]
}, config);
await agent.updateState(config, {
messages: [
new Command({
resume: {
decisions: [{
type: "reject",
message: "Tests haven't passed yet"
}]
}
})
]
});
await agent.invoke(null, config);
Example 4: Custom Middleware
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-4",
tools: [deployTool, sendEmailTool],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
deploy_to_prod: {
allowedDecisions: ["approve", "reject"],
description: "🚨 PRODUCTION DEPLOYMENT requires approval"
},
send_email: {
description: "📧 Email draft ready for review"
},
},
}),
],
checkpointer: new MemorySaver(),
});
Boundaries
What Agents CAN Configure
✅ Which tools require approval
✅ Allowed decision types per tool
✅ Custom interrupt descriptions
✅ Checkpointer implementation
What Agents CANNOT Configure
❌ HITL protocol structure
❌ Skip checkpointer requirement
❌ Interrupt without saving state
Gotchas
1. Checkpointer is REQUIRED
await createDeepAgent({ interruptOn: { write_file: true } });
await createDeepAgent({
interruptOn: { write_file: true },
checkpointer: new MemorySaver()
});
2. Thread ID Required
await agent.invoke({...});
await agent.updateState(...);
const config = { configurable: { thread_id: "session-1" } };
await agent.invoke({...}, config);
await agent.updateState(config, ...);
3. Check State Between Invocations
await agent.invoke({...}, config);
const state = await agent.getState(config);
if (state.next) {
}
await agent.updateState(config, {...});
await agent.invoke(null, config);
Full Documentation