5.5 KiB
Agent and Subagent Definition Patterns
This guide covers how to define agents and subagents programmatically using the Claude Agent SDK.
Core Concepts
AgentDefinition - Defines a specialized agent with specific tools, prompts, and model configuration.
Programmatic Definition - SDK best practice is to define agents programmatically using the agents parameter in ClaudeAgentOptions, not filesystem auto-discovery.
Basic Agent Definition
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions
options = ClaudeAgentOptions(
agents={
"agent-name": AgentDefinition(
description="When to use this agent",
prompt="System prompt defining role and behavior",
tools=["Read", "Grep", "Glob"],
model="sonnet" # or "opus", "haiku", "inherit"
)
}
)
AgentDefinition Fields
| Field | Type | Required | Description |
|---|---|---|---|
description |
str |
Yes | Natural language description of when to use this agent |
prompt |
str |
Yes | Agent's system prompt defining role and behavior |
tools |
list[str] |
No | Array of allowed tool names. If omitted, inherits all tools |
model |
str |
No | Model override: "sonnet", "opus", "haiku", or "inherit" |
Common Patterns
Read-Only Analyzer Agent
For code review, architecture analysis, or documentation review:
"code-reviewer": AgentDefinition(
description="Reviews code for best practices, security, and performance",
prompt="""You are a code reviewer. Analyze code for:
- Security vulnerabilities
- Performance issues
- Best practice adherence
- Potential bugs
Provide constructive, specific feedback.""",
tools=["Read", "Grep", "Glob"],
model="sonnet"
)
Code Modification Agent
For implementing features or fixing bugs:
"code-writer": AgentDefinition(
description="Implements features and fixes bugs",
prompt="""You are a code implementation specialist.
Write clean, tested, well-documented code.
Follow project conventions and best practices.""",
tools=["Read", "Write", "Edit", "Grep", "Glob"],
model="sonnet"
)
Test Execution Agent
For running tests and analyzing results:
"test-runner": AgentDefinition(
description="Runs tests and analyzes results",
prompt="""You are a testing specialist.
Execute tests, analyze failures, and provide clear diagnostics.
Focus on actionable feedback.""",
tools=["Bash", "Read", "Grep"],
model="sonnet"
)
Multiple Agents Pattern
Orchestrator with specialized subagents:
options = ClaudeAgentOptions(
system_prompt="claude_code", # Orchestrator needs Task tool knowledge
allowed_tools=["Bash", "Task", "Read", "Write"],
agents={
"analyzer": AgentDefinition(
description="Analyzes code structure and patterns",
prompt="You are a code analyzer. Examine structure, patterns, and architecture.",
tools=["Read", "Grep", "Glob"]
),
"fixer": AgentDefinition(
description="Fixes identified issues",
prompt="You are a code fixer. Apply fixes based on analysis results.",
tools=["Read", "Edit", "Bash"],
model="sonnet"
)
}
)
Loading Agent Definitions from Files
While programmatic definition is recommended, you can still store agent prompts in markdown files:
import yaml
def load_agent_definition(path: str) -> AgentDefinition:
"""Load agent definition from markdown file with YAML frontmatter."""
with open(path) as f:
content = f.read()
parts = content.split("---")
frontmatter = yaml.safe_load(parts[1])
system_prompt = parts[2].strip()
# Parse tools (can be comma-separated string or array)
tools_value = frontmatter.get("tools", [])
if isinstance(tools_value, str):
tools = [t.strip() for t in tools_value.split(",")]
else:
tools = tools_value
return AgentDefinition(
description=frontmatter["description"],
prompt=system_prompt,
tools=tools,
model=frontmatter.get("model", "inherit")
)
# Load and register programmatically
investigator = load_agent_definition(".claude/agents/investigator.md")
options = ClaudeAgentOptions(
agents={"investigator": investigator}
)
Best Practices
- Use programmatic registration - Define agents via
agentsparameter, not filesystem auto-discovery - Set orchestrator system_prompt - Main agent needs
system_prompt="claude_code"to use Task tool - Specific descriptions - Agent descriptions determine when they're used
- Restrict tools - Limit agent tools to minimum needed for safety and clarity
- Match agent names - Ensure agent names in
agents={}match what orchestrator references
Anti-Patterns
❌ Relying on filesystem auto-discovery
# SDK will auto-discover .claude/agents/*.md but this is NOT recommended
options = ClaudeAgentOptions() # Missing explicit agents={}
✅ Programmatic registration
options = ClaudeAgentOptions(
agents={"agent-name": AgentDefinition(...)}
)
❌ Missing orchestrator system prompt
options = ClaudeAgentOptions(
agents={...}
# Missing system_prompt="claude_code"
)
✅ Proper orchestrator configuration
options = ClaudeAgentOptions(
system_prompt="claude_code", # Orchestrator knows how to use Task tool
agents={...}
)