- the subagent projection's empty state becomes a serializable null sentinel (undefined fields vanish in JSON push frames, leaving stale identities in clients); consumers treat null and a missing key alike, and cached sentinels defer to the authoritative refold - the tool catalog generator mounts the projection registry for list_agents; the shared unavailable-error copy goes catalog-neutral - the design note's verification section records the new subagent-diagnostic snapshot honestly, the 2026-07-22 note's test inventory is rewritten to the current mechanism, and the session-store error code is documented
33 KiB
Subagent
English | 中文
The subagent seam — an agent delegating work to a child agent. Like bash it is one optional capability, not part of the agent-loop spine, so its vocabulary lives here rather than in core.md. But it differs from every other seam on one axis: multiple provider implementations coexist in one context, registered by name (ctx.subagents), where bash allows only one executor. The registry shape mirrors the LLM adapter registry, not the single-service bash executor.
Interface: dsh-subagent (ctx.subagents + the vocabulary below). Implementations are sibling packages (dsh-subagent-spawn, -fork, -acp, -codex, -claude-code, -dsh-sdk); the model-facing consumers are dsh-tool-subagent (per-provider delegation), dsh-tool-subagent-control (the optional global send_message and list_agents controls), and dsh-tool-subagent-report (the optional child-scoped report return channel). The same ctx.subagents service owns continuable-child orchestration through an internal activation manager and read-only direct-child discovery straight from the session store and optional session persistence. Product-provider rationale lives in the Codex and Claude Code Agent Note; common-seam rationale lives in the subagent Agent Note, the continuable subagents Agent Note, the report-tool Agent Note, the durable catalog Agent Note, the list-identity-projection Agent Note, and the merged-service Agent Note.
Sources: packages/subagent/subagent/src/types.ts, packages/subagent/subagent/src/index.ts, and packages/subagent/subagent/src/continuation.ts
Two kinds of capability, discovered two ways
A provider advertises its start-time features on a static descriptor the service checks BEFORE a one-shot run exists; a request that needs one the provider lacks is rejected loud (SubagentError('UNSUPPORTED_CAPABILITY')), never accepted-then-ignored. Those flags describe only the one-shot start() path, where the provider composes the child. Continuable children are composed by the continuation manager itself, so they are gated by one optional method whose presence IS the capability, with TS narrowing as the discovery mechanism: SubagentProvider.prepareContinuable.
/**
* Which START-TIME features a provider supports. Checked by the service before delegating to
* {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
* degradation" rule). These flags describe the ONE-SHOT
* {@link SubagentProvider.start} path, where the provider composes the child;
* continuable children are composed by the continuation manager itself and are
* gated by {@link SubagentProvider.prepareContinuable} instead. Each flag
* corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit`
* to `maxDepth`; the other names match.
*/
interface SubagentCapabilities {
readonly outputSchema: boolean
readonly depthLimit: boolean
readonly toolFilter: boolean
readonly persona: boolean
}
The one-shot start request
The tool layer builds this request from the model input and its own config; the service validates it against the named provider before start. Required parent supplies the session cwd, lineage, and delegation depth. Optional output schema, depth, tool filter, and persona require matching capability flags. Unsupported schemas fail at start; in-process backends scope filters and personas to child creation and implement the supported object-rooted schema with a forced capture tool.
/**
* What a caller asks for when starting a ONE-SHOT subagent. The tool layer
* builds this from the model's `{ description, prompt }` plus its own config;
* the service validates {@link SubagentCapabilities} against the named provider
* and resolves the durable descriptor before dispatching to
* {@link SubagentProvider.start}.
*/
interface SubagentStartRequest {
/** Optional short display label persisted with a session-backed child. */
readonly label?: string
/** Content delivered as the child's user message. */
readonly prompt: ContentBlock[]
/**
* The spawning agent. In-process providers derive workspace, lineage, and
* delegation depth from its durable session state. ACP reads only its cwd,
* and only when no deployment `cwd` override is configured.
*/
readonly parent: Agent
/**
* Cancellation signal from the spawning context (the tool's `exec.signal`).
* This is the canonical cancellation channel both before and after startup:
* a provider rejects `start()` after cleaning partial resources when it
* fires before the run is published, and cancels the published run's
* remaining turn work when it fires afterward.
*/
readonly signal: AbortSignal
readonly agentOptions?: AgentOptions
/**
* Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
* unsupported schemas or providers without the capability. Data must be plain host-realm JSON;
* a successful child returns the matching value as {@link SubagentResult.structured}.
*/
readonly outputSchema?: ObjectJsonSchema
/**
* Optional absolute delegation-depth cap for the child being started: its
* computed depth must be less than or equal to this non-negative safe
* integer. Requires {@link SubagentCapabilities.depthLimit}; rejected at
* start otherwise.
*/
readonly maxDepth?: number
/**
* Optional child tool scoping. Requires {@link SubagentCapabilities.toolFilter};
* rejected at start otherwise. In-process backends apply it as a scoped
* `tools.restrict()` in the child's creation window: the named tools vanish
* from the child's prompt AND refuse to execute (one visibility), with loud
* unknown-name validation.
*/
readonly toolFilter?: ToolRestriction
/**
* Optional per-child persona. Requires {@link SubagentCapabilities.persona};
* rejected at start otherwise. In-process backends register it as a scoped
* `deployment:persona` section on the child, SHADOWING the deployment's
* persona for this child alone — same template semantics as the deployment
* persona (strict `{{…}}` interpolation against the registered variables).
*/
readonly persona?: string
}
signal is the single cancellation channel before and after readiness. The subagent composition-controls Agent Note owns the persona, live global-tool filter, absolute-depth, and visibility-not-authority rationale.
The caller-facing request does not carry catalog format details or continuation state. SubagentService.start() resolves the detached one-shot descriptor after capability checks, then passes this provider-facing request to the selected transport; a continuable child never reaches SubagentProvider.start():
/**
* Provider-facing one-shot request after {@link SubagentService.start} resolves
* the durable child descriptor.
*/
interface ResolvedSubagentStartRequest extends SubagentStartRequest {
/** Detached descriptor a session-backed provider persists in the child log. */
readonly descriptor: SubagentDescriptorData
}
Continuable children and activations
A continuable background subagent is one durable child Session with at most one process-local Activation — a residency epoch for a reconstructed child Agent. An Activation is not a request, result, cancellation, or Task boundary: it may execute many FIFO turns and stays resident while descendants it created are still running. The continuation manager owns activation admission, direct-parent authorization, the live ownership graph, cold resume, and child-first disposal; the Agent loop owns all turn ordering and execution. No continuable path creates a Task or an intermediate result-bearing wrapper.
persisted Session
-> optional live Activation
-> one retained AgentHandle
-> Agent inbox as the only turn FIFO
-> zero or more owned child Activations
SubagentService.startContinuable() reserves the stable child id, snapshots the versioned subagent/descriptor payload, asks the named provider for its detached ContinuableCreateSpec, creates the child Agent through a private activation-owner scope, establishes any continuable-parent ownership, and submits the initial prompt. It resolves with { childId, messageId } when inbox acceptance yields the message id — without waiting for the turn to start or for the message to enter the Session log. Every failure before that acceptance rejects with neither id, disposing any created handle and rolling back the Activation and parent ownership.
SubagentService.followup() is the sole continuation-message operation, and routing depends only on Activation residency:
| Activation state | followup |
|---|---|
running |
enqueue in the same Activation |
waiting |
wake the same Activation |
| no Activation | cold-resume a new Activation |
running means the Agent has an active admission or turn, or waking inbox work; waiting means it is quiescent but still owns at least one child Activation that has not completed disposal; settled means quiescent with every owned child disposed, at which point the manager disposes the AgentHandle and removes the Activation. The manager derives these internal conditions from Agent quiescence and the owned-child set rather than maintaining a second execution state machine.
The Agent inbox is the only queue. Every continuation message becomes one Agent.followup() FIFO turn, so accepted messages have one observable order and a follow-up cannot redirect a turn already underway. Successful delivery returns the accepted MessageId; the existing agent/inbox/enqueue, agent/inbox/dequeue, and agent/inbox/discard events remain the message-lifecycle observations, and the continuation layer defines no subagent-specific delivery route.
Follow-up authority comes from an exact live Agent tool context. The authenticated Agent must be the durable child's direct parent recorded in SessionHeader.parentSession. MessageSource and senderSessionId are durable provenance after admission and grant no authority; the optional model-facing tool uses CoordinatorMessageSource.
For both operations the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child, and the seam exposes no public subagent cancellation or steering operation.
Every Activation owns its AgentHandle and an ownedChildren: Set<SessionId>; because one Session has at most one live Activation, the child Session id identifies the live child without another runtime-incarnation reference. Starting a child or submitting parent-originated work registers the child in a continuation-managed parent's set before the child can run, and that parent cannot settle while the set is non-empty. A top-level or other non-continuation Agent has no Activation and stays outside the waiting graph. Child release happens only after the child Agent is quiescent, every child of that child is disposed, the best-effort final session flush settles, and the child's AgentHandle completes disposal.
Final settlement awaits ctx.sessions.flush(session) but ignores its participation boolean because an arbitrary listener cannot prove that a persistence backend stored the state. Rejection is logged without failing the Activation, and the manager still disposes the handle and releases ownership; the persisted child state may then be missing or stale on a later resume. Manager unload invokes an internal manager-wide drain that closes admission and disposes every live forest; drainContinuableDescendants(parents) closes admission only below exact live host-owned Agents and disposes their continuable descendants while unrelated forests remain live. Both await already-admitted materializations in their scope, propagate cancellation top-down, release handles child-first, and await every selected branch despite individual failures. Durable child Sessions survive that process-local teardown.
/** Attribution for a model coordinator's follow-up to one of its children. */
interface CoordinatorMessageSource {
readonly kind: 'coordinator'
/** A message another agent addressed to this one (`relay` context form). */
readonly form: 'relay'
/** Session id of the agent whose tool call produced the follow-up. */
readonly senderSessionId: SessionId
}
/** Options for following up with one continuable child. */
interface SubagentFollowupOptions {
/** Durable attribution retained on the delivered message; it grants no authority. */
readonly source: MessageSource
/** Caller cancellation, owning the operation only until inbox acceptance. */
readonly signal: AbortSignal
}
/** Identities returned once a continuable child accepted its initial prompt. */
interface ContinuableStart {
/** The durable child session id, stable across activations. */
readonly childId: SessionId
/** The accepted initial prompt's inbox message id. */
readonly messageId: MessageId
}
An optional continuable-child setup contribution can install scope-local capabilities after base child composition and before Activation publication. The registry is ordered and transactional: a failed or revoked setup rolls back the unpublished Activation, child-scope disposal releases every installation, new registrations affect the next Activation, and registration removal revokes every resident installation immediately.
SubagentService.reportFrom() uses that extension seam without adding a second queue or a result-bearing child wrapper. The exact live child Agent authorizes the call; callers cannot name a recipient. The manager derives the only recipient from the child's durable parentSession, requires that parent Agent to be live, frames the selected content as one subagent-report user message, and returns the message's stable MessageId. Quiet delivery uses Agent.inject() and creates no inbox occurrence or parent turn; waking delivery uses Agent.followup() and creates one ordinary later parent turn. Neither mode concludes the child's turn, and no final answer reports implicitly.
/** Durable attribution for a continuable child's explicit parent report. */
interface SubagentReportMessageSource {
readonly kind: 'subagent-report'
/** A message another agent addressed to this one (`relay` context form). */
readonly form: 'relay'
/** Session id of the reporting child. */
readonly senderSessionId: SessionId
}
/** Deployment scheduling policy for accepted child reports. */
type SubagentReportDelivery = 'quiet' | 'wakeup'
/** Options for one continuable child's report to its direct parent. */
interface SubagentReportOptions {
/** Already-resolved parent scheduling policy. */
readonly delivery: SubagentReportDelivery
/** Caller cancellation, owning authorization and admission until acceptance. */
readonly signal: AbortSignal
}
The provider participates only in preparing the initial creation spec, where spawn and fork differ. Its returned spec carries only detached provider-specific creation inputs — today the optional parent-history seed — and no Agent, AgentHandle, prompt delivery, result, disposal, or resume operation. Cold resume does not dispatch through a provider at all: the manager folds the generic descriptor, calls ctx.agents.resume() through the same activation-owner scope, and submits the waiting turn.
/**
* What the continuation manager asks a provider for while materializing one
* continuable child's FIRST activation. The manager has already reserved the
* durable child identity and owns every later operation, so this request
* carries only what distinguishes a fresh child from one seeded with parent
* history.
*/
interface ContinuableCreateRequest {
/** The reserved durable child session id, for provider diagnostics. */
readonly sessionId: SessionId
/** The delegating parent agent whose history a seeding provider reads. */
readonly parent: Agent
/**
* Caller cancellation, which owns preparation only until the manager accepts
* the initial prompt into the child's inbox.
*/
readonly signal: AbortSignal
}
/**
* A provider's detached contribution to one continuable child's creation. This
* is DATA, never a capability: it carries no Agent, `AgentHandle`, prompt
* delivery, result, disposal, or resume operation, because the continuation
* manager owns the child's whole lifecycle after preparation.
*/
interface ContinuableCreateSpec {
/**
* Completed-turn prefix of the parent's log to seed the child session with,
* or absent for a fresh child. Same durable contract as
* `CreateAgentOptions.seed`: contiguous from seq 0, lossless JSON, balanced.
*/
readonly seed?: readonly SessionEvent[]
}
The descriptor (SubagentDescriptorData in descriptor.ts) is a mode-discriminated durable identity for every session-backed subagent. Both modes carry the provider name. A one-shot descriptor optionally carries a caller-owned display label; a continuable descriptor requires the delegation description as its durable creation label and additionally snapshots resolved child agentOptions.provider/model and optional persona/toolFilter for cold resume. It never snapshots the merge-extensible AgentOptions object, so an unrelated extension value cannot break continuation and a later composition input is a deliberate version change. It omits subagentDepth (cold resume trusts the persisted header's delegationDepth as the monotone floor) and outputSchema (one run or Activation's result contract, not durable identity).
A local one-shot provider appends the descriptor inside the child's initial turn before its first request. The continuation manager appends the descriptor after any provider-supplied lineage and before the initial prompt is admitted; header.seedLength remains the fork-lineage boundary: resume-time descriptor authority reads the child's own suffix, while the list-serving identity projection folds subagent/descriptor last-wins so the child's own descriptor overrides a fork-seeded ancestor's. The event is log-only: no surfaceOp, never in model history, and retained across compaction by the append-only log. Malformed current-version descriptors are corrupt; unsupported versions cannot be classified by this runtime.
Durable enumeration: listChildren() and SubagentListEntry
SubagentService.listChildren(parentSessionId) enumerates the parent's direct session-backed subagents from the live-preferred merge of ctx.sessions.list() and optional ctx.sessionPersistence.list() — no query seam, and no Agent is loaded or resumed. Candidates are the direct children whose durable header carries origin: 'subagent'; the marker classifies enumeration and coarse generic-route denial but cannot establish a valid descriptor, resumability, or authorization — the projection fold owns identity, and the Activation contract owns resume. Each row's mode/label is the registered subagent projection unit's value, served through a three-rung ladder: the registry's watermark cache for a live child (zero log reads); the optional projection checkpoint cache for a cold one (cachedSnapshot — a served identity is final, because identity is immutable once appended); otherwise one persistence.inspect() reading folded through the registry (bounded concurrency, recomputed per listing). The cache is a pure optional accelerator: absent, serving the null sentinel or missing the key, or faulting, it falls silently through to the authoritative refold. The fold is subagent/descriptor last-wins with no failure channel: the child's own descriptor overrides a fork-seeded ancestor's, and a malformed or unknown-version payload folds to a serializable null sentinel, treated as no value. The result is one SubagentListEntry[] in createdAt-then-id order: a served identity yields a child entry with mode: 'one-shot' | 'continuable' and activity: 'running' | 'inactive'; continuable entries always carry label, while one-shot entries carry it only when the start caller supplied presentation metadata. A settled candidate whose fold served no identity yields a corrupt diagnostic — missing, malformed, and unknown-version descriptors deliberately undistinguished, with unsupported kept in the type for consumers already routing on it but no longer produced; a running candidate without an identity is omitted (the creation window before its descriptor lands); a failed cold inspection yields one unavailable diagnostic retried on the next listing, so one damaged sibling cannot hide healthy children. hasChildren marks a direct descendant with durable subagent origin, read from the same merged material. Activity snapshots only whether the logical record is live in ctx.sessions, not outcome or resumability. Absent persistence, enumeration is live-only rather than an error — a cold child cannot be resumed then either. listChildren() throws SubagentError with code SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE when the ctx.sessionProjections registry is absent and SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE when the session store is, both checked before any read so a deployment with zero children still fails deterministically; the list tool requires ctx.subagents at plugin load. A service consumer such as a UI can display both modes and choose an unlabeled one-shot fallback, while the model-facing list_agents adapter (the separately loadable /list-agents plugin of dsh-tool-subagent-control) keeps only continuable entries and maps activity to its existing running/complete vocabulary. Listing does not consult the continuation manager's Activation map, Agent registry, or provider availability; send_message remains the authoritative delivery-time operation, and a listed running continuable child may still reject delivery as an ownership conflict. The read-path rationale lives in the list-identity-projection Agent Note.
The terminal result: SubagentResult
The outcome of a one-shot run, resolved by SubagentRun.result. structured is present only after a requested outputSchema was successfully satisfied; requesting a schema does not guarantee it, and a provider may return stopReason: 'error' when the child fails or finishes without a valid capture. A non-completed stopReason means output may be partial — the consumer maps it to an isError tool result rather than reporting partial output as success.
/**
* The terminal outcome of a subagent run, resolved by {@link SubagentRun.result}.
*/
interface SubagentResult {
/** The child's final assistant output (the last assistant message's content). */
readonly output: ContentBlock[]
/**
* The structured result after a requested `outputSchema` was successfully
* satisfied. Requesting a schema does not guarantee presence: a provider can
* end with `stopReason: 'error'` when the child fails or finishes without a
* valid capture. Shape is validated against the request schema by the
* provider; `unknown` here because the seam is schema-agnostic.
*/
readonly structured?: unknown
/** Why the run ended. A non-`completed` reason means `output` may be partial. */
readonly stopReason: SubagentStopReason
}
SubagentStopReason is a merge-extensible derived union — a backend may add variants, so consumers branch on the known cases and treat an unknown terminal reason as a failure:
/**
* Why a subagent run ended. Merge-extensible (a backend may add variants);
* consumers branch on the known cases and fall through `default`. The known
* cases mirror the harness turn-end vocabulary so the tool layer can map a
* non-`completed` result to an `isError` tool result.
*/
interface SubagentStopReasonMap {
/** The child finished its turn normally. */
completed: 'completed'
/** Cancelled through the request signal or disposal. */
aborted: 'aborted'
/** Model or transport failure. */
error: 'error'
/** The child hit its token ceiling before finishing. */
'max-tokens': 'max-tokens'
/** The child declined the task. */
refusal: 'refusal'
}
A one-shot run: SubagentRun
SubagentRun is the consumer-owned handle for a published one-shot child — one disposable foreground delegation with one result, never a durable child handle. Prompt submission, turn work, and infrastructure faults after publication belong to result. Consumers await that result and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. A run has no steering and no resume: continuable conversations have no run at all, because the continuation manager holds their AgentHandle directly and orders every turn through the child's own inbox.
/**
* ONE-SHOT child handle returned after publication. Prompt submission, turn
* work, and infrastructure faults after that boundary belong to {@link result}.
* Consumers await that result and must always {@link dispose} to cancel
* remaining work and reach quiescence. A run is one disposable foreground
* delegation with one result; continuable conversations have no run — the
* continuation manager holds their `AgentHandle` directly and orders every
* turn through the child's own inbox.
*/
interface SubagentRun {
/**
* Parent-scoped run id. For a local run, this MUST equal the published child
* session id, whose `parentSession` records `request.parent.session.id`; a
* remote provider mints an id unique in the parent namespace.
*/
readonly id: SessionId
/**
* The exact published in-process child, or `undefined` for a remote run.
* When present, its id is {@link id}; the provider retains no ownership
* implication beyond the run's ordinary {@link dispose} contract.
*/
readonly localAgent: Agent | undefined
/**
* Resolves with the child's terminal {@link SubagentResult} when the run
* settles. Does NOT reject on a child-level failure — a model/transport
* failure resolves with `stopReason: 'error'` so the consumer maps it to an
* `isError` tool result. Rejects on an infrastructure fault the seam cannot
* represent as a stop reason.
*/
readonly result: Promise<SubagentResult>
/**
* Cancel remaining work, reach child quiescence, and release resources.
* Idempotent.
*/
dispose(): Promise<void>
}
A local one-shot run MUST publish an ordinary child agent/session before start() fulfills, return that child session id as SubagentRun.id, expose the exact child as localAgent, record request.parent.session.id in the child's parentSession header, and append the resolved descriptor inside the child's initial turn before its first request. Runtime ownership may place the child under the parent, provider, or root scope. A remote provider instead returns a parent-scoped lifecycle id and localAgent: undefined; without a local child Session, it is absent from durable enumeration.
The provider seam: SubagentProvider
Each provider is a named child-agent transport, and multiple providers may coexist. The service validates requested start-time capabilities before start(), and rejects a continuable start on a provider without prepareContinuable. inheritsParentContext describes only conversation seeding (fork: true; spawn and acp: false), allowing consumers to generate accurate model-facing wording without implying inherited tools, services, or authority.
/**
* One registered transport for running child agents. Providers are trusted
* same-process implementations; callers treat descriptors and returned values
* as borrowed immutable data.
*/
interface SubagentProvider {
/** Unique registry name (e.g. `spawn`, `fork`, `acp`). */
readonly name: string
/** The start-time features this provider supports (see {@link SubagentCapabilities}). */
readonly capabilities: SubagentCapabilities
/**
* Whether the child sees the parent's completed-turn prefix. This is descriptive, not a
* service-validated start capability: the model-facing tool derives truthful wording from it.
* It says nothing about tool registration, injected services, or authority inheritance.
*/
readonly inheritsParentContext: boolean
/**
* Establish a ONE-SHOT child and return its handle after publication.
* The service has already validated that every requested start-time
* capability is supported and resolved `request.descriptor`, so a
* session-backed implementation appends that descriptor inside the child's
* initial turn. Before fulfillment, the provider owns setup and cleans any
* unpublished partial resources before rejecting. Ownership transfers on
* fulfillment; subsequent turn or infrastructure failure settles through
* the returned run.
*/
start(request: ResolvedSubagentStartRequest): Promise<SubagentRun>
/**
* OPTIONAL (continuable-creation capability): contribute the detached
* creation inputs that distinguish this provider's continuable children —
* today only whether the child session is seeded with parent history. Method
* presence IS the capability: the service rejects continuable starts on
* providers without it, while a provider that has it may still serve
* ordinary one-shot delegations.
*
* This is the provider's ONLY participation in a continuable child. The
* continuation manager owns identity reservation, composition, Agent
* creation, prompt delivery, cold resume, ownership, and disposal, so a
* provider never sees the child's Agent, handle, turns, or teardown.
*/
prepareContinuable?(request: ContinuableCreateRequest): Promise<ContinuableCreateSpec>
}
Provider start() fulfills with a published run. The service mints a unique runId, snapshots local from the provider's exact localAgent, observes the result, emits subagent/start, and returns the same run; a start() rejection implies cleanup of unpublished resources and emits no lifecycle pair, while a post-publication result rejection closes the emitted pair. Each continuable Activation emits the same observe-only pair for its residency epoch, so a cold resume is a new epoch with its own runId. The paired subagent/end carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions. Their provider field is provenance for the run or Activation epoch, not a claim that the provider remains registered when the edge is emitted.
In-process backends: depth and seed
The spawn and fork backends create an ordinary one-shot agent through parent.ctx, pass cancellation into core creation, and dispose through AgentHandle; a continuable child is instead created by the continuation manager through its own activation-owner scope. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Depth and fork seeding reuse existing agent and session vocabulary:
- Delegation depth is durable
SessionHeader.delegationDepthplus the merge-extensible runtime fieldAgentOptions.subagentDepth; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absoluterequest.maxDepthcap. - Fork seeding uses
CreateAgentOptions.seed(aSessionEvent[]prefix threaded throughAgentLoop.createAgent→ctx.sessions.prepare({ seed }), the same primitivectx.agents.resume()uses). The fork backend passes a balanced completed-turn prefix of the parent's log — the parent's events up to and including its lastturn/end— so the seed is contiguous-from-0 and the invariants replay accepts it (the in-flight, unbalanced turn is excluded).