Subagent Fundamentals
You’ve learned state management and tool building. Now you’ll master the third pillar of deep agents: subagents. This is where the framework truly shines—subagents solve the context bloat problem that causes long-running agents to degrade.
Exercise 1: The Context Bloat Problem
As an agent conversation grows, something problematic happens: the context window fills with increasingly irrelevant history. Early exchanges, tangential explorations, dead-end reasoning — all of it sits in memory, consuming tokens and degrading the agent’s ability to focus.
This is the "dumb zone": the point where an agent with a 200K token context window starts acting like it has a 20K window because most of its attention is wasted on noise. Research from teams building production agents has identified several specific failure modes:
-
Context distraction — as context grows beyond ~100K tokens, agents start repeating actions from their history rather than synthesizing new plans. They fall into pattern-mimicry, imitating what they’ve already done rather than reasoning about what to do next.
-
Context confusion — too many tools or too much irrelevant information overwhelms the model. One study found an 8B model failed with 46 tools loaded but succeeded with just 19 — despite having ample context capacity for all of them.
-
Context poisoning — early errors or hallucinations become embedded in the history and repeatedly referenced. The agent becomes fixated on incorrect information because it’s "in context" and feels authoritative.
-
Context clash — conflicting information from different conversation turns causes the model to make contradictory assumptions, with performance dropping up to 39% when information is spread across turns.
-
Lost goals — with ~50+ tool calls, agents drift off-topic or forget earlier objectives. The original task gets buried under layers of intermediate results.
For a deeper dive into these failure modes, see How Contexts Fail and Context Engineering for AI Agents from the Manus team.
Subagents solve this with a simple principle: fresh, isolated context per task.
When your main agent delegates work to a subagent, that subagent starts with a clean slate — no conversation history, no baggage. It receives only the task description, executes with full focus, and returns the result. The main agent integrates that result and continues.
| This isn’t just about avoiding errors — smaller subagents with focused context often outperform large agents with full history. Models reason better when given only the information relevant to the task at hand. Context isolation is a performance optimization, not just a memory management technique. |
Here’s the pattern:
This architecture keeps each piece of work sharp and focused, while the main agent handles orchestration and integration.
Exercise 2: Your First Subagent
Let’s define and use a research subagent.
-
Create a new file:
-
Run
-
Code Preview
cat > research_demo.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") researcher = { "name": "researcher", "description": "Use this subagent to research topics and gather information. It focuses on finding facts and summarizing them clearly.", "system_prompt": """You are a research assistant. When given a topic: 1. Break it down into key aspects 2. Provide factual, well-organized information 3. Cite your reasoning 4. Keep responses concise but thorough""", } agent = create_deep_agent( model=MODEL, subagents=[researcher], ) result = agent.invoke({"messages": [("user", "Research the key differences between REST and GraphQL APIs. " "Delegate this to your research subagent." )]}) print(agent_response(result)) EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") researcher = { "name": "researcher", "description": "Use this subagent to research topics and gather information. It focuses on finding facts and summarizing them clearly.", "system_prompt": """You are a research assistant. When given a topic: 1. Break it down into key aspects 2. Provide factual, well-organized information 3. Cite your reasoning 4. Keep responses concise but thorough""", } agent = create_deep_agent( model=MODEL, subagents=[researcher], ) result = agent.invoke({"messages": [("user", "Research the key differences between REST and GraphQL APIs. " "Delegate this to your research subagent." )]}) print(agent_response(result)) -
-
Run it:
uv run research_demo.pySample output (your results may vary)Here are the key differences between REST and GraphQL APIs: **Data Fetching:** - REST: Multiple endpoints return fixed data structures - GraphQL: Single endpoint with flexible queries for exactly what you need ...
You’ll see the main agent delegate to the researcher, which produces a focused, well-organized response.
The subagent is defined as a dictionary with three required fields (see the full configuration reference):
-
name: Unique identifier for routing -
description: What the subagent does — this is the primary routing signal -
system_prompt: Instructions that define the subagent’s behavior
Optional fields include tools (custom tool set), model (override the main agent’s model), skills, and middleware. The subagents parameter passes them to the main agent, which automatically gets a task tool for delegation.
Exercise 3: The task Tool
When you pass subagents to create_deep_agent(), the framework automatically adds a task tool to the main agent’s toolkit. This is the mechanism the main agent uses to delegate work — it’s a regular tool call, just like read_file or write_file, but instead of performing a file operation it spins up a subagent.
Here’s what happens when the main agent calls task():
The key insight is that the subagent’s entire lifecycle — creation, execution, and cleanup — happens inside a single tool call. The main agent sees only the final result as a ToolMessage, not the subagent’s intermediate reasoning or tool calls. This is what keeps the main agent’s context clean.
The task tool takes two arguments:
-
subagent_type: Which subagent to invoke (matches the subagent’snamefield) -
description: A natural-language description of what the subagent should do
The main agent decides which subagent to call by reading the description fields you provided when defining them. It decides what to ask by formulating the description argument based on the user’s request.
Let’s observe this in action.
-
Create a delegation inspector:
-
Run
-
Code Preview
cat > inspect_delegation.py << 'EOF' import os from deepagents import create_deep_agent MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") researcher = { "name": "researcher", "description": "Use this subagent to research topics and gather information. It focuses on finding facts and summarizing them clearly.", "system_prompt": """You are a research assistant. When given a topic: 1. Break it down into key aspects 2. Provide factual, well-organized information 3. Cite your reasoning 4. Keep responses concise but thorough""", } agent = create_deep_agent( model=MODEL, subagents=[researcher], ) result = agent.invoke({"messages": [("user", "Research the key differences between REST and GraphQL APIs. " "Delegate this to your research subagent." )]}) print("=== Delegation Analysis ===\n") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Delegated to: {tc['args'].get('subagent_type')}") print(f"Task description: {tc['args'].get('description')[:100]}...") print() EOFimport os from deepagents import create_deep_agent MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") researcher = { "name": "researcher", "description": "Use this subagent to research topics and gather information. It focuses on finding facts and summarizing them clearly.", "system_prompt": """You are a research assistant. When given a topic: 1. Break it down into key aspects 2. Provide factual, well-organized information 3. Cite your reasoning 4. Keep responses concise but thorough""", } agent = create_deep_agent( model=MODEL, subagents=[researcher], ) result = agent.invoke({"messages": [("user", "Research the key differences between REST and GraphQL APIs. " "Delegate this to your research subagent." )]}) print("=== Delegation Analysis ===\n") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Delegated to: {tc['args'].get('subagent_type')}") print(f"Task description: {tc['args'].get('description')[:100]}...") print() -
-
Run it:
uv run inspect_delegation.pySample output (your results may vary)=== Delegation Analysis === Delegated to: researcher Task description: Research the key differences between REST and GraphQL APIs, focusing on their architec...
Notice how the main agent formulated the description — it didn’t just pass through the user’s message verbatim. The main agent reasons about what information the subagent needs and crafts a focused task description.
Exercise 4: Subagent Descriptions as Routing
The description field is not just documentation—it’s the primary routing mechanism. The main agent reads descriptions to decide which subagent to use.
-
Create two subagents with overlapping capabilities but different descriptions:
-
Run
-
Code Preview
cat > routing_demo.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") fact_finder = { "name": "fact_finder", "description": "Use this subagent to find quick facts, definitions, and basic information. Best for straightforward lookups.", "system_prompt": "You provide concise, factual answers. Focus on clarity and brevity.", } deep_analyst = { "name": "deep_analyst", "description": "Use this subagent for complex analysis requiring critical thinking, trade-off evaluation, and nuanced reasoning.", "system_prompt": "You perform deep analysis. Consider multiple perspectives, evaluate trade-offs, and provide thorough reasoning.", } agent = create_deep_agent( model=MODEL, subagents=[fact_finder, deep_analyst], ) # Ambiguous task 1 result1 = agent.invoke({"messages": [("user", "What is REST?")]}) print("=== Task 1: What is REST? ===") for msg in result1["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") # Ambiguous task 2 result2 = agent.invoke({"messages": [("user", "Should I use REST or GraphQL for my new API? Consider team expertise, tooling, and long-term maintenance.")]}) print("=== Task 2: REST vs GraphQL decision ===") for msg in result2["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") fact_finder = { "name": "fact_finder", "description": "Use this subagent to find quick facts, definitions, and basic information. Best for straightforward lookups.", "system_prompt": "You provide concise, factual answers. Focus on clarity and brevity.", } deep_analyst = { "name": "deep_analyst", "description": "Use this subagent for complex analysis requiring critical thinking, trade-off evaluation, and nuanced reasoning.", "system_prompt": "You perform deep analysis. Consider multiple perspectives, evaluate trade-offs, and provide thorough reasoning.", } agent = create_deep_agent( model=MODEL, subagents=[fact_finder, deep_analyst], ) # Ambiguous task 1 result1 = agent.invoke({"messages": [("user", "What is REST?")]}) print("=== Task 1: What is REST? ===") for msg in result1["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") # Ambiguous task 2 result2 = agent.invoke({"messages": [("user", "Should I use REST or GraphQL for my new API? Consider team expertise, tooling, and long-term maintenance.")]}) print("=== Task 2: REST vs GraphQL decision ===") for msg in result2["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") -
-
Run it:
uv run routing_demo.pySample output (your results may vary)=== Task 1: What is REST? === Routed to: fact_finder === Task 2: REST vs GraphQL decision === Routed to: deep_analyst
The simple factual question routes to
fact_finder. The complex decision routes todeep_analyst. -
Now change the descriptions and re-run:
-
Run
-
Code Preview
cat > routing_demo2.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") fact_finder = { "name": "fact_finder", "description": "Use this subagent for ANY API-related questions, whether simple or complex.", "system_prompt": "You provide concise, factual answers. Focus on clarity and brevity.", } deep_analyst = { "name": "deep_analyst", "description": "Use this subagent for non-API analysis requiring critical thinking.", "system_prompt": "You perform deep analysis. Consider multiple perspectives, evaluate trade-offs, and provide thorough reasoning.", } agent = create_deep_agent( model=MODEL, subagents=[fact_finder, deep_analyst], ) result = agent.invoke({"messages": [("user", "Should I use REST or GraphQL for my new API? Consider team expertise, tooling, and long-term maintenance.")]}) print("=== With changed descriptions ===") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") fact_finder = { "name": "fact_finder", "description": "Use this subagent for ANY API-related questions, whether simple or complex.", "system_prompt": "You provide concise, factual answers. Focus on clarity and brevity.", } deep_analyst = { "name": "deep_analyst", "description": "Use this subagent for non-API analysis requiring critical thinking.", "system_prompt": "You perform deep analysis. Consider multiple perspectives, evaluate trade-offs, and provide thorough reasoning.", } agent = create_deep_agent( model=MODEL, subagents=[fact_finder, deep_analyst], ) result = agent.invoke({"messages": [("user", "Should I use REST or GraphQL for my new API? Consider team expertise, tooling, and long-term maintenance.")]}) print("=== With changed descriptions ===") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}\n") -
-
Run the modified version:
uv run routing_demo2.pySample output (your results may vary)=== With changed descriptions === Routed to: fact_finder
Now the same complex question routes to
fact_finderbecause its description claims API expertise.
Key lesson: Descriptions are routing signals. Write them carefully to guide delegation.
Exercise 5: Model Routing
Different tasks need different models. Research might work fine with a fast, cheap model like Haiku. Deep reasoning might need Sonnet or Opus.
-
Specify a
modelper subagent:-
Run
-
Code Preview
cat > model_routing.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") FAST_MODEL = os.environ.get("DEEPAGENTS_FAST_MODEL", "anthropic:claude-haiku-4-5-20251001") researcher = { "name": "researcher", "description": "Research topics and gather factual information.", "system_prompt": "You are a research assistant. Provide clear, organized facts.", "model": FAST_MODEL, } analyst = { "name": "analyst", "description": "Perform deep analysis requiring careful reasoning.", "system_prompt": "You are an analytical expert. Consider multiple angles, evaluate trade-offs, and provide thorough reasoning.", "model": MODEL, } agent = create_deep_agent( model=MODEL, subagents=[researcher, analyst], ) # Simple research task -> Haiku result1 = agent.invoke({"messages": [("user", "What are the main features of Python?")]}) print("Task 1 complete (routed to researcher/Haiku)\n") # Complex analysis -> Sonnet result2 = agent.invoke({"messages": [("user", "Analyze whether Python or Rust is better for building a high-performance data processing pipeline. " "Consider performance, developer productivity, ecosystem maturity, and hiring.")]}) print("Task 2 complete (routed to analyst/Sonnet)\n") EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") FAST_MODEL = os.environ.get("DEEPAGENTS_FAST_MODEL", "anthropic:claude-haiku-4-5-20251001") researcher = { "name": "researcher", "description": "Research topics and gather factual information.", "system_prompt": "You are a research assistant. Provide clear, organized facts.", "model": FAST_MODEL, } analyst = { "name": "analyst", "description": "Perform deep analysis requiring careful reasoning.", "system_prompt": "You are an analytical expert. Consider multiple angles, evaluate trade-offs, and provide thorough reasoning.", "model": MODEL, } agent = create_deep_agent( model=MODEL, subagents=[researcher, analyst], ) # Simple research task -> Haiku result1 = agent.invoke({"messages": [("user", "What are the main features of Python?")]}) print("Task 1 complete (routed to researcher/Haiku)\n") # Complex analysis -> Sonnet result2 = agent.invoke({"messages": [("user", "Analyze whether Python or Rust is better for building a high-performance data processing pipeline. " "Consider performance, developer productivity, ecosystem maturity, and hiring.")]}) print("Task 2 complete (routed to analyst/Sonnet)\n") -
-
Run it:
uv run model_routing.pySample output (your results may vary)Task 1 complete (routed to researcher/Haiku) Task 2 complete (routed to analyst/Sonnet)
This optimizes both cost and latency:
-
Cheap, fast models (Haiku) handle routine tasks
-
Expensive, capable models (Sonnet/Opus) handle complex reasoning
The main agent stays on Sonnet for orchestration, but delegates appropriately.
Exercise 6: The Default general-purpose Subagent
If you don’t define a subagent named general-purpose, DeepAgents automatically adds one with all the same tools as the main agent.
This is useful when you want context isolation without specialization—just a fresh slate for a task.
-
Create a script to demonstrate the default subagent:
-
Run
-
Code Preview
cat > default_subagent.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") # No subagents defined agent = create_deep_agent( model=MODEL, subagents=[], # Empty list ) result = agent.invoke({"messages": [("user", "Delegate this task to a subagent: Calculate the factorial of 10 and explain the result." )]}) print("=== Result ===") print(agent_response(result)) print("\n=== Delegation ===") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}") EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") # No subagents defined agent = create_deep_agent( model=MODEL, subagents=[], # Empty list ) result = agent.invoke({"messages": [("user", "Delegate this task to a subagent: Calculate the factorial of 10 and explain the result." )]}) print("=== Result ===") print(agent_response(result)) print("\n=== Delegation ===") for msg in result["messages"]: if hasattr(msg, 'tool_calls') and msg.tool_calls: for tc in msg.tool_calls: if tc['name'] == 'task': print(f"Routed to: {tc['args'].get('subagent_type')}") -
-
Run it:
uv run default_subagent.pySample output (your results may vary)=== Result === The factorial of 10 is 3,628,800. This represents the product of all positive integers from 1 to 10 (10 × 9 × 8 × 7 × 6 × 5 × 4 × 3 × 2 × 1). === Delegation === Routed to: general-purpose
Even though you didn’t define any subagents, the main agent can delegate to
general-purpose, which has the same tools and model but fresh context.
This is a powerful pattern for breaking up long-running conversations without needing specialized subagents.
|
The CLI’s |
Module Summary
You’ve accomplished:
-
Understanding the context bloat problem and how subagents solve it
-
Defining and using your first subagent
-
Observing the
tasktool in action -
Using descriptions as routing signals
-
Routing different models per subagent for cost/performance optimization
-
Leveraging the default
general-purposesubagent for context isolation
Key takeaways:
-
Subagents provide fresh, isolated context per task
-
Descriptions are the primary routing mechanism—write them carefully
-
You can optimize cost and latency by assigning different models to different subagents
-
The
general-purposesubagent is always available for context isolation without specialization
Next, you’ll learn how to define subagents in YAML files and use the loader pattern for cleaner, more maintainable agent architectures.