Expand description
P5-3 (COMPOSABLE-HARNESS-DESIGN.md §2 module 9 subagents: “D1 spawn
tool; D3 sub-agents/named-defs/background+resume/teams; D5 subagent
transcripts”; §2.1 D-1 “subagents → core.session(lineage), core.tools;
background-mode → permissions.approvals”; §2.2 C6): the data shapes and
pure-function resource-bound checks the spawn/join/background machinery
in crate::agent::Agent builds on. Kept separate from agent.rs so the
depth/concurrency-cap arithmetic and the lineage record shape are
unit-testable without a full Agent/mock-Provider harness — the same
“pure config → set, testable without the loop” precedent P3’s
crate::modules module documents for itself.
Activation. Everything here is inert until Agent actually consults
it, which only happens when Config::subagents_enabled is true
(capabilities.subagents.enabled, default false) — so importing this
module changes nothing for an agent that never turns the module on.
Structs§
- Concurrency
Guard - A held slot against a
try_acquireconcurrency gauge. Decrements the gauge on drop (including an early return, a panic-unwind, or the normal end of a background task’s future) so a completed spawn always releases its slot — no separate “remember to release” call site to forget. - Named
Agent Definition - A named subagent type (
[capabilities.subagents.agents.<name>], §3.1) — the CC “subagent definition” shape: its own system prompt, an optionally NARROWED tool set (a spawned child’s tool surface is always the intersection of the parent’s already-enabled tools and this list — seeAgent::run_spawn_subagent’s doc comment for why it can only narrow, never widen), and an optional model override. - Parent
Queue Approval Handler - §2.2 C6
background_prompts = "parent"’scrate::permissions::PermissionsApprovalHandler: pushes everyAsk-tier request onto the parent’s queue (crate::agent::Agent::pending_child_approvals) and returnscrate::permissions::ApprovalOutcome::Denyimmediately — NEVER blocks, since a detached background child has no way to wait for an answer that can’t arrive synchronously (the hard C6 requirement this whole policy exists to satisfy). “Surfaced to the parent” means exactly that: recorded for the parent to inspect/audit, not a live prompt the parent’s later answer retroactively changes. - Queued
Approval - A queued approval request from a
background_prompts = "parent"child, surfaced viaAgent::pending_child_approvals(§2.2 C6 “parent-surfaced queue”). This struct is ALWAYS a record for the parent to inspect/audit (never a pending decision the parent’s answer changes retroactively) — but what actually answers the underlying call depends on whichPermissionsApprovalHandlerAgent::run_spawn_subagentinstalled for the child: - Subagent
Lineage - Typed, lossless NATIVE-WRITE lineage record for a spawned child (§1.13;
§5.2 P5 row 3: “native write side — store already parses CC sidechains +
CX lineage on import”). Field names deliberately mirror the keys the
IMPORT-side loaders already populate on
crate::session::SessionMeta::parent_tool_use_id/crate::session::SessionMeta::lineage(seeSelf::to_lineage_map), so a natively-spawned session and an imported CC/CX one land in the same shape rather than two parallel formats a translator would need to know about separately.
Enums§
- Background
Prompts Policy capabilities.subagents.background_prompts(§2.2 C6’s schema value, §3.1): how a BACKGROUND child’s tool-approvalAskdecisions are resolved, since a detached background task cannot block on an interactive prompt it has no way to answer.
Functions§
- check_
depth - Fail-closed depth check (resource bound, build-brief “a parent spawning
children spawning children… must not fork-bomb”):
ErrNAMES the exceeded cap rather than silently clamping the depth or panicking.current_depthis the SPAWNING agent’s own depth (0 for a top-level agent); the new child would be spawned atcurrent_depth + 1. - try_
acquire - Fail-closed concurrency check-and-acquire (resource bound: “a max
concurrent subagents… cap, fail-closed”). Atomic compare-exchange loop
(not a check-then-increment race, which would let two racing spawns both
pass a check against the same stale count) —
Nonewhenmax_concurrentsubagents are already in flight anywhere in this spawn tree (the gauge is a singleArcshared root-to-leaf, percrate::agent::Agent’s doc comment on its own concurrency-gauge field),Some(guard)otherwise, with the slot already counted.