| name | deep-agents-orchestration |
| description | INVOKE THIS SKILL when using subagents, task planning, or human approval in Deep Agents. Covers SubAgentMiddleware, TodoList for planning, and HITL interrupts. |
Deep Agents include three orchestration capabilities:
- SubAgentMiddleware: Delegate work via
task tool to specialized agents
- TodoListMiddleware: Plan and track tasks via
write_todos tool
- HumanInTheLoopMiddleware: Require approval before sensitive operations
All three are automatically included in create_deep_agent().
Subagents (Task Delegation)
| Use Subagents When | Use Main Agent When |
|---|
| Task needs specialized tools | General-purpose tools sufficient |
| Want to isolate complex work | Single-step operation |
| Need clean context for main agent | Context bloat acceptable |
Main agent has `task` tool -> creates fresh subagent -> subagent executes autonomously -> returns final report.
Default subagent: "general-purpose" - automatically available with same tools/config as main agent.
Create a custom "researcher" subagent with specialized tools for academic paper search.
from deepagents import create_deep_agent
from langchain.tools import tool
@tool
def search_papers(query: str) -> str:
"""Search academic papers."""
return f"Found 10 papers about {query}"
agent = create_deep_agent(
subagents=[
{
"name": "researcher",
"description": "Conduct web research and compile findings",
"system_prompt": "Search thoroughly, return concise summary",
"tools": [search_papers],
}
]
)
Create a custom "researcher" subagent with specialized tools for academic paper search.
import { createDeepAgent } from "deepagents";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const searchPapers = tool(
async ({ query }) => `Found 10 papers about ${query}`,
{ name: "search_papers", description: "Search papers", schema: z.object({ query: z.string() }) }
);
const agent = await createDeepAgent({
subagents: [
{
name: "researcher",
description: "Conduct web research and compile findings",
systemPrompt: "Search thoroughly, return concise summary",
tools: [searchPapers],
}
]
});
Configure a subagent with HITL approval for sensitive operations.
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
subagents=[
{
"name": "code-deployer",
"description": "Deploy code to production",
"system_prompt": "You deploy code after tests pass.",
"tools": [run_tests, deploy_to_prod],
"interrupt_on": {"deploy_to_prod": True},
}
],
checkpointer=MemorySaver()
)
Subagents are stateless - provide complete instructions in a single call.
Subagents are stateless - provide complete instructions in a single call.
Custom subagents don't inherit skills from the main agent.
agent = create_deep_agent(
skills=["/main-skills/"],
subagents=[{"name": "helper", ...}]
)
agent = create_deep_agent(
skills=["/main-skills/"],
subagents=[{"name": "helper", "skills": ["/helper-skills/"], ...}]
)
TodoList (Task Planning)
| Use TodoList When | Skip TodoList When |
|---|
| Complex multi-step tasks | Simple single-action tasks |
| Long-running operations | Quick operations (< 3 steps) |
write_todos(todos: list[dict]) -> None
Each todo item has:
content: Description of the task
status: One of "pending", "in_progress", "completed"
Invoke an agent that automatically creates a todo list for a multi-step task.
from deepagents import create_deep_agent
agent = create_deep_agent()
result = agent.invoke({
"messages": [{"role": "user", "content": "Create a REST API: design models, implement CRUD, add auth, write tests"}]
}, config={"configurable": {"thread_id": "session-1"}})
Invoke an agent that automatically creates a todo list for a multi-step task.
import { createDeepAgent } from "deepagents";
const agent = await createDeepAgent();
const result = await agent.invoke({
messages: [{ role: "user", content: "Create a REST API: design models, implement CRUD, add auth, write tests" }]
}, { configurable: { thread_id: "session-1" } });
Access the todo list from the agent's final state after invocation.
result = agent.invoke({...}, config={"configurable": {"thread_id": "session-1"}})
todos = result.get("todos", [])
for todo in todos:
print(f"[{todo['status']}] {todo['content']}")
Todo list state requires a thread_id for persistence across invocations.
agent.invoke({"messages": [...]})
config = {"configurable": {"thread_id": "user-session"}}
agent.invoke({"messages": [...]}, config=config)
Human-in-the-Loop (Approval Workflows)
| Use HITL When | Skip HITL When |
|---|
| High-stakes operations (DB writes, deployments) | Read-only operations |
| Compliance requires human oversight | Fully automated workflows |
Configure which tools require human approval before execution.
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
interrupt_on={
"write_file": True,
"execute_sql": {"allowed_decisions": ["approve", "reject"]},
"read_file": False,
},
checkpointer=MemorySaver()
)
Configure which tools require human approval before execution.
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()
});
Complete workflow: trigger an interrupt, check state, approve action, and resume execution.
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
agent = create_deep_agent(
interrupt_on={"write_file": True},
checkpointer=MemorySaver()
)
config = {"configurable": {"thread_id": "session-1"}}
result = agent.invoke({
"messages": [{"role": "user", "content": "Write config to /prod.yaml"}]
}, config=config)
state = agent.get_state(config)
if state.next:
print(f"Pending action")
result = agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config=config)
Complete workflow: trigger an interrupt, check state, approve action, and resume execution.
import { createDeepAgent } from "deepagents";
import { MemorySaver, 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) {
console.log("Pending action");
}
result = await agent.invoke(
new Command({ resume: { decisions: [{ type: "approve" }] } }), config
);
Reject a pending action with feedback, prompting the agent to try a different approach.
result = agent.invoke(
Command(resume={"decisions": [{"type": "reject", "message": "Run tests first"}]}),
config=config,
)
Reject a pending action with feedback, prompting the agent to try a different approach.
const result = await agent.invoke(
new Command({ resume: { decisions: [{ type: "reject", message: "Run tests first" }] } }),
config,
);
Edit the proposed action arguments before allowing execution.
result = agent.invoke(
Command(resume={"decisions": [{
"type": "edit",
"edited_action": {
"name": "execute_sql",
"args": {"query": "DELETE FROM users WHERE last_login < '2020-01-01' LIMIT 100"},
},
}]}),
config=config,
)
### What Agents CAN Configure
- Subagent names, tools, models, system prompts
- Which tools require approval
- Allowed decision types per tool
- TodoList content and structure
What Agents CANNOT Configure
- Tool names (
task, write_todos)
- HITL protocol (approve/edit/reject structure)
- Skip checkpointer requirement for interrupts
- Make subagents stateful (they're ephemeral)
Checkpointer is required when using interrupt_on for HITL workflows.
agent = create_deep_agent(interrupt_on={"write_file": True})
agent = create_deep_agent(interrupt_on={"write_file": True}, checkpointer=MemorySaver())
Checkpointer is required when using interruptOn for HITL workflows.
const agent = await createDeepAgent({ interruptOn: { write_file: true } });
const agent = await createDeepAgent({ interruptOn: { write_file: true }, checkpointer: new MemorySaver() });
A consistent thread_id is required to resume interrupted workflows.
agent.invoke({"messages": [...]})
config = {"configurable": {"thread_id": "session-1"}}
agent.invoke({...}, config=config)
agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config=config)
A consistent thread_id is required to resume interrupted workflows.
await agent.invoke({ messages: [...] });
const config = { configurable: { thread_id: "session-1" } };
await agent.invoke({ messages: [...] }, config);
await agent.invoke(new Command({ resume: { decisions: [{ type: "approve" }] } }), config);
Interrupts happen BETWEEN invoke() calls, not mid-execution.
result = agent.invoke({...}, config=config)
if "__interrupt__" in result:
result = agent.invoke(
Command(resume={"decisions": [{"type": "approve"}]}),
config=config,
)