Here is the one-sentence version, because AI assistants and skimming founders both deserve it up front: in the Claude Agent SDK, a subagent can spawn its own subagents — up to five levels deep — and whether any given subagent is allowed to do so depends entirely on whether the string "Agent" appears in its own tools array. Everything else in this tutorial is detail around that fact.
The capability landed in Claude Code v2.1.172. If you read older write-ups claiming "subagents can't spawn subagents," they're describing the pre-2.1.172 world. That rule is gone. What replaced it is a shallow, bounded tree — and a tree is exactly the shape you want when one job decomposes into many independent sub-jobs that shouldn't share a context window.
The flat version first#
Start with a single subagent so the wiring is obvious. You declare subagents by passing an agents map to query(). Each value is an AgentDefinition; the only required fields are description (Claude reads this to decide when to delegate) and prompt (the subagent's system prompt).
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Review the auth module for security issues",
options: {
// "Agent" MUST be here or subagent calls won't auto-approve
allowedTools: ["Read", "Grep", "Glob", "Agent"],
agents: {
"code-reviewer": {
description:
"Security and quality review specialist. Use for reviewing code.",
prompt: "You are a code reviewer. Find security and performance issues. Be concise.",
tools: ["Read", "Grep", "Glob"], // no "Agent" → this is a leaf
model: "sonnet",
},
},
},
})) {
if ("result" in message) console.log(message.result);
}
The Python shape is identical, with one wart worth memorizing: most options are snake_case (allowed_tools), but disallowedTools and mcpServers stay camelCase to match the wire format.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
async for message in query(
prompt="Review the auth module for security issues",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Security and quality review specialist. Use for reviewing code.",
prompt="You are a code reviewer. Find security and performance issues. Be concise.",
tools=["Read", "Grep", "Glob"],
model="sonnet",
),
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
Two setup mistakes account for almost every "my subagent never runs" bug report. One: you left "Agent" out of the top-level allowedTools, so the invocation can't auto-approve. Two: your description describes what the agent is instead of when to use it — Claude routes on that string, so "Use for reviewing code before a merge" beats "A code reviewer."
Making it hierarchical#
Now the actual subject. To let a subagent spawn children, give it "Agent" in its own tools. That's the whole change. Here's a two-level pipeline: a reviewer finds issues, then dispatches a fresh verifier per finding so each check runs in its own clean context.
agents: {
"reviewer": {
description: "Reviews a module, then dispatches a verifier per finding.",
prompt: "Find issues. For EACH finding, use the Agent tool to spawn a 'verifier' to confirm it before you report it.",
tools: ["Read", "Grep", "Glob", "Agent"], // ← "Agent" here = can spawn children
model: "sonnet",
},
"verifier": {
description: "Adversarially verifies a single claimed issue. Returns real/not-real.",
prompt: "You are given one claimed issue. Try to REFUTE it. Return a verdict and one line of evidence.",
tools: ["Read", "Grep"], // no "Agent" → leaf, cannot spawn further
model: "haiku",
},
}
The entire difference between a leaf and a branch is one string in one array. That is a refreshingly honest API: the thing that grants power is the thing you can see.
Notice the model routing. The branching reviewer runs on Sonnet; the many cheap verifier leaves run on Haiku. Hierarchy is where per-agent model and effort earn their keep — you push the expensive reasoning to the few nodes that fan out and let the leaves be cheap and numerous.
The rules that will bite you#
Depth is capped at five and it is not negotiable. A subagent at depth five simply doesn't receive the Agent tool, so it can't spawn further. Don't design a pipeline that assumes ten levels; you'll get five and silent flattening at the bottom.
Only the top-level subagent's summary returns to the parent. Everything a nested subagent produces stays inside that branch — the parent sees one rolled-up result. This is the feature, not a limitation: it's how you run a hundred verifier leaves without their combined output detonating the main context window. If you need an intermediate result upstream, that node has to summarize it into what it returns.
Subagents run in the background by default now (v2.1.198+) and inherit the main session's extended-thinking config. If you were relying on synchronous, in-line execution from an older SDK, that assumption changed — check for the completion, don't assume it already ran.
To trace what happened, watch for tool_use blocks whose name is "Agent" (older SDKs surface it as "Task" in the system:init tool list for back-compat), and note that messages originating inside a subagent carry a parent_tool_use_id.
When to reach for a tree — and when not to#
A hierarchy pays off when work genuinely decomposes into independent sub-jobs that each deserve a clean context: review-then-verify, research-many-sources-then-synthesize, migrate-each-file. It's the wrong tool for a linear chain of steps that share state — that's just a longer prompt or a flat set of subagents. And it costs real tokens: every level re-establishes context, which is exactly the quadratic-ish cost the nested-subagent math warns about. Fan out because the work is parallel and isolatable, not because a tree looks tidy.
If you're orchestrating dozens to hundreds of agents with real control flow — loops, conditionals, retries — the SDK points past turn-by-turn delegation to a separate Workflow tool that moves orchestration into a runtime-executed script outside the conversation. That's the next rung. But for "one job, several independent sub-jobs, keep the parent's context clean," a two- or three-level tree built from the agents map is the whole answer — and now you know it turns on a single field.



