Skip to content

What a subagent is

A subagent is a child agent with its own session, its own scope, and its own turn loop that one parent agent starts to offload a self-contained task. In DeepSeek Harness a subagent is not a special runtime mode — it is a capability seam (ctx.subagents, owned by packages/subagent/subagent) backed by a registry of named providers, so an agent delegates through one service API while the transport is swappable. Unlike a shell seam (one executor), the subagent seam lets multiple providers coexist by name. The second difference from a shell tool is that a subagent is not a tool: it is a scoped service, and the tools the model actually calls (subagent, send_message, report, …) are thin consumers of it.

The domain vocabulary lives in the glossary: a subagent gets its own scope, its parent/child facts travel as lineage data, and a creator composes the child's world in the setup window. Scope is deliberately flat — scoped registrations do not inherit down to children; anything the child needs is composed explicitly during setup.

parent Agent ──ctx.subagents.start/startContinuable──▶ provider
                                                          │ (spawn | fork | acp | codex | claude-code | dsh-sdk)

                                               child Agent + child Session
                                               (own scope, own turn loop)
                                                          │ report / settlement

                                                    parent turn stream

The package family

PackageRolectx keyTransport
subagentService Definition: provider registry, contracts, descriptors, continuationctx.subagents
subagent-in-process-driverShared one-shot run driver (depth, composition, structured output)in-process
subagent-spawn-in-processFresh child, no parent historyregisters on ctx.subagentsin-process
subagent-fork-in-processChild seeded with parent's completed turnsregisters on ctx.subagentsin-process
subagent-dsh-sdkOut-of-process Harness child via the TS SDKregisters on ctx.subagentsexternal process
subagent-acpOut-of-process child over the Agent Client Protocolregisters on ctx.subagentsexternal process
subagent-claude-codeReal Claude Code child (official SDK)registers on ctx.subagentsexternal process
subagent-codexReal Codex app-server childregisters on ctx.subagentsexternal process
tool-subagentModel-facing delegation tool (subagent)registers on ctx.tools
tool-subagent-controlModel-facing send_message / interrupt_agent / list_agentsregisters on ctx.tools
tool-subagent-reportChild-scoped report return channelregisters in child scopes
client/ui-subagentWeb catalog tree, @ reference, composer controlctx events + ctx.inputTriggers

The service contract (dsh-subagent)

The heart of the seam is the SubagentRuntime service in packages/subagent/subagent/src/index.ts, with the typed contracts in src/types.ts.

MemberMeaning
registerProvider(provider)Register a trusted same-process provider by name; duplicate names fail loud.
start(name, request)One-shot foreground delegation; resolves with a holder-owned SubagentRun after the child is published.
startContinuable(spec)Establish one durable continuable child and deliver its initial prompt; returns { childId, messageId }.
followup(parent, childId, content, …)Deliver a later message from the exact live direct parent as the child's next FIFO turn.
interrupt(targetSessionId, authority)Stop one live continuable child's current turn (keepInbox: true).
reportFrom(child, content, …)Deliver a selected message from the exact live continuable child to its direct parent.
listChildren(parentSessionId) / listDescendants(rootSessionId)Enumerate the durable subagent catalog.

The provider contract

SubagentProvider (in src/types.ts) is the interface transports implement: { name, capabilities, inheritsParentContext, start(request), prepareContinuable?() }. capabilities is four booleans that the service validates before delegating — outputSchema, depthLimit, toolFilter, persona — so a request needing an unsupported feature is rejected loudly rather than accepted-then-ignored. inheritsParentContext is descriptive, not enforceable: it says only whether the child sees completed parent history (fork does; spawn and out-of-process providers do not), never whether it inherits tools, services, or authority.

prepareContinuable?() is the optional method whose presence is the continuation capability. It returns only a detached ContinuableCreateSpec ({ seed? }) — data, never a capability — because the continuation manager owns identity reservation, composition, agent creation, prompt delivery, cold resume, ownership, and disposal after preparation.

Creation options

SubagentStartRequest carries the one-shot delegation options. The model-facing tool builds these from the model's { description, prompt } plus its own config:

FieldMeaning
promptContent delivered as the child's user message.
parentThe spawning agent; in-process providers derive workspace, lineage, and delegation depth from it.
signalThe canonical cancellation channel, before and after startup.
agentOptionsOptional child provider, model, maxTokens overrides.
outputSchemaObject-rooted JSON Schema for a structured final result (needs outputSchema capability).
maxDepthAbsolute delegation-depth cap for the child (needs depthLimit).
toolFilterChild tool restriction, applied as a scoped tools.restrict() (needs toolFilter).
personaPer-child persona, shadowing the deployment persona (needs persona).
labelOptional durable display label for a session-backed child.

The setup window and delegation policy

Composition happens inside the child's setup window — after the scope and agent exist, before the agent/session is published. For in-process children, applyChildComposition(childCtx, parent, composition) (in packages/subagent/subagent/src/child-agent.ts) joins the parent's agent-preset composition before applying the child's own persona and tool filter — the join is what gives a model-facing child a working tool registry. childSessionMeta() records the joined preset id on the durable header so a cold read rebuilds the same composition.

Delegation also fixes the child's permission scope at the boundary (src/child-agent.ts helpers): captureDelegatedPolicyOverrides(parent) snapshots the parent's explicit sandbox override and pins the child's approval policy to 'never' whenever the approval capability is composed, so every sandbox_permissions ask is rejected deterministically. appendDelegatedPolicyOverrides() writes each value to the child's own log as a source: 'delegation' sandbox/mode or approval/policy event. Every in-process child also gets a scoped runtime-context statement (subagent:delegation) telling it the scope is fixed.

In-process vs spawned vs external backends

The in-process providers share one run driver (in subagent-in-process-driver). Spawn and fork differ only in their seed:

Aspectspawnfork
Session seednonebalanced completed-turn prefix (up to the last turn/end)
Parent history visiblenoyes (one-time snapshot)
Capabilitiesall fourall four
Modelinherits parent unless overriddencopies inherited prefix bytes for cache reuse
Continuable pathusableimplemented but no shipped composition uses it (bind to backgroundMode: one-shot)

The shared driver's startInProcessRun() clears the parent depth, calls parent.ctx.agents.create directly, installs persona/tool-filter/structured-output in the unpublished setup window, drives one task with child.followup(prompt) + child.whenIdle(), then reads the child's output.

The external providers run the child in a separate process and return localAgent: undefined, so their one-shot runs are not part of trace-backed enumeration:

  • subagent-acp speaks the Agent Client Protocol (@agentclientprotocol/sdk) over a child subprocess with a configurable permission policy (allow/reject) for the child's own permission prompts.
  • subagent-codex drives a real Codex app-server child.
  • subagent-claude-code drives Claude Code through the official Claude Agent SDK.
  • subagent-dsh-sdk runs an out-of-process Harness child through the TypeScript SDK.

Delegation lineage and depth

Parent/child facts are carried as lineage data, never by scope structure: the child's SessionHeader records parentSession, a durable delegationDepth, and identity origin: 'subagent'. Depth accounting lives in packages/subagent/subagent/src/depth.ts:

ts
export function delegationDepthOf(agent: Agent): number {
  const runtime = agent.options.subagentDepth
  if (runtime !== undefined && (!Number.isSafeInteger(runtime) || runtime < 0 || Object.is(runtime, -0)))
    throw new TypeError('agent subagentDepth must be a non-negative safe integer')
  // The header value was validated at the session boundary (creation and load).
  return Math.max(agent.session.header.delegationDepth ?? 0, runtime ?? 0)
}

The persisted SessionHeader.delegationDepth is authoritative and monotone — runtime options may deepen it but never lower it — so a resumed child cannot be re-counted as top-level. assertSubagentMaxDepth() validates a recorded cap. The model tool's maxDepth default is 3 (0 forbids delegation).

The result contract

A one-shot SubagentRun is { id, localAgent, result, dispose() }. result resolves with SubagentResult = { output, structured?, stopReason }. The stop reasons mirror the harness turn vocabulary — completed, aborted, error, max-tokens, refusal. Crucially, result does not reject on a child-level failure (it resolves with stopReason: 'error' so a consumer can map it to an errored tool result); it rejects only on an infrastructure fault the seam cannot represent. dispose() is idempotent and cancels remaining work. The AssistantOutputFold/finalAssistantOutput helpers select the child's last non-empty assistant message (usage-only messages are skipped), else its accumulated assistant text.

Continuable children (the Activation)

A continuable child has one durable Session and at most one process-local Activation — a residency epoch for a reconstructed child Agent. The Agent inbox is the only turn queue, so the continuation manager (in src/continuation.ts) owns residency while the Agent loop owns turn ordering and execution. Every continuation message is Agent.followup() and becomes one FIFO turn. Routing depends only on residency: running enqueues, waiting wakes the same Agent, an absent Activation cold-resumes a new one from the durable subagent/descriptor. The manager cold-resumes never dispatches through a provider — the folded descriptor is the whole reconstruction input. On settlement, the child's durable direct parent receives a settlement notice (source kind subagent-settled), delivered before ownership release, as one ordinary later turn (waking) or by injection into a draining lineage.

The three model-facing tools

dsh-tool-subagent — delegation

Each plugin instance binds one provider to one toolName (default subagent). The model receives the { description, prompt } plus optional run_in_background:

ConfigDefaultMeaning
provider (required)Provider name (spawn, fork, acp, …)
toolNamesubagentModel-facing name, distinct per instance
enableRunInBackgroundtrueExposes background mode
backgroundModeone-shotone-shot (Task-backed job) or continuable (durable child id)
agentOptions / persona / toolFilter / maxDepthChild customization passed into start()

Foreground awaits run.result and always dispose(). One-shot background registers a plain parent-owned Task and returns { kind: 'background', jobId }. Continuable background calls ctx.subagents.startContinuable() and returns { kind: 'continuable', subagentId }.

dsh-tool-subagent-controlsend_message / interrupt_agent / list_agents

Optional globally named control tools over ctx.subagents, registering once so multiple delegation tools never duplicate them. send_message(subagent_id, message) becomes the child's next FIFO turn and returns no reply. interrupt_agent(agent_id) stops only the current turn (keepInbox), parking queued messages. list_agents(scope: 'children' | 'descendants') projects the durable catalog to continuable children with status running / idle / ready (storage-only, resumable-not-terminal).

dsh-tool-subagent-report — child→parent report

A child-scoped tool installed into continuable child scopes via registerContinuableSetup(), not globally. report(output: string) reaches exactly the child's live direct parent (derived from durable parentSession); reportDelivery selects wakeup (default, one ordinary parent turn) or quiet (parent.inject(), context without a model request). Report success returns the stable parent-accepted MessageId, not a delivery receipt.

Lifecycle events

The service emits subagent/start / subagent/end (scoped to the delegating parent, sharing a runId, with a local flag) for every one-shot run and every resident continuable Activation epoch, plus subagent/provider-added / subagent/provider-removed. Providers record the durable subagent/descriptor session event (versioned, snapshotSubagentDescriptor() / foldSubagentDescriptor()), which is log-only: absent from model history, retained across compaction.

UI (briefly)

client/ui-subagent contributes the lazily expandable subagent catalog tree to conversation.session.header.actions, reason-specific read-only composer replacements, and the existing @ reference source to ctx.inputTriggers. It reads subagentsByParent and session summaries through the standard useSessions hook; selecting a row calls SessionRuntime.openSubagent() with the exact { parentSessionId, childSessionId, mode } address. The catalog is model-free: subagent-origin rows are omitted from the sidebar, so the header catalog is their navigation entry point.

Known limitations

  • ACP children stay one-shot and non-trace-enumerable — they have no local child session in the parent's corpus.
  • No host-user continuationfollowup() requires the exact live direct parent; only interrupt() accepts a durable parent-address.
  • No current-turn steering — continuable messages and waking reports enqueue later turns.
  • Process-local residency — Activations do not coordinate two harness processes; a durable mailbox and lease protocol are deferred.

Further reading

  • Session query & log export and Workflow & Ralph — sibling orchestration families that ctx.subagents powers.
  • Glossary — entries for scope, setup window, lineage, goal round.
  • The subsystem reference in the repo: docs/subsystems/subagent.md.
  • The seam's own README: packages/subagent/subagent/README.md, plus each provider's README.md under packages/subagent/<provider>/.
  • Agent Notes that own the decisions: .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md and 2026-07-21-continuable-background-subagents.md.
  • The full contract types: packages/subagent/subagent/src/types.ts.