qwen-code/docs
tanzhenxin 8de93b876b
feat(core): allow sub-agents to spawn nested sub-agents up to a configurable depth (#6189)
* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth

Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.

See knowledge/qwen-code/design/nested-subagents.md.

* fix(core): address review findings on nested sub-agent spawning

- Deny the agent tool to workflow-spawned subagents: depth gating would
  otherwise re-admit it, letting a workflow leaf spawn outside the
  orchestrator's concurrency cap, agent accounting, and token budget.
- Reject non-finite maxSubagentDepth values (JSON 1e309 parses to
  Infinity and would unbound the recursion cap; NaN would silently block
  all nesting) and cap the knob at 100 to catch typos.
- Add a --max-subagent-depth CLI flag mirroring sibling budget flags,
  with loud validation for flag typos, and document the setting.
- Log guard rejections (depth, fork containment) and silent
  fork-to-subagent downgrades through the agent debug logger.
- Refresh stale comments (depthOverride resume pinning, depth-gated
  AgentTool exclusion) and drop references to a design doc that lives
  outside the repository.
- Fill review-noted test gaps: nesting predicate primitives, fork-context
  prepareTools, persisted-depth restoration on background resume,
  nested AgentInteractive depth pinning, nested fork fallback, and the
  blocked-spawn returnDisplay shape.

* fix(cli): add maxSubagentDepth to the CliArgs test literal

The exhaustive CliArgs mock in gemini.test.tsx missed the new field,
failing CI's clean tsc build (the local incremental build skipped the
test file).

* fix(core): add a teammate backstop to the agent spawn guards

execute() backstopped depth and fork containment but not the teammate
exclusion, so its guards covered less than prepareTools() gates. A
teammate spawn call that slipped past schema-hiding would have nested.
Block it symmetrically with the fork guard, log the rejection, and pin
the behavior in a test.

* fix(core): normalize persisted maxSubagentDepth on resume

The resume path trusted the raw sidecar value, bypassing the Config
clamp — a tampered or malformed sidecar (1e309 parses to Infinity;
JSON.stringify turns Infinity into null) would remove the nesting cap
for resumed agents. Extract the clamp into a shared
normalizeMaxSubagentDepth used by both the Config constructor and the
flag-restore path, and refresh the stale settings schema description
(clamp range, non-finite fallback, workflow-agent wording).

* test(core): pin null-to-default normalization of resumed maxSubagentDepth

JSON.stringify(Infinity) === 'null', so a sidecar can legitimately carry
null; widen the persisted flag type to admit it and parameterize the
resume test over both the clamp (5000 -> 100) and the null fallback
(null -> 5).

* fix(core): harden nesting depth edges from final review pass

- Normalize persisted meta.depth on resume: the sidecar is untrusted
  JSON, and a tampered negative depth (or -1e309 → -Infinity) would pin
  the resumed frame below zero and pass canSpawnNestedAgent for every
  cap. Invalid values fail closed to the depth ceiling — the agent
  keeps running but cannot spawn.
- Register monitor notification routing for in-process interactive
  agents: framing runLoop() made their monitors agent-owned, and owned
  dispatch has no session fallback, so notifications were silently
  dropped. InProcessBackend now routes them into the agent's message
  queue and tears the routing down on release.
- Downgrade background spawn requests from nested launchers to awaited
  foreground runs: a nested launcher cannot honor the background
  completion contract (send_message/task_stop excluded, notifications
  session-scoped), which orphaned the child's results.
- Extract spawnBlockReason() as the single spawn-exclusion policy
  shared by prepareTools() and execute(), replacing two hand-kept
  copies of the depth/teammate/fork rules.
- Share DEFAULT_MAX_SUBAGENT_DEPTH / MAX_SUBAGENT_DEPTH_LIMIT across
  the core normalizer, the CLI flag validator, and the settings schema.
- Log dropped teammate names from nested spawns; revert the impossible
  |null persisted-flag widening to an honest tampered-sidecar framing;
  document the constructor-time depth capture invariant.

* test(cli): add DEFAULT_MAX_SUBAGENT_DEPTH to core package mocks

settingsSchema.ts now imports the shared constant, so CLI tests that
mock @qwen-code/qwen-code-core with an explicit export list need the
new export.

* feat(cli): display nested sub-agents as a tree in the TUI (#6191)

* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth

Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.

See knowledge/qwen-code/design/nested-subagents.md.

* feat(cli): display nested sub-agents as a tree in the TUI

Render nested agents depth-first with indent + dim '↳' in the live agent
panel and background tasks view; promote orphaned children to root with a
'· from <parent>' annotation. Detail view gains a level badge, Parent
breadcrumb, and Sub-agents section. The [blocking] tag and two-step cancel
confirm now apply only to provably user-blocking foreground chains. Parent
completion summaries carry a '· N sub-agents' tail (guard-rejected spawns
now record as failed tool calls so the count stays honest). Also fixes the
live-panel Enter-for-detail order mismatch by sharing one display order
between the panel render and the composer keyboard mapping.

* fix(core): address round-1 review on nested sub-agent spawning

- Derive launch metadata (hooks, spans, task rows, meta sidecar) from the
  resolved subagent config instead of the raw requested type, so a fork
  request that falls back to the awaitable path no longer reports "fork".
- Pin the blocked-spawn failure contract in tests: error is set and
  returnDisplay.status is 'failed' for both the depth and fork guards; also
  document the failure-path routing at buildSpawnBlockedResult.
- Drop source-comment references to private knowledge/ design docs that do
  not exist in this repository.

* test: address round-2 review on sub-agent counting and fork fallback

- Exercise the legacy 'task' alias in the scrollback sub-agent count so
  the migration-aware name set is covered, not just the canonical name.
- Pin the nested-fork downgrade: a sub-agent requesting a fork falls back
  to the awaitable general-purpose subagent even in interactive mode.
- Drop a duplicated 'nesting depth guard' describe block left behind by
  the automated base-branch merge (kept the copy with the failure-shape
  assertions).

* fix(core): keep actionable guidance in blocked-spawn error messages

The scheduler's failure path sends only error.message to the model and
the scrollback, discarding llmContent. With the terse terminateReason as
the message, a blocked spawn lost its "do the task yourself instead"
instruction, inviting retry loops. Carry the full guidance text in
error.message and keep terminateReason for the display card.

* test(cli): pin the tree indent clamp at depth beyond TREE_INDENT_MAX_LEVELS

Maintainer mutation-testing on the PR found that removing the clamp in
treeRowPrefix survived the suite. Assert a depth-4 row indents 3 levels
(12 spaces), plus the base marker/indent behavior.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-03 03:18:57 +00:00
..
design feat(daemon,sdk): resumable /acp session stream (Last-Event-ID) + opt-in SDK transports export (#5852) 2026-06-30 02:07:48 +00:00
developers feat(cli): Harden daemon-managed channel worker (#6098) 2026-07-01 13:26:38 +00:00
e2e-tests feat(worktree): Phase D — startup --worktree flag + symlinkDirectories + PR refs (#4381) 2026-05-27 17:04:51 +08:00
plans feat(core)!: redesign auto-compaction thresholds with three-tier ladder (#4345) 2026-05-25 21:11:08 +08:00
superpowers feat(channels): add identity and task lifecycle metadata (#6105) 2026-07-02 09:04:07 +00:00
users feat(core): allow sub-agents to spawn nested sub-agents up to a configurable depth (#6189) 2026-07-03 03:18:57 +00:00
verification/abort-controller-refactor fix(core): stop AbortSignal listener leak in long sessions (MaxListenersExceededWarning) (#4366) 2026-05-26 14:21:49 +08:00
_meta.ts feat: refactor docs 2025-12-05 10:51:57 +08:00
declarative-agents-port.md feat(core): port declarative-agent mcpServers + hooks (CC 2.1.168 parity follow-up) (#4996) 2026-06-12 14:15:51 +08:00
index.md fix: lint issues 2025-12-19 15:52:11 +08:00
yaml-parser-replacement.md feat(core): port declarative-agent mcpServers + hooks (CC 2.1.168 parity follow-up) (#4996) 2026-06-12 14:15:51 +08:00