name: deepagents-subagents description: Using SubAgentMiddleware to spawn subagents for task delegation, context isolation, and specialized work in Deep Agents. language: python
deepagents-subagents (Python)
Overview
SubAgentMiddleware enables agents to delegate work to specialized subagents via the task tool. Subagents provide:
- Context isolation: Subagent work doesn't clutter main agent's context
- Specialization: Different tools/prompts for specific tasks
- Token efficiency: Large subtask context compressed into single result
- Parallel execution: Multiple subagents can run concurrently
When to Use Subagents
| Use Subagents When | Use Main Agent When |
|---|---|
| Task needs specialized tools | General-purpose tools sufficient |
| Want to isolate complex multi-step work | Single-step operation |
| Need clean context for main agent | Context bloat acceptable |
| Task benefits from different model/prompt | Same config works |
How It Works
Main agent has task tool → creates fresh subagent → subagent executes autonomously → returns final report to main agent.
Default subagent: "general-purpose" - automatically available with same tools/config as main agent.
Defining Subagents
Dictionary-based Subagent
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}"
@tool
def summarize_paper(paper_id: str) -> str:
"""Summarize a research paper."""
return f"Summary of paper {paper_id}"
agent = create_deep_agent(
subagents=[
{
"name": "research",
"description": "Research academic papers and provide summaries",
"system_prompt": "You are a research assistant. Search papers and provide concise summaries.",
"tools": [search_papers, summarize_paper],
"model": "claude-sonnet-4-5-20250929", # Optional: override model
}
]
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Research recent papers on transformers"}]
})
# Main agent calls: task(agent="research", instruction="Research recent papers on transformers")
CompiledSubAgent (Custom LangGraph)
from deepagents import create_deep_agent, CompiledSubAgent
from langgraph.graph import StateGraph
def create_weather_graph():
workflow = StateGraph(...)
# Build custom graph
return workflow.compile()
weather_graph = create_weather_graph()
weather_subagent = CompiledSubAgent(
name="weather",
description="Get weather forecasts for cities",
runnable=weather_graph
)
agent = create_deep_agent(
subagents=[weather_subagent]
)
Decision Table: Subagent Patterns
| Pattern | When to Use | Example |
|---|---|---|
| Specialized tools | Task needs unique tools | code-reviewer with linting tools |
| Different model | Cost/capability tradeoff | GPT-4 main, GPT-3.5 for simple subagents |
| Context isolation | Keep main context clean | web-research dumps to files, returns summary |
| Parallel work | Independent subtasks | analyze-data + generate-report simultaneously |
Code Examples
Example 1: Research Subagent
from deepagents import create_deep_agent
from langchain.tools import tool
@tool
def web_search(query: str) -> str:
"""Search the web."""
return f"Search results for: {query}"
@tool
def analyze_data(data: str) -> str:
"""Analyze data and extract insights."""
return f"Analysis: {data[:100]}..."
agent = create_deep_agent(
subagents=[
{
"name": "researcher",
"description": "Conduct web research and compile findings",
"system_prompt": "Search thoroughly, save results to /research/ directory, return concise summary",
"tools": [web_search],
},
{
"name": "analyst",
"description": "Analyze data and provide insights",
"system_prompt": "Provide data-driven insights with specific numbers",
"tools": [analyze_data],
}
]
)
result = agent.invoke({
"messages": [{
"role": "user",
"content": "Research market trends for EVs, then analyze the data"
}]
})
# Main agent: task(agent="researcher", ...) -> task(agent="analyst", ...)
Example 2: Subagent with Human-in-the-Loop
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": "Deploy code safely with all checks",
"tools": [run_tests, deploy_to_prod],
"interrupt_on": {"deploy_to_prod": True}, # Require approval
}
],
checkpointer=MemorySaver() # Required for interrupts
)
Example 3: Subagent with Custom Skills
from deepagents import create_deep_agent
agent = create_deep_agent(
skills=["/main-skills/"], # Main agent skills
subagents=[
{
"name": "python-expert",
"description": "Python code review and refactoring",
"system_prompt": "Review Python code for best practices",
"tools": [read_code, suggest_improvements],
"skills": ["/python-skills/"], # Subagent-specific skills
}
]
)
# Note: Custom subagents DON'T inherit main agent's skills by default
# General-purpose subagent DOES inherit main agent's skills
Example 4: Default General-Purpose Subagent
from deepagents import create_deep_agent
agent = create_deep_agent()
# Agent can use default "general-purpose" subagent for context isolation
result = agent.invoke({
"messages": [{
"role": "user",
"content": "Analyze this large dataset and summarize the key findings"
}]
})
# Agent may call: task(instruction="Analyze dataset and summarize")
# Uses general-purpose subagent with same tools/config as main
Boundaries
What Agents CAN Configure
✅ Subagent name and description ✅ Custom tools for subagents ✅ Different models per subagent ✅ Subagent-specific system prompts ✅ Subagent middleware and skills ✅ Human-in-the-loop for subagent tools
What Agents CANNOT Configure
❌ Change the task tool name
❌ Make subagents stateful (they're ephemeral)
❌ Share state directly between subagents
❌ Remove the default general-purpose subagent
❌ Have subagents call back to main agent
Gotchas
1. Subagents Are Stateless
# ❌ Subagents don't remember previous calls
agent.invoke({"messages": [{"role": "user", "content": "task(agent='research', instruction='Find data')"}]})
agent.invoke({"messages": [{"role": "user", "content": "task(agent='research', instruction='What did you find?')"}]})
# Second call won't remember first call - fresh subagent each time
# ✅ Main agent maintains conversation memory, not subagents
2. Custom Subagents Don't Inherit Skills
# ❌ Subagent won't have main agent's skills
agent = create_deep_agent(
skills=["/main-skills/"],
subagents=[{"name": "helper", ...}] # No skills
)
# ✅ Explicitly provide skills to subagent
agent = create_deep_agent(
skills=["/main-skills/"],
subagents=[{
"name": "helper",
"skills": ["/helper-skills/"], # Subagent-specific
...
}]
)
# ✅ General-purpose subagent DOES inherit main skills
# agent.invoke() -> task(instruction="...") uses general-purpose with main skills
3. Subagent Results Are Final
# Subagents return a single final message
# They can't have back-and-forth dialogue with main agent
# ❌ Can't do this:
# Main: "task(agent='research', instruction='Find data')"
# Research: "What topic?"
# Main: "AI"
# Research: "Here's AI data"
# ✅ Provide complete instructions upfront
# Main: "task(agent='research', instruction='Find data on AI, save to /research/, return summary')"
4. Subagent Interrupts Require Main Checkpointer
# ❌ Subagent HITL without checkpointer
agent = create_deep_agent(
subagents=[{
"name": "deployer",
"interrupt_on": {"deploy": True}
}]
)
# ✅ Checkpointer on main agent, not subagent
agent = create_deep_agent(
subagents=[{
"name": "deployer",
"interrupt_on": {"deploy": True}
}],
checkpointer=MemorySaver() # On main agent
)