Delegating Work to Subagents in the Claude Agent SDK
A subagent is a child agent with its own isolated context that a parent agent can hand a subtask to.
Search across all documentation pages
A subagent is a child agent with its own isolated context that a parent agent can hand a subtask to.
Instead of a single agent trying to hold an entire large task, including every intermediate step of every sub-problem, in one context window, a parent can delegate self-contained pieces to subagents and only receive back their final results.
Subagents exist for two related reasons: context isolation and parallelism.
Isolation means a subagent's exploratory reasoning, failed attempts, and intermediate tool calls never pollute the parent's context; the parent sees a clean result.
Parallelism means independent subagents can run concurrently, since neither depends on the other's intermediate state.
This page covers how to define subagents, when a task is a good candidate for delegation, and how tool scoping and results flow between parent and child.
Quick-reference recipe card - copy-paste ready.
from claude_agent_sdk import query, AgentOptions, SubagentConfig
options = AgentOptions(
allowed_tools=["file_edit", "bash"],
subagents=[
SubagentConfig(
name="frontend-reviewer",
description="Reviews React component changes for correctness and style.",
allowed_tools=["file_edit"],
),
SubagentConfig(
name="backend-reviewer",
description="Reviews API route changes for correctness and security.",
allowed_tools=["file_edit", "bash"],
),
],
)
async for message in query(
prompt="Review this PR's frontend and backend changes using the two reviewer subagents.",
options=options,
):
print(message)When to reach for this:
import asyncio
from claude_agent_sdk import query, AgentOptions, SubagentConfig
async def audit_monorepo_packages(repo_path: str, packages: list[str]) -> None:
subagents = [
SubagentConfig(
name=f"audit-{pkg}",
description=f"Audits the {pkg} package for outdated dependencies and lint errors.",
allowed_tools=["bash", "file_edit"],
tool_config={
"bash": {"allowed_commands": ["npm outdated", "npm run lint"]},
"file_edit": {"allowed_paths": [f"packages/{pkg}/**"]},
},
)
for pkg in packages
]
options = AgentOptions(
cwd=repo_path,
allowed_tools=["file_edit"],
subagents=subagents,
)
prompt = (
f"Use the audit subagents for each of {packages} to check for outdated "
"dependencies and lint errors, then write a combined AUDIT.md summary "
"at the repo root with one section per package."
)
async for message in query(prompt=prompt, options=options):
if message.get("type") == "text":
print(message["text"], end="", flush=True)
elif message.get("type") == "subagent_result":
print(f"\n[{message['subagent_name']} finished]")
asyncio.run(audit_monorepo_packages("/repo", ["billing", "auth", "search"]))What this demonstrates:
bash and file_edit access independently of the parent and of each other.file_edit for itself, since its own job is just writing the combined summary.name, a description the parent model uses to decide when to invoke it, and its own allowed_tools/tool_config.| Signal | Favors | Reason |
|---|---|---|
| Task needs one more capability, no independent exploration | Adding a tool | Simpler; no extra context boundary needed |
| Task is a self-contained subtask with its own exploration | Subagent | Keeps exploratory noise out of the parent's context |
| Multiple similar subtasks across independent inputs | Subagents (one per input) | Enables parallel execution |
| Subtask needs a narrower or different tool scope than the parent | Subagent | Tool scope is per-subagent, not shared |
# A subagent's description is what the parent model reads to decide
# when to invoke it - treat it like a tool description, not a comment.
SubagentConfig(
name="test-runner",
description="Runs the project's test suite and reports only failing tests.",
allowed_tools=["bash"],
tool_config={"bash": {"allowed_commands": ["pytest"]}},
)| Parameter | Type | Description |
|---|---|---|
name | str | Identifier the parent uses to invoke this subagent |
description | str | Tells the parent model when this subagent applies |
allowed_tools | list[str] | Tool scope for this subagent, independent of the parent |
tool_config | dict | Per-tool scoping for this subagent's tools |
allowed_tools into every subagent defeats the purpose of scoping work narrowly. Fix: scope each subagent to only what its specific job requires.| Alternative | Use When | Don't Use When |
|---|---|---|
| A single agent with more tools | The task needs one more capability, not independent exploration | The subtask would otherwise flood context with irrelevant reasoning |
| Sequential tool calls in one agent | Steps genuinely depend on each other's output | Steps are independent and could run concurrently |
| Separate top-level query() calls | Subtasks are fully unrelated, no shared parent task | Subtasks are part of one coherent parent task that needs a combined result |
allowed_tools.The same way it decides to call any tool: the model reads the subagent's description alongside the conversation and chooses to invoke it when the task matches.
Yes, when they're independent of each other. Subagents whose tasks don't depend on one another's output can be dispatched and awaited concurrently.
Not by default; it typically starts with a fresh context for its own invocation. Pass any needed context explicitly in the prompt you give the subagent.
The SDK's subagent model supports nesting in principle, since each subagent runs its own internal tool-use loop, but deep nesting adds real overhead and debugging difficulty; most tasks are well served by one level.
Whatever you scope it to via its own allowed_tools and tool_config, independent of the parent's tool scope; a subagent is not automatically granted the parent's tools.
A tool call executes one discrete action and returns a result. A subagent invocation runs a full internal tool-use loop, potentially calling several tools and reasoning across multiple steps, before returning one final result.
When it's a single, simple action that a direct tool call would handle just as well; the overhead of a fresh context and internal loop isn't worth it for trivial steps.
The failure (or an incomplete/error result) comes back to the parent as the observation from that invocation, and the parent's own loop has to decide how to proceed, the same way it would handle any failed tool call.
Concurrency limits are an application-level concern; you control how many subagent invocations you dispatch at once in your own orchestration code around query().
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the Claude Agent SDK (latest release, Python and TypeScript). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 16, 2026