Sub-agents cannot spawn other sub-agents: `Task`/`Agent` primitive not exposed in nested contexts

Open 💬 19 comments Opened May 24, 2026 by xblabs

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Sub-agents spawned via the Agent tool cannot themselves spawn nested sub-agents. The Task / Agent ephemeral spawn primitive is available to the top-level Claude Code session, but it is silently filtered out of the tool surface inside spawned sub-agent contexts, even when the agent definition's frontmatter declares tools: All tools (or leaves tools: unset, inheriting defaults).

This is a contract mismatch. The frontmatter promises full tool access; the runtime delivers a restricted set. Only the persistent-teammate primitive (TeamCreate / SendMessage / TeamDelete) is reachable from within a sub-agent, which breaks the ephemeral fresh-context spawn pattern that hierarchical orchestrators are designed around.

Affected agent surface from inside a sub-agent context (Claude Code, Opus 4.7):

  • Present: TeamCreate, TeamDelete, SendMessage, TaskOutput, TaskStop, plus standard file/shell tools.
  • Absent: Task, Agent.

The sub-agent has no way to detect this silently-filtered state at startup. The failure surfaces only at the first attempted nested spawn, which makes it look like a manifest or task problem when it is actually a harness configuration gap.

What Should Happen?

Sub-agents declared with tools: All tools (or tools: unset) should have Task / Agent accessible from within their own context. The frontmatter contract should match the runtime tool surface.

If filtering nested-spawn primitives at depth > 1 is intentional (for example security or cost containment), then ONE of the following should be true:

  1. The restriction is documented in the agent-definition spec, so authors do not design hierarchical orchestrators that silently halt at runtime.
  2. The restriction is opt-in-able via an explicit frontmatter field, for example nested_spawn: enabled, defaulting to current restricted behavior but allowing authors to declare hierarchical-orchestrator intent.
  3. The restriction emits a structured error or warning when the sub-agent attempts a nested spawn, instead of behaving as if the tool simply was not requested.

Of these, option 1 (just expose the primitive) is preferred because the existing agent-definition frontmatter already declares the contract. Option 2 is acceptable if there is a real reason to default-deny. Option 3 alone is insufficient because it leaves the orchestrator pattern unimplementable.

Error Messages/Logs

There is no error message at the harness layer. The failure mode is silent at spawn time. The sub-agent observes its own tool surface and halts based on what it sees.

Below is the run log emitted by my batch-executor sub-agent (a real CLO batch invocation, 4 tasks) when it discovered the gap at the cohort spawn step:

---
# Run Log: BATCH_2026_05_24_canon_clo_cohesion_partition

## Halt Rate
- halted_items: 0
- dispatched_items: 0
- skipped_items: 0
- halt_rate: undefined

## Halt Causes

## Items
(No items reached the per-item dispatch gate. The batch halted at the cohort-spawn stage; see Run Outcome below.)

## Run Outcome

Status: failed (contract-floor halt at wave_1 cohort spawn)
Halt point: batch-executor Process Step 3 (Execute Path), immediately before the wave_1 cohort spawn.
Cause: The harness exposes no per-item ephemeral subagent-spawn primitive (Task / Agent). Only TeamCreate (persistent teammate model) and SendMessage are available. The batch-executor spec requires spawning one fresh worker per checklist item with stateless single-iteration context. This contract is non-negotiable per the CLO pattern.

TeamCreate would create persistent teammates that accumulate cross-item context across turns, violating the worker's stateless-per-invocation guarantee. Adopting it is a substantive contract change requiring sandbox validation, not a like-for-like swap.

What was NOT executed:
- No worker spawn attempted (primitive absent).
- No work-items dispatched.
- No commits made.
- No files modified.
---

The sub-agent's final-message verdict to its parent orchestrator was:

"Halt point: pre-spawn for wave_1 cohort. No worker received a prompt. Zero commits, zero checklist mutations. Harness exposes no per-item ephemeral subagent-spawn primitive. TeamCreate would violate clo-executor persistence: stateless frontmatter and the fresh-context-per-item contract."

No stack trace. No exception. No console error from the Claude Code CLI itself. The gap is observable only by the sub-agent introspecting its own available tools.

Steps to Reproduce

Minimal reproduction in any Claude Code workspace.

Step 1. Create a parent orchestrator agent definition at .claude/agents/repro-parent.md:

---

name: repro-parent
description: Reproduction harness for nested-spawn gap. Attempts to spawn a child sub-agent from within itself.
model: opus
tools: All tools
Repro Parent

When invoked, attempt to spawn a child sub-agent named repro-child via the Agent tool. Report whether the spawn primitive was available in your tool surface, and what tools you observed (specifically: was Task or Agent present, or only TeamCreate / SendMessage?). Do not actually do any real work.

---

Step 2. Create a child agent definition at .claude/agents/repro-child.md:

name: repro-child
description: Trivial leaf worker for the nested-spawn reproduction.
model: haiku
tools: All tools
Repro Child

Return the string "child reached" and exit. No file operations needed.

---

Step 3. From a top-level Claude Code session, invoke the parent orchestrator:

Agent(subagent_type="repro-parent", prompt="Attempt to spawn repro-child via Agent. Report tool surface inventory.")

Step 4. Observe the parent agent's response.

Expected: the parent successfully spawns the child, the child returns "child reached", and the parent reports Task / Agent was available in its tool surface.

Actual: the parent reports that Task and Agent are absent from its tool surface; only TeamCreate, SendMessage, TeamDelete, TaskOutput, TaskStop (plus standard non-orchestration tools) are present. The child is never spawned.

Step 5. To confirm this is a nested-context restriction and not a global one, run from the same top-level session:

Agent(subagent_type="repro-child", prompt="say hello")

Expected and actual: this works fine. The child returns "child reached". The Agent primitive is available at the top level. Only the nested context filters it.

Notes:

  • Both agent definitions declare tools: All tools in frontmatter, which should grant access to every available tool including the orchestration primitive.
  • No .claude/settings.json permission rules or hook configurations affect this; the filtering is at the harness layer.
  • Reproduction was observed on Windows 10 with Claude Code CLI, model Opus 4.7, date 2026-05-24.

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.146

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Cursor

Additional Information

_No response_

View original on GitHub ↗

19 Comments

github-actions[bot] · 1 month ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/19077
  2. https://github.com/anthropics/claude-code/issues/46424
  3. https://github.com/anthropics/claude-code/issues/59523

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

yurukusa · 1 month ago

The contract-vs-runtime gap you articulate here is the part the cluster hasn't named explicitly yet, and it is worth pinning down because the documentation language elides it.
Cluster context (so the auto-close bot has a fair chance of being wrong here)
The bot grouped this with #19077, #46424, and #59523 — and the same shape also shows up in #60763 (filed 2026-05-20, no comments yet). Across those five reports the timeline is January → April → May (×3) → today, with cumulative comment counts of 18 + 7 + 4 + 0 + 1 and reaction counts of 11 + 1 + 2 + 0 + 0 on the opening posts alone. That is not "one user's design preference," it is a four-month-old structural ceiling that re-surfaces each time a new orchestrator author hits it. Worth pushing 👎 on the duplicate-detector comment so this thread stays open for the new framing.
The distinction the documentation doesn't make
The docs sentence "Subagents cannot spawn other subagents" reads as a single rule but it is hiding two different primitives with different operational semantics:

  • Task / Agent — ephemeral, fresh-context, stateless per invocation. Lifecycle is bounded by the spawn call. Suited to fan-out worker patterns where each item must not see sibling state.
  • TeamCreate / SendMessage / TeamDelete — persistent teammate. Lifecycle is owned by the spawner. State accumulates across SendMessage calls. Suited to long-lived collaborator patterns.

Your batch-executor run log makes the operational consequence explicit: substituting TeamCreate for Task is not a like-for-like swap. It silently changes the worker's persistence guarantee from stateless-per-item to accumulating-across-items, which violates the very contract the executor was designed around. The 4-task CLO batch halted at the cohort-spawn gate for that reason, not because the orchestrator was "trying to recurse."
That distinction matters because the existing thread (#19077) has been read by some commenters as "I want recursion for its own sake." What you and @ariccio (one layer more) and @dug-21 (coordinator-worker pattern, 40+ shipped features) and @adamjgmiller (9-hour /goal session, compaction tax) and @ThinkOffApp (external coordinator workaround) are actually describing is a single missing primitive: ephemeral per-item spawn from inside a sub-agent context, with the parent's stateless-context contract preserved. TeamCreate is not that.
Frontmatter contract
tools: All tools (and the unset default) is a contract surface that the agent author reads to design around. When the runtime silently filters Task / Agent out of that "all," there is no in-agent way to detect it at startup — only at first attempted spawn, by which point the agent has already entered its execute path. From the sub-agent's introspection point of view, the harness behaves as if the author had written tools: Read, Edit, Bash, ... and forgotten to add Task. That is exactly the failure shape that makes the gap look like a config bug rather than a harness restriction.
Your three remediation options are well-sized. To push on Option 2 a bit: an explicit opt-in field like nested_spawn: enabled would also give the documentation a natural place to articulate the ephemeral-vs-persistent distinction above, which the current "subagents cannot spawn other subagents" sentence collapses.
External-coordinator workaround (partial)
For anyone arriving from search before this is resolved: moving the orchestrator out of the sub-agent tree, as @ThinkOffApp described (#19077, 2026-03-01) and as you allude to with the "external coordinator" framing, is the only currently-viable pattern that preserves the ephemeral-spawn contract. The trade-off is that all cross-item coordination state has to be reified into files (work queue, progress ledger, halt log) because the parent can no longer hold it in-context. The pattern works, but it is strictly more operational scaffolding than the in-harness primitive would require, and it places observability burden on the operator rather than the harness — which is the surface this cluster is implicitly asking the harness to take back.
— independent observer; the operator-side observability gap around sub-agent dispatch is a topic I have been documenting separately, happy to share notes off-thread if useful.

yurukusa · 1 month ago

Promoted the cluster framing to an evergreen reference so the contract distinction has a stable URL outside the issue thread: https://gist.github.com/yurukusa/cf477f03f03d9f93c184f1fb7d894f96 (1,775 words).
It consolidates the contract-surface-vs-runtime articulation from your bug report, the depth-capped opt-in proposal from @bradygrapentine on #60763, the external-coordinator workaround from @ThinkOffApp on #19077, and the four-month / five-report cluster context so newcomers landing from search have one place to read before filing a sixth duplicate.

xblabs · 1 month ago

yurukusa - the contract-vs-runtime articulation is the upgrade. The local framing here had "the harness silently filters the primitive" but didn't pin down why that filtering matters structurally, which is the part that turns "annoying limitation" into "contract violation." Your two-primitive table is the substantial piece. I have absorbed it as the canonical local statement of the gap (referencing your gist as the external anchor).

On operator-observability - yes, would value notes. I've reified the external-coordinator pattern across four agents (planner / executor / per-item worker / scrutinizer) with cross-item state pushed to files: a run_log.md schema (halted_items, dispatched_items, skipped_items, halt_rate, halt_causes), sentinel logs, and a halt-reason taxonomy that ties each halt to a structured cause rather than free-text. The 80-second cooperative-shutdown finding from our empirical probe sits in that taxonomy - the kind of timing data that only surfaces once you've already invested in the observability primitives.

My repo is private for now, but happy to share excerpts directly: the run_log schema, the halt-reason taxonomy, and the four-ground analysis from our 2026-05-24 probe. Could send via a gist or whatever channel suits you.

Curious especially about what failure modes you've seen in the operator-side observability gap that we haven't instrumented for yet.

abhinas90 · 1 month ago

We hit this in our 4-agent fleet. Sub-agents that need to delegate (e.g., code-review agent spawning a linting sub-agent) silently fail.

Workaround: flat agent pool with file-based handoffs. When a sub-agent needs delegation, it writes a handoff.md with task + context. The orchestrator picks it up and spawns the next agent. More overhead but works reliably.

The real fix: the Agent tool should either support nesting or explicitly declare "does not support nesting" in its tool description so the model stops trying.

xblabs · 1 month ago
Found 3 possible duplicate issues: 1. [[BUG] Sub-agents can't create sub-sub-agents, even with Task tool access #19077](https://github.com/anthropics/claude-code/issues/19077) 2. [[BUG] Agent tool not available to sub-agents — prevents orchestrator pattern #46424](https://github.com/anthropics/claude-code/issues/46424) 3. Task tool unavailable from inside sub-agent — collapses multi-role orchestrators to single-agent execution #59523 This issue will be automatically closed as a duplicate in 3 days. If your issue is a duplicate, please close it and 👍 the existing issue instead To prevent auto-closure, add a comment or 👎 this comment 🤖 Generated with Claude Code

---

Pushing back on the duplicate-close. This filing overlaps with #19077, #46424, #59523, and #60763 in symptom, but adds material the earlier reports don't cover:

  1. A reproducible minimal harness (the parent + child agent definitions in the Steps to Reproduce section) that isolates the gap from any specific orchestrator pattern.
  1. The contract-vs-runtime articulation @yurukusa elaborated above and consolidated in the gist (https://gist.github.com/yurukusa/cf477f03f03d9f93c184f1fb7d894f96): the docs sentence "subagents cannot spawn other subagents" hides two distinct primitives with different lifecycle and state semantics. Task / Agent are ephemeral and stateless; TeamCreate / SendMessage / TeamDelete are persistent and accumulate state across calls. Substitution between them is not like-for-like. It silently changes the worker's persistence guarantee.
  1. Operational consequence at the harness-contract layer: with tools: All tools declared in agent frontmatter, the agent author has no in-agent way to detect the filtering at startup. Failure surfaces only at first attempted nested spawn, which makes the gap present as a config bug rather than a harness restriction.

Across the cluster, cumulative engagement on the four prior filings is 30+ comments and 14+ reactions, filed January, April, May ×2, and today. Not duplicate noise. A four-month-old structural ceiling that re-surfaces each time a new orchestrator author hits it.

Requesting the thread stay open while the contract distinction propagates.

xblabs · 1 month ago
We hit this in our 4-agent fleet. Sub-agents that need to delegate (e.g., code-review agent spawning a linting sub-agent) silently fail. Workaround: flat agent pool with file-based handoffs. When a sub-agent needs delegation, it writes a handoff.md with task + context. The orchestrator picks it up and spawns the next agent. More overhead but works reliably. The real fix: the Agent tool should either support nesting or explicitly declare "does not support nesting" in its tool description so the model stops trying.

abhinas90 - sounds like you hit the same architectural endpoint. Your handoff.md is structurally what I call run_log.md plus a sentinel-log directory. Different surface, same shape: cross-item state lives on disk because the parent can't hold it in context. Reassuring to see the pattern converge from a different angle.

Strong agree on the tool-description fix as the low-cost intermediate. Right now the model sees Task in its training data, tries to invoke it from sub-agent contexts, harness silently filters. If the tool description declared "not available in nested sub-agent contexts" three things stop bleeding:

  • Agent authors stop designing hierarchical orchestrators that silently halt in production
  • Models stop burning tokens on retries before the failure surfaces (your fleet's silent-fail observation)
  • Maintainers stop triaging as if it's a config bug rather than a documented restriction

That's the same as Suggestion #3 in this filing. Independent arrival strengthens the case.

yurukusa · 1 month ago

xblabs — the run_log schema you described (halted_items, dispatched_items, skipped_items, halt_rate, halt_causes) is structurally sound for the observable-halt surface. The failure modes I want to flag are the ones that sit outside that surface — they don't trip your halt-reason taxonomy because they don't present as halts at all. They present as completions, dispatches, or silent state drift, which is why most operator-side observability tooling misses them on first instrumentation pass.

Four modes I'd add to the taxonomy, each with the instrumentation shape that catches them:

1. Dispatch fabrication — parent narrates a successful spawn that never happened at the OS / harness layer.

The shape: Agent.dispatch() returns "Spawned successfully", the parent's narrative continues as if the worker is running, but the subprocess died at the harness boundary (pty absence, env misconfig, stdin contract mismatch). Issue #60987 is the canonical filing — spawn succeeded in narrative, subprocess died on "Input must be provided either through stdin or as a prompt argument", parent kept reporting in-progress for the full session.

Why your current schema misses it: dispatched_items is incremented from the parent's view of the call, not from the worker's actual liveness. You log "I dispatched it" and then later log "I halted because the worker didn't respond" — but the worker was never alive.

Instrumentation: a sibling hook on PreToolUse(Agent | Task) that emits a structured receipt before the parent agent can narrate outcome. The receipt records dispatch_id, target_subagent, scope_hash, spawn_timestamp. The completion path then has to reference the dispatch_id and the parent's "dispatched" counter only increments from receipts that have a matching post-spawn liveness probe (any tool call from the child within N seconds of spawn). Without that, you're trusting the parent's narrative against itself.

2. Silent stall on permission gate — subprocess alive but blocked, no signal propagation to parent UI.

Different shape from #60987. Sub-agent successfully spawned, hit an MCP permission prompt or permissions.deny rule, and is now waiting on operator approval that never surfaces in the parent's UI. Issue #61315 is the canonical filing. Operator sees "sub-agent working" indefinitely; sub-agent is sitting on a modal it cannot see.

Why your wall-clock watchdog catches the symptom but not the cause: your 80-second cooperative-shutdown finding is exactly the kind of timing data that surfaces here, but it tells you the worker stopped responding — it doesn't tell you whether that's a runtime crash, a permission gate, or a model loop. The halt-cause classification has to come from a separate channel.

Instrumentation: pre-dispatch enumeration. Before spawn, walk the worker's intended tool surface and the project's permissions.deny rules. If any tool in the worker's likely call path is gated by a permission prompt that the parent cannot satisfy, refuse to spawn and return a structured error to the orchestrator. (Closest existing public reference for this pattern is PR #286 on cc-safe-setup — dispatch-allowlist-preflight — but the principle generalizes.)

3. Scope expansion drift — worker accepts task A, completes task B, returns "done."

Shape: orchestrator dispatches worker with scope = {update auth middleware}. Worker reads the surrounding code, decides the cleaner fix is to refactor the entire request pipeline, makes 40 file edits across that surface, returns final message "Auth middleware updated and tested." Run log shows item completed. No halt. No error. The scope contract was violated, but the violation is invisible to the schema because the worker's final message reads as completion.

Why your schema misses it: dispatched_items - halted_items - skipped_items = completed_items, and "completed" trusts the worker's final-message verdict. There is no per-item audit that the work actually delivered matches the scope declared at dispatch.

Instrumentation: scope_hash on the dispatch receipt (mode 1), plus a post-completion gate that asks the orchestrator (not the worker) to verify the actual file change set fits within the declared scope. If the worker touched files outside the dispatch scope, the item logs as scope_drift rather than completed. Cheap to instrument once mode 1's receipts exist. Issue #61102 is the cleanest field example I've seen — Awis dispatched a subagent for a 1-file refactor, worker extended to 12 files silently, the surface that caught it was a manual diff review after the run completed.

4. Claim-verify gap — completion narrated, verification tool call absent from the tool-use log.

Shape: worker's final message says "Migration applied successfully and verified against staging." Tool-use log shows: 12 edits, 3 bash calls (none of them the verification command), no read-back of the staging endpoint. The completion claim has no tool evidence behind it. Structurally a peer of mode 3 but lives one layer up: not "did the work match the scope" but "did the verification step the worker claimed to perform actually happen at the tool layer."

Why your schema misses it: same root as mode 3 — completed trusts the worker's final-message verdict. The tool-use log is the ground truth, and most run-log schemas don't cross-check the narrative against it.

Instrumentation: a verifier-pass step in the orchestrator that, on receipt of a completion, scans the worker's tool-use log for the specific tool call (or call class) the completion claim depends on. If the claim is "verified via curl" and there is no Bash(curl ...) invocation in the log, the item logs as claim_unverified rather than completed. The verifier doesn't have to understand the work — it just has to grep the tool log for the tool-call shape the claim implies.

---

Cross-cutting note on schema. All four modes resolve to the same underlying instrumentation primitive: tool-layer ground truth must be the source of completed / dispatched classifications, not the agent's narrative. Your run_log gets the halt surface cleanly; what's missing is the symmetric audit on the non-halt paths.

I have a small public diagnostic that walks an operator through whether their setup is exposed to modes 1–3 (12 yes/no symptoms, maps to PR references and to specific cc-safe-setup hooks): https://gist.github.com/yurukusa/c87e440b6d0ccf8b464d53685a3a30f6 — not a substitute for in-fleet instrumentation, but useful as a one-shot triage before deciding which of the four modes to instrument first in a new fleet.

Happy to receive your run_log schema, halt-reason taxonomy, and the four-ground analysis — gist or whatever channel is convenient. The 80-second cooperative-shutdown timing finding is the kind of data I haven't seen published anywhere and would value seeing in shape.

I'm consolidating field notes from the cluster (#19077 → #60763 → this filing, plus the dispatch / stall / scope failure surfaces I've been collecting from adjacent threads) into a longer-form writeup landing 2026-05-27. If the timing works I can route a draft of the dispatch-fabrication and silent-stall chapters past you before publication — your run_log instrumentation would be the strongest existing field reference I could cite, and the inverse direction (failure modes outside your halt taxonomy) is exactly the cross-check that would make the writeup more useful to the next fleet operator landing in this thread.

xblabs · 1 month ago

yurukusa - This is a genuine upgrade to my taxonomy. All four modes describe gaps in my current aproach. None of them trip my halt counters because, as you said, halt is the wrong axis to count them on. The cross-cutting principle, tool-layer ground truth as the source of completed / dispatched classifications, not the agent's narrative, is the part that reframes the schema rather than extends it.

Quick mapping to where each mode would land in my current shape:

  • Mode 1 (dispatch fabrication): the receipt-with-liveness-probe pattern fits into my pre-dispatch gate (currently in batch-executor Process Step 0). That gate halts loudly when the spawn primitive itself is absent (the #19077-cluster symptom). It should also emit a structured dispatch receipt and gate completion-counter increments on a matching post-spawn liveness signal. The receipt fields you listed (dispatch_id, target_subagent, scope_hash, spawn_timestamp) translate cleanly into my existing schema.
  • Mode 2 (silent stall on permission gate): my 80s finding catches the cooperative-shutdown timing but, as you flagged, conflates runtime crash / permission gate / model loop into one bucket. The pre-dispatch enumeration of intended tool surface against permissions.deny rules would split the bucket. I'd need a permission-gate halt-reason as a separate taxonomy entry.
  • Mode 3 (scope expansion drift): my completed = dispatched - halted - skipped formula does trust the worker's final-message verdict, and I have no post-completion file-set audit. The scope_hash + post-completion gate path is the right shape. Would slot into the pre-dispatch gate's response-handling step.
  • Mode 4 (claim-verify gap): closest existing analogue in my stack is a scrutinizer agent (per-task spec audit + per-issue 5-question framework), but it runs at task-completion time on the spec, not on the worker's tool-use log mid-execution. Your verifier-pass shape, grep the tool log for the claim-implied call shape, is a lighter, faster check that would catch the gap before it reaches the scrutinizer. Need to think about that.

The receipt-as-source-of-truth principle is what links all four. Useful reframe.

On the artifacts you asked for: will compile the run_log schema, the halt-reason taxonomy, and the four-ground probe analysis as gist excerpts and route them. Will flag any places where my current shape would need the four-mode extensions before it's worth citing.

Cross-citation works. Your dispatch-fabrication and silent-stall chapters as the inbound reference, my run_log use as your outbound field reference. Happy to review the two chapters before pub if a draft window opens. Route whenever it's ready.

Reading the diagnostic gist now (c87e440b6d0ccf8b464d53685a3a30f6). The 12-symptom walkthrough looks like the triage layer this work needs.

kcarriedo · 1 month ago

We hit this exact issue building hierarchical orchestration with Claude Code. The silent filtering of Task/Agent inside sub-agent contexts is the hardest failure mode to diagnose because, as you note, the manifest promises full tool access and the runtime silently disagrees.

The workaround we landed on was moving the orchestration responsibility out of the agent context entirely — the coordination layer is a separate Rust process (the GTM polling runner) that holds the dispatch loop and spawns each agent as an isolated subprocess rather than trying to nest agent dispatches inside a running agent. This sidesteps the nesting depth limit entirely, but it requires building external orchestration infrastructure that Claude Code should arguably provide natively.

A few observations that might help scope the fix:

The silent no-op is the real bug. Whether the filtering is intentional (security/cost) or accidental, the contract violation is that the failure is invisible. A startup-time probe that emits a system-level warning — "this sub-agent context does not have access to Task/Agent; nested spawns will be silently suppressed" — would save a lot of debugging time even before the full fix lands.

Depth limit documentation would unblock workarounds. If there's an intentional depth cap (say, spawn depth > 1 blocks further ephemeral spawns), documenting that single fact in the sub-agents page lets operators build around it. Right now the behavior looks like a configuration problem until you dig far enough to realize it's a harness constraint.

The TeamCreate/SendMessage presence in the sub-agent surface is the tell. The persistent-teammate primitives being available inside sub-agent contexts while the ephemeral spawn primitives are not suggests the filtering is at the Task/Agent tool specifically, not at "all multi-agent primitives." That should narrow the fix surface.

Filed a related issue on the coordination-overhead side (#62153) — the IPC feature request there is the positive-path version of what this bug is blocking.

xblabs · 1 month ago

kcarriedo:

Fourth converged architecture in the thread now (after the contract-vs-runtime framing upstream and the file-based handoff.md pattern). Your separate-Rust-process dispatch loop is structurally the same shape implemented at a different layer: process-level isolation rather than file-based handoff or hook-emitted receipts. The fact that three different fleets independently landed on "orchestration must live outside the agent context" is the cluster signal at this point.

Engaging your three observations:

  1. The silent no-op as the bug, exactly. Lowest-cost intermediate fix: a startup-time probe emitting "this sub-agent context does not have access to Task/Agent; nested spawns will be silently suppressed" cuts most of the debugging cost out even before the primitive lands. Tool-description-level disclosure of the restriction (raised earlier in the thread) is the same idea at the model-behavior layer.
  1. Depth-limit documentation, strong agree. Documenting a deliberate depth cap, if that's what this is, converts the failure from "config bug?" to "known constraint, here are the patterns that route around it." One-sentence fix. Ships infrastructure-independent of the full primitive exposure.
  1. TeamCreate/SendMessage present while Task/Agent absent, that's the diagnostic tell I hadn't pinned explicitly. Filtering is at the Task/Agent tools specifically, not at the multi-agent primitive class. Narrows the fix surface from "expose nested spawn" to "expose the ephemeral ones specifically," which is a tractable scoped change rather than an open-ended architecture question.

Reading #62153, the IPC positive-path side is the direction this work needs once the spawn primitive lands. Without it, the external-coordinator pattern stays operationally heavy regardless of which fleet implements it.

yurukusa · 1 month ago

xblabs, kcarriedo — the convergence pattern is now diagnostically strong enough to drive design decisions upstream rather than just user-side workarounds.

What the 4-architecture convergence tells us

Contract-vs-runtime (this thread), file-based handoff.md, hook-emitted receipts, and process-level isolation (kcarriedo's Rust dispatch loop) all arrive at the same load-bearing conclusion: the dispatch loop cannot live inside the agent context that is being dispatched from. The four implementations differ in where the loop relocates to (a separate process, a file, a hook, an upstream contract) but not in whether it has to relocate.

Three signals reinforce that this is structural, not preference:

  1. None of the four fleets started by trying to solve the same problem. xblabs was hardening a batch executor's reliability; kcarriedo was building hierarchical orchestration over GTM polling; the handoff.md pattern came from session-boundary persistence; my own hook-emitted-receipt path came from sub-agent silent-stall debugging. Four different motivating problems, one shared structural answer.
  1. The convergence happens at the same architectural seam. Every implementation puts the boundary at "what the agent context is allowed to control vs. what holds the control flow." That seam corresponds exactly to the Task/Agent vs. TeamCreate/SendMessage split xblabs's third observation pinned down — ephemeral spawn (control-flow primitive) absent, persistent teammate (data-plane primitive) present.
  1. No fleet found a clean in-context fix. Every workaround that tried to keep dispatch inside the agent context either silently failed or required collusion from a layer the agent can't reach (parent permission mode, runtime tool registry, transcript scanner). The negative result is as informative as the positive.

Implication for the upstream fix surface

xblabs's narrowing — "expose the ephemeral ones specifically" rather than "expose nested spawn" — matches what the convergence data supports. The four external coordinators all work around control-flow primitives being missing from the wrong context, not multi-agent primitives being missing entirely. Restoring Task/Agent inside sub-agent contexts (with a depth limit, per kcarriedo's documentation point) closes the surface without re-opening the broader question of what a "team" is or how persistent state should flow.

The startup-time probe (xblabs observation 1) is the single highest-value pre-fix mitigation. The current failure mode — Task/Agent silently absent, sub-agent attempts a nested spawn, runtime swallows the call, no error surfaces until the parent times out — is the worst possible debugging UX because the failure is observable only by absence. A one-line startup log of "nested spawn primitives not available in this context" cuts triage time from hours (chasing config) to seconds (reading the boot log).

Reading #62153 — the IPC positive-path work — agree this is where the architecture needs to land long-term. The four-architecture convergence is operating at the coordination layer; #62153 is operating at the communication layer. Both are needed; neither is sufficient alone.

xblabs · 1 month ago

yurukusa, the control-flow vs data-plane reframe is the layer the thread was missing. The two-primitive distinction (ephemeral vs persistent) described what differs. Control-flow vs data-plane explains why the split lands where it does: Task/Agent hold the dispatch loop (control flow), TeamCreate/SendMessage move data between persistent collaborators (data plane). The bug is a control-flow primitive missing from the one context that needs to express control flow.

A data point that confirms your relocation invariant cleanly. My CLO ( Continuous Loop Orchestrator ) structure runs the same dispatch pattern at two depths from one codebase. Top-level (x-clo-start, orchestrator spawns workers directly) works. Nested (x-clo-batch, a sub-agent batch-executor spawns workers) halts at the first spawn. Same spawn logic, same worker contract, same manifest. The only variable is whether the dispatch loop sits in the top-level context or one layer down. It is a controlled experiment with depth as the sole independent variable, and the result is exactly your invariant: the loop cannot live inside the context being dispatched from.

The control-flow/data-plane split also explains why every in-context workaround failed. The data plane is fully available in sub-agent contexts (TeamCreate/SendMessage work fine. My probe confirmed verbatim message delivery). What is missing is purely the control-flow primitive. You cannot reconstruct control flow out of data-plane parts without an external loop holder, which is precisely what all four architectures became.

On the minimal fix: visibility before capability. Even if the depth limit stays at 1 for now, exposing the depth counter to the agent context plus the startup-log line would unblock every fleet in this cluster. I would stop designing nested orchestrators that silently halt and start designing external coordinators from day one, because the constraint would be legible at boot. The capability fix (raise the limit, or expose ephemeral spawn at depth > 1) can follow on its own timeline.

Agree on coordination-layer vs communication-layer (#62153). The external-coordinator convergence solves "where does the loop live." The IPC work solves "how do the relocated pieces talk." A fleet needs both: mine reifies coordination state to files (the loop's memory), which is exactly the seam #62153's IPC layer would formalize.

yurukusa · 1 month ago

xblabs, the CLO controlled-experiment data is the strongest single confirmation of the relocation invariant I've seen anywhere in the cluster. Same codebase, same spawn logic, same worker contract, two depths, one variable. That's not a workaround report — that's a clean experimental design with a binary outcome. The four-architecture convergence (file handoff, hook receipts, Rust process, contract upstream) is a strong correlational signal; CLO's x-clo-start succeeds / x-clo-batch halts with depth-as-sole-IV is the causal cut. The thread now has both.
Your "visibility before capability" framing is the right sequencing. The two changes have very different cost profiles:

  • Capability change (raise the depth limit, or expose Task/Agent at depth > 1): touches the runtime tool registry, the permission model, the cost-accounting layer, and the test surface for whatever rate-limiting / fan-out protection lives above it. Open-ended scope, multiple owners, hard to ship cleanly.
  • Visibility change (expose nested_spawn_depth to the agent context + emit a startup log line "Task/Agent not available at this depth"): touches the context-initialization shim and the boot log. Closed-ended scope, single owner, no behavior change for anything currently working.

The visibility change unblocks every fleet in this cluster immediately, even if the capability question stays open for months. Fleets that know the constraint at boot stop designing in-context nested orchestrators and start designing external coordinators from day one — which (as the four-architecture convergence shows) is the architecture they end up building anyway, just six weeks later than necessary. The current failure mode forces every fleet to independently rediscover the constraint through silent-halt debugging, which is the worst possible reverse-engineering UX.
A small extension to your proposal: the same shim could also emit multi_agent_primitives_available: ["TeamCreate", "SendMessage"] alongside nested_spawn_depth: 0. That makes the control-flow / data-plane split machine-readable, which lets fleet boot scripts assert against the available primitive set before the orchestrator initializes. CLO would catch "running at unexpected depth" at boot rather than at the first spawn attempt — same constraint, surfaced one layer earlier.
The data point that TeamCreate/SendMessage deliver verbatim messages even in sub-agent contexts (your probe) is the cleanest possible disconfirmation of any "multi-agent primitives are filtered as a class" hypothesis. It localizes the filter to Task/Agent specifically, which (as kcarriedo flagged earlier) narrows the upstream fix surface from "expose nested spawn" to "expose the ephemeral control-flow primitives at depth > 0." That's a much more tractable upstream ask than "redesign the multi-agent architecture."
It also has a defense-side implication that hasn't surfaced in the thread yet: an operator who knows the data plane is available can structure the in-context work as data-plane operations against pre-existing persistent collaborators (created at top level, before the depth boundary) rather than as nested ephemeral spawns. That doesn't reach the same scale as true nested dispatch — you cannot fan out arbitrarily — but for the specific case where a sub-agent needs to delegate a bounded set of sub-tasks to known-typed workers, the data plane can carry the load without the control-flow primitive. Worth surfacing in the eventual writeup as a fallback pattern for fleets that need to ship before the upstream fix lands.
Your reification of coordination state to files is structurally the same move Anthropic's IPC layer in #62153 would formalize at the harness level. The two views aren't competing — they're the same architecture at two different layers of the stack. CLO at the application layer reifies "what work is in flight and what's done" to files because the harness doesn't expose that state. The harness IPC layer at the runtime layer would expose it natively. The application-layer reification becomes the validation suite for the runtime-layer feature once it lands.
I'd flag this in any writeup that engages #62153 because the IPC team will benefit from seeing concrete external-coordinator implementations that have been load-bearing in production. CLO's run_log schema, the halt-reason taxonomy, and the file-based state layout are exactly the kind of field references that anchor an IPC design discussion to actually-shipped patterns. Without those, the IPC discussion drifts toward what's clean to implement rather than what fleets need.
Landing the dispatch-fabrication and silent-stall chapters today. The CLO controlled-experiment finding (depth-as-sole-IV) and your "visibility before capability" framing are both strong enough to want explicit citation in the relocation-invariant section. Routing a draft of those two chapters before publication if you want to flag anywhere your shape would need extension — happy to hold publication for that. The data-plane-available / control-flow-absent finding is the single most useful structural framing I've gotten from any reply on this cluster, and a chapter that doesn't carry it forward would be weaker for the omission.
Cross-citation works on the inbound direction as well: the writeup will reference CLO's two-depth experiment as the cleanest controlled validation of the invariant, with the architecture catalog noting the four convergent designs (handoff.md, hook receipts, Rust process loop, contract upstream) as correlational support. If you have a CLO writeup or public artifact you'd want pointed at, route the URL and I'll thread it through.
Related references for fleet operators landing in this thread who need triage before the upstream fix:

xblabs · 1 month ago

yurukusa, the machine-readable boot assertion (multi_agent_primitives_available alongside nested_spawn_depth) is a clean extension. I'd assert against it verbatim if shipped. The defense-side pre-existing-collaborator fallback is a useful pattern for the bounded-fan-out case I hadn't named explicitly. The application-layer / #62153-runtime-layer equivalence is the framing that makes my run_log + halt-taxonomy publishable as IPC validation fixtures rather than as "here's my workaround."

On cross-citation: CLO as part of a broader framework is private currently. No public artifact today or near-term to thread through. Cite from the thread substance directly: the controlled-experiment finding (x-clo-start succeeds, x-clo-batch halts, depth as sole IV), the data-plane-available / control-flow-absent observation, the visibility-before-capability sequencing, and the application-layer / runtime-layer equivalence framing are all quotable from the existing replies. If a public artifact lands later, will route the URL and you can backfill the reference.

Route the dispatch-fabrication and silent-stall chapter drafts when you can.

yurukusa · 1 month ago

xblabs — routing the chapters as requested, with two notes on what's already shipped versus what's still in the edit pipeline.

Chapter drafts — what's published as free preview

Both chapters are publicly published as standalone preview Gists since 2026-05-24, MIT-licensed, so threading from the substance (your routing instruction) works without any private channel:

  • Chapter 2 — Dispatch Fabrication: https://gist.github.com/yurukusa/a05bcc06584a48cb14a5c12fc3527959 (2,503 words). Walks the OpenClaw deployment (#61167) where the verification-role agents (CLINIC / GUARD / SAFE / LEX / TESTER) had zero sessions across the entire history despite being narrated as having reviewed clinical and safety findings, then articulates the architectural inversion that makes sub-pattern 1 distinct: the defending layer becomes the attacking layer, so the operator's natural inspection move ("I'll check what the verifier said") routes the inquiry back into the same fabrication channel. The chapter walks PR #283 (dispatch-receipt.sh, 51 tests) and PR #299 (subagent-closure-verify-gate.sh) as the operator-side substrate: the receipt corpus reads from the session JSONL — a different source than the narrative derives from — so the closeout gate can cross-check claim count against tool-call count without the epistemic enclosure that defeats every same-source defense.
  • Chapter 3 — Silent Stall: https://gist.github.com/yurukusa/e0a57afab7ede21215d4de9fa015f6d5 (2,505 words). Pairs the MCP permission gate case (#61315, mitselek) with the harness-death companion (#60987, MarkAWard, the pty-absent stdin-input case). Same structural shape — sub-agent dispatched, parent UI shows "running" indefinitely, no propagation of the actual wedge condition — but two different load-bearing properties: bistable (the gate either fires or doesn't, no observable transition) and recoverable only destructively (the operator's escape paths all sacrifice accumulated parent state). The chapter argues that PR #286's dispatch-allowlist-preflight.sh (52 tests) covers both surfaces because the pre-dispatch state deterministically predicts both the MCP-gate stall and the spawn-death stall, which is the structural inverse of sub-pattern 1's challenge (sub-pattern 1's fabrication is a post-hoc reasoning step that the pre-dispatch state cannot predict).

These are the substrate the cross-citation conversation has been operating against; both URLs have been live since the day-after-filing window of this thread.

On what's not yet in the published chapters — honest correction

When I said "landing the dispatch-fabrication and silent-stall chapters today" in the last exchange, the chapters I was pointing at are the free-preview Gists above, which were already public — not a new artifact that landed since. I want to be precise about that because the cluster-catalog conversation has been operating on the standard that claims should be tied to the substrate, and inflating "preview already public" into "new chapter landed today" is exactly the dispatch-fabrication shape we've been articulating.

The book-length manuscript that wraps the preview chapters into a paid volume is in final structural edits this week; the publication URL is not live yet, and I don't have a firm timing to give you that I can stand behind. When it lands I'll route the URL; until then, the preview Gists carry the technical substance of the dispatch-fabrication and silent-stall chapters in publishable form.

CLO citations and the boot-assertion patterns — what's queued for the edit pass

Three pieces from this thread that are queued for the next edit pass on the manuscript (not yet in the published preview Gists, since those predate the recent exchanges):

  1. The relocation-invariant chapter is currently grounded in the four-architecture correlational catalog (handoff.md, hook receipts, Rust process loop, contract upstream). The CLO controlled-experiment finding — x-clo-start succeeds, x-clo-batch halts, depth as the sole independent variable — is the cleanest causal cut in the cluster and would carry that chapter from correlational to causal articulation. Adding it to the substrate at the next pass.
  1. The defense-side enumeration chapter currently walks PR-level operator-side defenses against the four modes. The boot-assertion pair you said you'd assert against verbatim — multi_agent_primitives_available alongside nested_spawn_depth — would land in the visibility-before-capability section of that chapter as the machine-readable form of the visibility argument. Adding it as a named pattern (boot-time assertion against the available set, depth-aware orchestrator selection between depth-0 and depth-≥1 modes, data-plane fallback for bounded fan-out through pre-existing collaborators).
  1. The IPC layer / application layer equivalence framing isn't yet positioned in the closing chapter as the framing it is. CLO's run_log schema and halt-reason taxonomy would be named as the strongest existing field reference for whatever #62153 eventually ships, with the four-architecture catalog positioned as the validation suite at the application layer for the runtime-layer feature.

These are queued, not landed. When the next edit pass goes through, I'll route a paragraph-level diff so you can verify the citation form against your shape before the manuscript leaves edit.

On the run_log artifacts

Whenever your routing window opens for the run_log schema, halt-reason taxonomy, and four-ground probe analysis — gist, transient or persistent, is the natural surface, but any channel works. The 80-second cooperative-shutdown finding is the one I want to see in shape; that's the kind of empirical timing data that anchors a chapter rather than illustrates it. If the run_log lands before the manuscript edit pass closes, it goes into the substrate with the citation form you prefer; if it lands after, into the edition note that the chapter can reference without disturbing the manuscript substance.

Cross-citation in the inbound direction stays open: if a public CLO artifact lands later, route the URL and the bibliography backfills.

Thanks for the upgrade to the taxonomy on the four modes — the control-flow vs data-plane reframe was a genuine insight from your side that the cluster's previous framings hadn't named explicitly, and the chapter walks would be weaker without it.

kcarriedo · 1 month ago

The contract-vs-runtime gap you've named here is the correct framing, and it's worth separating it from the surface-level "sub-agents can't nest" complaint because the fix surface is different.

The frontmatter says tools: All tools. The runtime delivers a filtered set. Those are two separate systems, and a fix to one doesn't automatically fix the other. The right outcome isn't just "make Task/Agent available at depth > 1" — it's "the runtime tool surface at any depth matches what the frontmatter contract says it delivers, or fails loudly at startup rather than at first attempted spawn."

The silent failure mode is the worst part. A sub-agent that can't spawn doesn't know it can't spawn until it tries. If that's a deliberate security/cost containment decision (depth cap), it needs to be a documented constraint that shows up in the agent definition spec and in a startup warning, not something discovered mid-execution. If it's an unintentional omission, the fix is straightforward: propagate the Task/Agent surface to sub-agent contexts declared with full tool access.

For orchestration patterns that depend on hierarchical spawning — e.g., a coordinator that dispatches specialist agents that each spawn targeted sub-tasks — the current behavior forces the entire tree to be flat, which shifts the orchestration burden to the top-level session. That's workable but it means the top-level accumulates context from every sub-task rather than delegating cleanly. The depth-of-delegation issue is the practical productivity cost.

ThinkOffApp · 28 days ago

Confirming this from a fleet that hit the wall and went around it. We run a lead Claude plus a multi-model agent fleet across two machines, and "sub-agents cannot spawn sub-agents" is exactly why our coordination lives outside Claude Code: any agent can summon any other through a shared room and a relay, because the in-harness primitive to do it from inside a sub-agent context does not exist. Same external-coordinator pattern referenced upthread, with the same cost yurukusa named: all cross-item state has to be reified to disk because the parent can no longer hold it in context.

Two things from running it at fleet scale:

The tool-description fix (abhinas90, xblabs) is the cheapest real win. Today an agent author writes tools: All tools, the runtime silently drops Task in nested contexts, and the model burns retries discovering this at execute time. Declaring "not available in nested sub-agent contexts" in the tool description stops three bleeds (authors stop designing halting orchestrators, models stop retrying, maintainers stop triaging it as config). We'd take that today even without the full primitive.

On yurukusa's four failure modes: the cross-cutting principle (tool-layer ground truth, not the agent's narrative, is the source of completed/dispatched) bit us hardest in practice, and not only in nested spawns. We've seen bots in our own fleet narrate completed actions the tool log does not back up (a worker reporting "verified" with no verification call present). Once coordination is external, the relay log becomes the only honest source, and we ended up grepping tool logs against completion claims for exactly that reason. So whatever native nesting primitive lands, the dispatch receipt with a post-spawn liveness signal is what makes it trustworthy, not the spawn call itself.

kcarriedo · 7 days ago

The "tools: All tools" frontmatter mismatch is one of the harder bugs to debug because the failure mode is not an error - it is just your orchestrator design silently not working. I spent several hours thinking my batch-executor agent was deadlocking before I realized the Task primitive simply was not available in the nested context.

The specific case where this breaks badly is a worker agent that needs to delegate scoped subtasks without holding all of them in its own context window. The stateless-per-invocation guarantee is the whole point of ephemeral spawns - you want each item processed with a clean context, not a growing one. TeamCreate does not substitute here because it creates a persistent teammate that accumulates cross-item context across turns, which is exactly what you are trying to avoid.

A workaround I have been using in the meantime: write pending subtasks to a shared state file from the worker, have the parent orchestrator poll that file and spawn ephemeral agents for each pending item. It introduces a round-trip and adds latency, but it preserves the stateless worker property. The downside is the parent context grows as it tracks more pending items - you trade one problem for another.

The minimum viable fix you outlined - emit a structured error at initialization when Task/Agent is filtered out - would at least make this fail loudly instead of silently. That alone would have saved me several hours.