Claude Code Subagents Can Now Spawn Subagents
In Claude Code, a subagent can spawn its own subagents. This landed in v2.1.172.
To see why that matters, you have to look at what wasn't possible before. I maintain uc-taskmanager — a Claude Code pipeline that automates requirement → specification → plan → implementation → verification → commit across six agents. Its README stated this constraint twice:
"Subagents can't nest. So Main Claude orchestrates everything."
That premise is gone. So I moved the orchestrator out of Main Claude and into an agent.
This post is that migration.
- How nested subagent spawn actually works (the official spec, summarized)
- What got better and what got worse when the orchestrator became an agent
-
Why it's still worth it despite real downsides — and how it divides with
dynamic workflows - Whether it works headless (
claude -p) - And how I kept the pipeline alive when v2.1.217 disabled nested spawn by default
1. Nested subagents — the official spec
First, what the runtime actually guarantees. (As of July 2026; latest Claude Code version is 2.1.217.)
Since when, and how deep
| Version | Change |
|---|---|
| 2.1.172 | Subagents can spawn their own subagents (up to 5 levels deep) |
| 2.1.187 | A background subagent's depth is fixed when first spawned. Resuming it later from a shallower context doesn't change it |
| 2.1.193 | The subagent panel shows the full tree (siblings, direct children, and the path back to main) |
| 2.1.212 |
Per-session spawn cap of 200 (CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION) |
| 2.1.217 |
Nested spawn disabled by default. Set CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH to allow it. Concurrency cap of 20 added (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS) |
How it's enabled
If an agent definition's tools list includes Agent, that agent can spawn subagents of its own. To prevent a specific agent from spawning others, omit Agent from tools or add it to disallowedTools.
---
name: orchestrator
description: Orchestrates the entire WORK pipeline autonomously via nested spawn
tools: Agent, Read, Write, Edit, Bash, Glob, Grep
model: opus
---
One gotcha: writing a type list in parentheses inside a subagent definition — Agent(some_type) — does nothing; the list is ignored. The Agent(agent_type) allowlist syntax applies only to an agent running as the main thread via claude --agent. So you can't express "this agent may only call builder" through tools.
What depth means
Depth counts levels below the main conversation, regardless of whether each level runs in the foreground or background. A subagent at depth five doesn't receive the Agent tool at all, so it can't go deeper.
Our pipeline looks like this:
Main Claude (depth 0)
└─ orchestrator (depth 1)
├─ specifier (depth 2)
├─ planner (depth 2)
└─ builder / verifier / committer (depth 2)
We only use depth 2, so there's room under the cap of 5.
What nested subagents can't do
This constraint shaped the design more than anything else. A nested subagent cannot ask the user anything directly. Interactive tools like AskUserQuestion aren't available to it. If your pipeline has approval gates, this decides your architecture: the actual handling of approvals and decisions must always happen at the Main Claude boundary.
References
- Create custom subagents — Claude Code Docs (see "Spawn nested subagents" and "Session subagent limit")
- claude-code CHANGELOG
- Environment variables
2. Moving the orchestrator from Main Claude into an agent
Before — orchestration scattered across the terminal session
Back when nesting wasn't possible, the structure looked like this:
Main Claude ──spawn──> specifier → returns dispatch XML
Main Claude ──spawn──> planner → returns dispatch XML
Main Claude ──spawn──> scheduler → returns XML saying "builder is next"
Main Claude ──spawn──> builder → ...
Main Claude ──spawn──> verifier → ...
Main Claude ──spawn──> committer → ...
There was an agent called scheduler that did no LLM work of its own. It read the DAG and returned XML naming what to call next; the actual spawn was always done by Main Claude. Because nesting was impossible, the coordinator couldn't coordinate — it could only write coordination memos.
The consequences:
- Approval gates, context hand-off, and resume logic were all scattered across Main Claude (the terminal session)
- Every stage transition passed through Main's context — at least 3N round trips for N TASKs
- We observed trigger failures where the pipeline stalled after specifier (a row in the WORK list, but no WORK folder)
After — a single orchestrator agent
With nesting from v2.1.172, the structure became:
[WORK] tagged message
│
▼ (work-pipeline SKILL)
Main Claude ── spawns orchestrator once (raw request + REFERENCES_DIR + mode=gated|auto)
(handles gates) │ depth 1
▼
orchestrator ← flow control + TASK DAG scheduling + autonomous decisions + logging
│ nested spawn (depth 2)
┌──────────────────┼──────────────────────────────┐
▼ ▼ ▼
specifier → planner → [per TASK] builder → verifier → committer
(creates WORK) (PLAN+DAG) (DAG READY order, builder retried ≤3 on FAIL)
│
▼
Final WORK summary + list of auto-decisions returned to Main Claude
Three core principles:
- The orchestrator is a coordinator. Heavy reasoning (requirements, design, implementation) is delegated by nest-spawning the existing agents. The orchestrator itself only schedules, mediates decisions, and logs.
-
schedulerwas deleted. It was pure coordination with no LLM work of its own, so the DAG logic was absorbed inline into the orchestrator. - Main Claude spawns nothing but the orchestrator.
How approval gates work
Because of the constraint from section 1 — nested subagents can't ask the user — gates are built on a yield model.
At a gate, the orchestrator returns an XML signal and stops (parks):
<gate type="stage" work="WORK-01" stage="specifier">
<summary>Requirement spec complete — 5 FRs, 2 NFRs</summary>
</gate>
There are two kinds of gate:
-
type="stage"— fixed gates: after specifier (GATE-1) and after planner (GATE-2) -
type="decision"— dynamic escalation. Whenever the orchestrator or a child judges that the user has to decide something, it emits background + options + a recommendation, at any point in the run
Main Claude relays the signal, asks the user, and on approval resumes the parked orchestrator with SendMessage, preserving its context. If the handle is gone (session ended, say), it spawns a fresh orchestrator reconstructed from the activity log and DECISIONS.md on disk. Disk is always the source of truth; the parked agent handle is just an optimization.
In mode=auto (triggered when the request says "auto"), there are no gate stops: the orchestrator runs to completion in a single spawn, resolving every judgment call with its recommended option and recording each one in DECISIONS.md and the final report's auto-decisions section.
What got better
① Round trips disappeared. Six stages × N TASKs collapsed into one Main Claude spawn. Intermediate output — build logs, file contents, verification output — never reaches Main's context at all.
② Reference docs are read once. This was the unexpected win. Previously all six agents each read 5–8 reference files from disk. Now the orchestrator reads them once and slices them against the section consumption matrix at the top of each reference file, shipping the slices to each child's prompt as a <ref-cache>. Children never touch disk.
| Section | orchestrator | specifier | planner | builder | verifier | committer |
|---------|:---:|:---:|:---:|:---:|:---:|:---:|
| § 1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| § 6 | ✅ | | | ✅ | ✅ | ✅ |
...
This only works because the orchestrator assembles child prompts directly. When Main Claude did the spawning, there was no single place to enforce the distribution.
③ DAG parallelism got easy. When several TASKs are READY, the orchestrator issues multiple builder spawns in the same turn. Going through Main Claude would have chopped that at every user-turn boundary.
④ Logging has one owner. STAGE_START is written right before spawning a child; STAGE_DONE is written after the gate clears, when a gate exists. That ordering is what stops a cross-session resume from skipping an unapproved gate. Children write no logs at all and return pure artifacts.
What got worse
① Observability drops. Most of the execution happens inside the subagent tree, so all you see in the terminal is the orchestrator's summary. You have to build observability yourself, through log design.
② The user-interaction path got complicated. One gate now takes: return <gate> XML → yield → Main asks the user → resume via SendMessage → (on failure) fall back to a log-based re-spawn.
③ Context management is binary. Subagents only support auto-compaction. No manual /clear, no /compact, no partial clearing. Your options are "keep context = SendMessage" or "reset = TaskStop + re-spawn". There is no middle.
④ You're coupled to runtime policy. The whole architecture rests on one runtime behavior: that subagents receive the Agent tool. That isn't my code. (→ section 5)
3. Why do it anyway
That's not a short list of downsides. I still wouldn't go back. Two reasons.
3-1. The unit of delegation becomes the whole pipeline, not a stage
This is the essence. Before nested spawn, you could delegate one stage of work to a subagent. Now you can delegate the entire pipeline. What follows from that:
① It doesn't break at user-turn boundaries. In the old structure, the pipeline advanced only as long as Main Claude remembered to call the next agent. That's exactly why we saw stalls after specifier. Now the orchestrator loops inside itself. Throw one [WORK] at it and it runs to the end.
② Main's context stays empty. While the pipeline runs, only the final summary enters Main Claude's context. No build logs, no verification output, no file contents. A long WORK doesn't pollute your main conversation.
③ Unattended execution becomes natural. Because the delegation unit is the whole pipeline, one claude -p call takes you from requirement to commit. Queue requirements at night, review in the morning. You can't do that with stage-level delegation — Main has to intervene at every step.
④ Retry and resume become an internal loop. When verifier returns FAIL, the orchestrator re-dispatches builder (up to 3 times). None of it surfaces to the user. That retry judgment used to live in Main Claude, which made it depend on the mood of the session's conversation.
Weighed against the downsides:
| Downside | What it really is | Offset |
|---|---|---|
| Lower observability | One-time implementation cost | The activity log became canonical, which gave us cross-session resume and an audit trail. Structured logs beat terminal scrollback |
| Complex interaction path | One-time implementation cost | Gates went from "Main asks if it feels like it" to an explicit protocol. Where execution stops is now fixed in code |
| Binary context management | Standing constraint | In practice, lifetimes are per-WORK, so partial clearing almost never came up |
| Runtime coupling | Standing risk | Absorbed by capability probing + degraded mode (→ section 5) |
Most of the downsides are costs you pay once; the benefits are collected on every WORK. That asymmetry is the answer. Three of the four downsides above are already closed in code; all four benefits still show up every run.
3-2. So what about dynamic workflows?
There's an obvious question here. Claude Code already has dynamic workflows (v2.1.154+): a JavaScript script that orchestrates dozens to hundreds of subagents through agent() / parallel() / pipeline(). If you need orchestration, why not just use that?
The docs draw the line precisely in one phrase: who holds the plan.
| Workflow (script) | Orchestrator agent | |
|---|---|---|
| Decides what runs next | The script (deterministic) | The LLM (adaptive) |
| Where intermediate results live | Script variables | Agent context + artifacts on disk |
| Scale | Dozens to hundreds (16 concurrent, 1000 total) | A handful to dozens |
| Resume | Same session only — exit and it starts fresh | Log-based on disk, across sessions and days |
| Mid-run user input | Not possible | Possible via <gate> yield |
| What's repeatable | The orchestration itself | The agent definitions |
| Availability | Paid plans; can be disabled org-wide | Wherever subagents work |
The decisive line is in the docs' own constraints table:
No mid-run user input — Only agent permission prompts can pause a run. For sign-off between stages, run each stage as its own workflow
In other words, the official guidance for stage sign-off is: split the workflow per stage. Our pipeline has GATE-1 (requirement approval), GATE-2 (plan approval), and dynamic decision gates that can fire at any point during execution. That last one can't be split into stages ahead of time, because you don't know when it will fire. It isn't structurally expressible.
The point is that they're for different things. The overlap, though, is wider than you'd expect.
A subagent isn't a pipeline by nature. It's a worker: you delegate once, it returns a result, done. What turns it into a pipeline is the collaboration protocol you impose through skills and reference documents — what order, what artifacts, what formats. The agents read that and move. With nested spawn, the entity that enforces the protocol (the orchestrator) also moved inside the agent layer.
Which means a good share of what only workflows could do is now absorbed by an agent pipeline. Stage fan-out, result collection, retry on failure, proceed-after-verification — the shapes of orchestration are expressible without a script. It fits especially well when a run involves approvals, spans days, or treats on-disk artifacts as canonical. A workflow starts over if you leave the session, and it can't ask a human anything mid-run.
Conversely, homogeneous bulk work and reproducibility belong to scripts. Migrating 500 files, auditing an entire codebase — "the same thing N times" is script territory, and the fact that the orchestration itself persists as a file you can diff, review, and re-run is a property only scripts have.
Situational flexibility, on the other hand, favors agents. A script's flow is fixed when it's written. When an unexpected dependency shows up mid-run, or a requirement turns out ambiguous, or a verification failure means the plan has to change, a script does only what was coded. The orchestrator adds a TASK on the spot, or raises a gate. Omissions, likewise, are caught not by determinism but by an independent verifier plus the activity log — verifier records per-TASK execution and acceptance-criteria status, and any point with a STAGE_START and no STAGE_DONE gets picked back up by resume logic.
So:
- When homogeneity, volume, and reproducibility matter → Workflow
- When judgment, adaptation, approvals, and long lifetimes matter → Orchestrator agent
They aren't mutually exclusive, either. Having the orchestrator call a Workflow during TASK scheduling (STEP C) when a large fan-out is needed looks like the natural next step — the agent holds approvals and state, the script takes the homogeneous parallelism.
4. It works headless
This was the part I most wanted to confirm, and the answer is yes.
env -u CLAUDECODE -u ANTHROPIC_API_KEY \
claude -p "[WORK] <requirement> auto" \
--dangerously-skip-permissions \
--output-format stream-json
With --output-format stream-json, the nesting is visible directly in the stream. Each event's parent_tool_use_id points at the parent agent's tool_use, so you can reconstruct orchestrator → specifier and orchestrator → builder exactly. In mode=auto it doesn't stop at gates: it creates the WORK folder, produces Requirement.md / PLAN.md / TASK-*.md, commits, and flips the WORK state — unattended, start to finish.
This is the evidence behind the "unattended execution" claim in 3-1.
Two caveats:
-
You can't resume inside headless. Within a single
claude -pcall you can't resume a parked agent withSendMessage; that needs a separate invocation (--resume <session-id>).mode=autoruns to completion in one pass so it doesn't care, andgatedwas interactive by premise anyway. -
Unset
CLAUDECODEbefore running. Otherwise you hit the guard against running Claude Code inside a Claude Code session.
Worth doing the math on the per-session spawn cap (200) too. A WORK with N TASKs uses 1 specifier + 1 planner + 3N. That fits up to about 60 TASKs in one session.
5. The problem I hit after adopting it — 2.1.217
That's the part that went well. Now the problem.
What I confirmed today:
- Nested spawn does not work on Claude Code 2.1.217.
- It works on 2.1.216.
The cause was in the changelog.
Changed subagents to no longer spawn nested subagents by default; set
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHto allow deeper nesting
The feature didn't disappear — the default flipped. The same release added a concurrency cap of 20 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS). It reads as a bundle of safety rails against unbounded fan-out, and the direction makes sense.
⚠️ As of this writing, the default value of
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHisn't listed in the env-vars docs. The sub-agents page's line about depth 5 being "fixed and not configurable" also looks like it predates the 2.1.217 change. All I can state firmly is the changelog line and the behavior I observed.
The symptom: not an error, but a quiet success
This was the core of the problem. When nested spawn was blocked, the orchestrator did not fail.
Starting up without Agent in its tool list, unable to call any children, it did everything itself. Analyzed the requirement, wrote the plan, wrote the code, verified it, committed, and reported success.
Judging by artifacts alone, everything is fine. There's a WORK folder, a Requirement.md, a commit. In reality:
- Role separation was gone — whoever implemented it also verified it
- Verification independence was gone — the entire reason verifier exists evaporated
- Context isolation was gone — six isolated context windows collapsed into one
Failing silently is the worst failure mode. Had the pipeline crashed I'd have known immediately; instead it reported success and I didn't notice for a while.
Fix ①: pin the version
The immediate remedy is simple. I dropped back to 2.1.216. Unblocking it with the environment variable (CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH) is an option too, but for something shipped as a plugin / npm package, "an architecture that depends on the user's environment variables" is the worse choice.
Fix ②: capability probing + degraded mode
Pinning a version only saves my machine. I can't control users' environments, so the pipeline itself has to detect this and survive it.
So I added STEP 0 to the orchestrator: a capability check that runs before anything else, before reading a single file.
#### STEP 0. Capability check — is nested spawn available? (highest priority)
**Do this before anything else. Decide before reading any file.**
Check whether the `Agent` tool is present in your own tool list.
| Result | Action |
|--------|--------|
| `Agent` present | Normal path — proceed to STEP 1 |
| `Agent` absent | **Degraded** — follow the procedure below |
When it decides "degraded", the orchestrator does nothing. And "nothing" is enforced strictly:
- Perform no work inline — standing in for a child role is forbidden
-
Read no files — not even the references. Never call
Read/Glob/Grep, not once - Create no artifacts — no WORK folder, no
Requirement.md, no activity log - As the very first response, return the following and terminate immediately
<capability-degraded reason="no-agent-tool">
<detail>Agent tool not injected into subagent; nested spawn unavailable</detail>
</capability-degraded>
The prompt even carries this note: "The block above is everything you need to return. Do not read xml-schema.md to check the format." LLMs have a habit of opening files to confirm the rules, so without an explicit prohibition, "read no files" doesn't hold.
Then Main Claude takes over the orchestrator role.
[normal] [degraded]
Main Claude (0) Main Claude (0) ← also acts as orchestrator
└─ orchestrator (1) ├─ specifier (1)
├─ specifier (2) ├─ planner (1)
├─ planner (2) └─ builder / verifier / committer (1)
└─ builder… (2)
One design decision matters here. I did not write a separate procedure for degraded mode. Main Claude simply reads orchestrator.md and executes the procedure written there. There is one canonical copy. The moment you maintain two orchestration procedures, they drift.
Exactly three things differ from the normal path:
| Item | Normal | Degraded |
|---|---|---|
| Child spawn depth | 2 (orchestrator spawns) | 1 (Main Claude spawns directly) |
| Gate handling | Orchestrator returns <gate> and yields → Main asks the user |
Main asks the user directly — no <gate> XML, SendMessage, or TaskStop needed |
| Who reads the references | Orchestrator | Main Claude |
Artifact formats, log event schema, <ref-cache> assembly rules, and resume logic are all identical. The activity log records ORCHESTRATOR_DEGRADED — reason=no-agent-tool once, right after ORCHESTRATOR_START, so the log alone tells you which mode a run used.
And even in degraded mode, standing in for a child role is still forbidden. If Main Claude writes code or commits directly, role separation disappears exactly as it did when nesting was blocked. Only the depth drops to 1; the six-agent pipeline runs unchanged.
So the "standing risk" I filed under runtime coupling in section 3 turned into a fixed cost. If nesting works, it runs at depth 2; if not, depth 1. Either way, the six roles stay separated.
What I took away
① Don't assume runtime capabilities — probe for them. Agent architectures rest on assumptions about which tools exist. A single minor release can flip one. Capability checks have to run first, before any side effects. If you decide after reading even one file, you're already late.
② The failure mode of an LLM pipeline isn't a crash — it's a plausible completion. Take away a tool and the agent doesn't stop. It muddles through alone and tells you it succeeded. Where normal software would have died on a null reference, the problem with an LLM is that it doesn't die. That's why "do not stand in for a role" has to be an explicit prohibition in the prompt.
③ The degraded path needs the same single source of truth. The temptation to write a separate fallback procedure is strong, but two copies will drift. Design it as "same procedure, different executor" and you keep one place to maintain.
④ If disk is the source of truth, most things recover. Session dropped, handle lost, fell into degraded mode — with the activity log and DECISIONS.md, the run resumes at exactly the right point. The parked agent handle is only ever an optimization.
Wrapping up
Nested subagent spawn genuinely changes how you design a multi-agent pipeline. The unit of delegation rises from a stage to the whole pipeline, and the moment it does, unattended execution, context isolation, and internal retries all follow.
In exchange, you design observability and interaction yourself, and you're exposed to runtime policy changes. One flipped default in 2.1.217 demonstrated that exposure.
Use nested spawn — but design what happens when it isn't there first. And hand the parts that need volume and determinism to dynamic workflows — the two aren't competitors.
Appendix — Extending with external agents (cross-verification and delegation)
Splitting the work into six roles pays one more dividend: you can swap engines per role.
Register an external coding agent with Claude Code over MCP (Codex, Cowork-style tools, and so on) and a few lines in the project's CLAUDE.md are enough to hand a specific point of the pipeline over to it. Since the pipeline already leaves Requirement.md / PLAN.md / TASK-NN.md / *_result.md behind, the context to hand an external agent is already prepared. The input for cross-verification comes for free.
Pattern A — delegate builder execution to an external agent
The external engine implements; Claude's verifier verifies. Because the implementer and the verifier are different model families, the bias of passing your own work is structurally reduced.
## Delegation policy (CLAUDE.md)
- Delegate code implementation in the builder stage to the Codex MCP.
Send: the full `TASK-NN.md`, relevant file paths, and acceptance criteria
- verifier is performed by Claude regardless of delegation. Delegated
implementations are held to the same criteria.
- If delegation fails or the MCP doesn't respond, fall back to the built-in
builder and record that fact in the activity log.
Pattern B — cross-verify after WORK completion, collected by risk rank
This one paid off more for the cost. When a WORK finishes, hand the full diff plus the requirement and plan documents to the external agent for review, take the results back as risk grades, and branch on the grade automatically.
## Post-WORK cross-verification policy (CLAUDE.md)
When every TASK in a WORK is complete:
1. Request verification from the Codex MCP.
Send: the WORK's full diff, `Requirement.md`, `PLAN.md`, per-TASK `*_result.md`
2. Results must come back with a risk grade (Critical / High / Mid / Low) and rationale.
3. Handling by grade:
- **High and above** → fold into this WORK as additional TASKs and
implement, verify, and commit immediately
- **Mid and below** → record as items under `TODO/` and close the WORK
4. Limit cross-verification to one round per WORK (additional TASKs do not
trigger another round).
This automates the "fix it" versus "write it down" call. The step where a human skims a review and sorts it disappears, and anything High or above comes back already implemented and committed inside the same WORK.
Practical caveats
- Fix the grading criteria in writing. Ask for "rate the risk" with no rubric and you get grade inflation. Defining what Critical/High/Mid/Low mean for your project cuts the variance sharply.
- Auto-implement only High and above. Let it fix everything automatically and the WORK never ends. Setting a floor is itself a design decision.
- Cap the rounds. If a TASK created by cross-verification triggers another cross-verification, you have a loop.
- It costs more. Two engines look at the same code, so tokens go up. But different model families don't share failure modes — this is qualitatively different from running the same model twice. Keep it as a switch you flip for important WORKs and the cost stays bounded.
In short, a side effect of moving the pipeline inside an agent via nested spawn is swappability at the role level. Because the orchestrator assembles child prompts directly, whether a given child is a Claude subagent or an external MCP agent becomes a one-line policy decision.
The pipeline described here is open source: uc-taskmanager — installable as a Claude Code plugin or via npm (uctm).
Top comments (0)