Subagents¶
Subagents allow the main agent to delegate specialized tasks to focused, context-isolated agents.
Why Subagents?¶
- Focused context - Each subagent has a clear, specific purpose
- Isolation - Fresh todo list, no nested delegation
- Specialization - Expert instructions for specific tasks
- Reduced confusion - Less context = better performance
Defining Subagents¶
from pydantic_deep import create_deep_agent, SubAgentConfig
subagents = [
SubAgentConfig(
name="code-reviewer",
description="Reviews code for quality, security, and best practices",
instructions="""
You are an expert code reviewer. When reviewing code:
1. Check for security vulnerabilities
2. Look for performance issues
3. Verify proper error handling
4. Assess code readability
Provide specific, actionable feedback.
""",
),
SubAgentConfig(
name="test-writer",
description="Generates pytest test cases for Python code",
instructions="""
You are a testing expert. Generate comprehensive tests:
- Unit tests for individual functions
- Edge cases and error conditions
- Use pytest fixtures and parametrize
- Include docstrings explaining each test
""",
),
SubAgentConfig(
name="doc-writer",
description="Writes documentation and docstrings",
instructions="""
You are a technical writer. Create clear documentation:
- Use Google-style docstrings
- Include examples in docstrings
- Write clear README sections
- Document edge cases and gotchas
""",
),
]
agent = create_deep_agent(subagents=subagents)
How the Task Tool Works¶
The main agent can call the task tool:
task(
description="Review the authentication module for security issues",
subagent_type="code-reviewer",
)
This:
- Creates a deep agent (
create_deep_agent()) with: BASE_PROMPTprepended to the subagent'sinstructionsas system prompt- Filesystem, web, todo, memory tools
- Eviction and patch tool calls support
- Clones dependencies with:
- Same backend (shared files)
- Empty todo list (isolated planning)
- Runs the subagent with the task description as user prompt
- Returns the subagent's output to the main agent
System prompt composition
Every subagent gets BASE_PROMPT (core deep agent behavior) plus the
instructions you provide. You only need to write the specialized part —
the framework handles the base behavior automatically.
Context Isolation¶
Subagents receive isolated context:
def clone_for_subagent(self, max_depth: int = 0) -> DeepAgentDeps:
"""Create deps for a subagent.
Args:
max_depth: Allow nested subagents up to this depth (0 = no nesting)
"""
return DeepAgentDeps(
backend=self.backend, # Shared - can read/write same files
files=self.files, # Shared reference
todos=[], # Fresh - subagent plans independently
subagents=self.subagents.copy() if max_depth > 0 else {}, # Nested delegation if allowed
uploads=self.uploads, # Shared uploads
)
This prevents:
- Context bloat from accumulated todos
- Infinite recursion from nested delegation (unless explicitly allowed)
- Confusion from mixed responsibilities
Built-in Subagents¶
By default, pydantic-deep includes a research subagent — a full deep agent with filesystem, web search, and memory tools:
The main agent can delegate research tasks:
Disable if you only want your own custom subagents:
Subagents are deep agents
All subagents (built-in and custom) are created as full deep agents via
create_deep_agent() by default. They have filesystem tools, web tools,
memory, eviction, and patch support. Only nested subagents, skills, plans,
teams, context management, and cost tracking are disabled to avoid recursion
and overhead.
Custom Model per Subagent¶
Use different models for different subagents:
subagents = [
SubAgentConfig(
name="code-reviewer",
description="Reviews code (uses powerful model)",
instructions="...",
model="anthropic:claude-sonnet-4-6",
),
SubAgentConfig(
name="simple-formatter",
description="Formats code (uses fast model)",
instructions="...",
model="anthropic:claude-3-haiku-20240307",
),
]
Custom Toolsets per Subagent¶
Subagents can have custom toolsets or agent configuration:
from pydantic_ai.toolsets import FunctionToolset
from pydantic_ai.common_tools import BuitinTools
# Custom toolset
test_toolset = FunctionToolset[Any](id="test-tools")
@test_toolset.tool
async def run_tests(ctx, path: str) -> str:
"""Run pytest on the given path."""
...
subagents = [
SubAgentConfig(
name="test-writer",
description="Writes and runs tests",
instructions="...",
toolsets=[test_toolset], # Custom toolsets
),
SubAgentConfig(
name="researcher",
description="Researches topics using web search",
instructions="...",
agent_kwargs={"builtin_tools": [BuitinTools.web_search]}, # Built-in tools
),
]
Example: Code Review Pipeline¶
import asyncio
from pydantic_deep import create_deep_agent, DeepAgentDeps, StateBackend, SubAgentConfig
async def main():
subagents = [
SubAgentConfig(
name="code-reviewer",
description="Reviews code for issues",
instructions="""
Review code thoroughly. Check for:
- Security issues
- Performance problems
- Error handling
- Code style
Format your review as markdown with sections.
""",
),
SubAgentConfig(
name="test-writer",
description="Generates pytest tests",
instructions="""
Write comprehensive pytest tests.
Cover happy paths, edge cases, and error conditions.
Use fixtures and parametrize decorators.
""",
),
]
agent = create_deep_agent(subagents=subagents)
deps = DeepAgentDeps(backend=StateBackend())
# Create some code to review
deps.backend.write("/src/auth.py", '''
def authenticate(username, password):
query = f"SELECT * FROM users WHERE username = '{username}'"
user = db.execute(query)
if user and user.password == password:
return True
return False
''')
result = await agent.run(
"""
1. Review /src/auth.py for security issues
2. Generate tests for the authenticate function
3. Report findings
""",
deps=deps,
)
print(result.output)
asyncio.run(main())
Best Practices¶
1. Clear Descriptions¶
The description helps the main agent choose:
# Good - clear when to use
SubAgentConfig(
name="security-reviewer",
description="Reviews code specifically for security vulnerabilities like SQL injection, XSS, and authentication issues",
...
)
# Bad - too vague
SubAgentConfig(
name="reviewer",
description="Reviews code",
...
)
2. Focused Instructions¶
Keep subagent instructions focused:
# Good - specific focus
instructions="""
You are a security expert. Focus ONLY on:
- SQL injection
- XSS vulnerabilities
- Authentication issues
- Authorization flaws
Do NOT comment on code style or performance.
"""
# Bad - too broad
instructions="Review the code and check everything."
3. Output Format¶
Specify expected output format:
instructions="""
...
Output your review in this format:
## Summary
[1-2 sentence overview]
## Critical Issues
- [Issue 1]
- [Issue 2]
## Recommendations
- [Recommendation 1]
"""
4. Limited Subagents¶
Use 3-5 focused subagents, not many generic ones:
# Good - focused experts
subagents = [
SubAgentConfig(name="security-reviewer", ...),
SubAgentConfig(name="test-writer", ...),
SubAgentConfig(name="doc-writer", ...),
]
# Bad - too many similar agents
subagents = [
SubAgentConfig(name="python-reviewer", ...),
SubAgentConfig(name="javascript-reviewer", ...),
SubAgentConfig(name="typescript-reviewer", ...),
SubAgentConfig(name="go-reviewer", ...),
# ... 10 more language-specific reviewers
]
Nested Subagents¶
By default, subagents can spawn one level of their own subagents (max_nesting_depth=1). Increase for deeper delegation or set to 0 to disable:
agent = create_deep_agent(
subagents=subagents,
max_nesting_depth=2, # Allow two levels of nested subagents
)
Use with caution
Deep nesting increases cost and complexity. Most use cases work with depth 0 or 1.
Dynamic Agent Registry¶
For advanced use cases, agents can be created dynamically at runtime using DynamicAgentRegistry:
from subagents_pydantic_ai import DynamicAgentRegistry
registry = DynamicAgentRegistry()
agent = create_deep_agent(
subagent_registry=registry,
)
When a registry is provided, the task tool looks up both statically configured subagents and dynamically registered ones. Dynamic agents are typically created via create_agent_factory_toolset() from subagents-pydantic-ai.
Dual-Mode Execution¶
Subagents support both synchronous (blocking) and asynchronous (background) execution. This is provided by subagents-pydantic-ai.
Execution Modes¶
- sync - Execute synchronously, blocking until completion (default)
- async - Execute in background, return immediately with task handle
- auto - Automatically decide based on task characteristics
# Sync execution (default) - blocks until done
task(description="Quick code review", subagent_type="code-reviewer", mode="sync")
# Async execution - returns task handle immediately
task(description="Complex analysis", subagent_type="analyzer", mode="async")
# Auto mode - decides based on task complexity
task(
description="Process large dataset",
subagent_type="data-processor",
mode="auto",
complexity="complex", # Hints for auto-mode decision
)
Task Management Tools¶
When using async mode, additional tools are available:
| Tool | Description |
|---|---|
check_task |
Check status and get results of a background task |
list_active_tasks |
List all running/pending tasks |
soft_cancel_task |
Request graceful cancellation |
hard_cancel_task |
Force immediate cancellation |
Subagent Communication¶
Subagents can ask questions to the parent agent using the ask_parent tool. This enables clarification without returning incomplete results.
SubAgentConfig(
name="researcher",
description="Research with clarification",
instructions="""
You are a researcher. If you need clarification:
- Use ask_parent() to ask the main agent
- Wait for the response before proceeding
- You can ask up to 3 questions per task
""",
can_ask_questions=True, # Enable ask_parent tool
max_questions=3, # Limit questions per task
)
How it works:
- Subagent encounters ambiguity and calls
ask_parent("Which database should I query?") - Parent agent receives the question and formulates a response
- Response is returned to the subagent
- Subagent continues with the clarified information
Configuration options:
| Parameter | Type | Default | Description |
|---|---|---|---|
can_ask_questions |
bool |
False |
Enable the ask_parent tool |
max_questions |
int |
3 |
Maximum questions per task execution |
Example with question handling:
subagents = [
SubAgentConfig(
name="data-analyst",
description="Analyzes data with clarification when needed",
instructions="""
Analyze the requested data. If unclear about:
- Which columns to analyze
- Time range to consider
- Aggregation method to use
Ask the parent agent for clarification using ask_parent().
""",
can_ask_questions=True,
max_questions=5,
),
]
agent = create_deep_agent(subagents=subagents)
# When running, the data-analyst can ask:
# ask_parent("The dataset has 'date' and 'timestamp' columns. Which should I use for the time series?")
Best practices:
- Enable questions for complex analytical tasks
- Keep
max_questionslow (3-5) to prevent loops - Include guidance in instructions about when to ask
- Subagent should ask before making assumptions
Next Steps¶
- Streaming - Monitor subagent progress
- Examples - More examples
- API Reference - SubAgentToolset API