DeepSeek Harness · Orchestration & Subagents

Subagent Is a Seam: In-Process to Delegating Claude Code

A sub-Agent is a capability seam — in-process, remote, or another product can plug in. Core source: packages/subagent/.

Course goalAfter this you can explain three things: why DSH defines sub-Agents as a provider registry (a seam) so in-process spawn, context-carrying fork, and delegating to Claude Code or Codex share one interface; why capability mismatches fail loud before start instead of silent ignore; and what one-shot run vs continuable Activation each solve.
Interactive demo · Delegation switchboard

Same sub-task “research this module and report back” — switch among four providers and delegate once each. Watch two things: what stays identical on both sides of the seam, and what changes with the impl. Then flip “attach persona requirement” and see how the capability latch blocks out-of-scope requests before start.

Parent Agent session log (inside the DSH process)
user“Help me wire in the payment module”
assistantSure — I’ll look at the existing structure first.
turn/endTurn 1 ended (seed boundary)
user“Also check this module’s historical issues”
tool/callsubagent “research this module and report back”
ctx.subagents · provider registry (seam)
spawn fork acp codex claude-code sdk
Capability latch & sub-Agent stage
Start-time capabilitiesspawn
outputSchema (structured output)Supported
depthLimit (delegation depth cap)Supported
toolFilter (limit tools)Supported
persona (swap persona)Supported
SubagentError: subagent provider "claude-code" does not support the "persona" capability · code: UNSUPPORTED_CAPABILITY
Sub-AgentIn-process
(context empty)
Sub-Agent working
Pick a provider and hit “Play”, or scroll here to auto-play spawn.
Teaching simulation. Capability-table data from each provider’s source: spawn and fork support all four (subagent-spawn-in-process/src/index.ts line 42, subagent-fork-in-process/src/index.ts line 62); Claude Code and Codex are both NO_START_CAPABILITIES (their src/index.ts lines 54 and 49). Error copy verbatim from packages/subagent/subagent/src/index.ts lines 490–493.
How it works · which layer defines the seam

Conclusion first. DSH didn’t build a standalone sub-Agent feature — it built a registry called ctx.subagents: any transport implementing SubagentProvider can register by name. The official release registers six: spawn (in-process fresh), fork (in-process with context), acp (protocol bridge), codex, claude-code (each starts a real product CLI process), sdk (remote DSH instance). Source: docs/subsystems/subagent.zh.md lines 5–7.

Input: the tool layer assembles the model’s delegation into a SubagentStartRequest with prompt, parent Agent, cancel signal, plus four optionals (structured-output schema, depth cap, tool filter, persona). What happens: the service checks the chosen provider’s static capability table first — each optional needs a matching capability flag; missing one throws UNSUPPORTED_CAPABILITY before start. Output: a SubagentRun handle; the parent awaits its result, which lands as an ordinary tool result. To the parent, all four providers return the same kind of thing.

Worth pausing on fork’s identity. Many frameworks make “carry parent context” a boolean. DSH makes fork its own provider because they differ by more than a switch: fork must slice a “balanced prefix of completed turns” from the parent log as seed, cutting at the last turn/end — in-flight turns are unbalanced and unreplayable, so they must be excluded. That’s a session-log contract a flag can’t explain.

tool-subagent Model says “go research” StartRequest ctx.subagents registry Pick provider by name · check capability table Missing capability → UNSUPPORTED_CAPABILITY spawn / fork In-process sub-Agent fork carries parent-log balanced prefix All four capabilities supported acp / sdk Protocol bridge & remote instance localAgent: undefined claude-code / codex Starts a real product CLI process Uses parent session’s cwd All start-time capabilities false Back to parent Agent SubagentRun.result → one tool result Non-completed maps to isError Above the seam everything is the same; below it, transport differs by provider
Teaching diagram: nodes and edges explain source relationships; content is course-adapted.
fork and spawn are two providers

Difference isn’t parameters — it’s the seed: fork slices the parent log to the last turn/end as child-session seed; spawn starts from zero. inheritsParentContext is descriptive only, so the tool layer can phrase honestly.

One-shot run vs continuable Activation

SubagentRun is one-and-done: await result, dispose, end. A continuable sub-Agent has no run — it’s a persistent session plus at most one resident Activation; the parent uses send_message to append turns, interrupt_agent to interrupt, and receives report.

Background end isn’t silent

When a continuable sub-Agent settles, the manager unconditionally injects a subagent-settled notice to the parent with final output. It uses a different message-source kind than the sub-Agent’s own report, so the transcript won’t count runtime bookkeeping as the sub-Agent speaking.

Key evidence · Capability latch & fork seed

First evidence is the capability latch itself — clear without pasting code. assertCapabilities turns the four optionals into a demand list: if the request carries outputSchema, the provider table must have outputSchema true; maxDepth demands depthLimit; toolFilter and persona likewise. Then item-by-item check — first mismatch throws SubagentError naming which provider lacks which capability, code UNSUPPORTED_CAPABILITY. No degrade, no warn-and-continue; no child process starts before this.

Source: packages/subagent/subagent/src/index.ts lines 481–495, verified on 2026-08-13. Demo error copy is verbatim from lines 490–493.

Second evidence is fork’s seed function — seven lines that say what “with context” actually carries:

packages/subagent/subagent-fork-in-process/src/index.tslines 48–54 excerpt
function completedTurnPrefix(parent: Agent): SessionEvent[] {
  const events = parent.session.events
  const lastEnd = events.findLast(e => e.type === 'turn/end')
  if (lastEnd === undefined) return []
  // seq === array index (the append contract), so slice up to and including it.
  return events.slice(0, lastEnd.seq + 1)
}
Source snapshot note: Based on the local deepseek-harness-master repo; verified against packages/subagent/subagent/src/index.ts and packages/subagent/subagent-fork-in-process/src/index.ts, verified on 2026-08-13. Code blocks keep the original source text.

Two more mechanisms without pasted code — cite in prose. The whole Claude Code provider is: resolve the claude binary, start a real CLI via the official Agent SDK in the parent session’s cwd, hang it under the shared subprocess owner (subagent-claude-code/src/index.ts lines 62–91). In the four official presets, codex and claude-code delegation tool rows ship with disabled: true; comments say: copy the preset, delete disabled, and that product backend opens only for the copied session (apps/cli/config/agent-presets/standard/agent.cordis.yml lines 200–219). Delegating to another product in DSH is a config switch.

Side-by-side · In-product delegation shapes vs transport-layer registry

Claude Code puts delegation in the product layer. One Task tool (AgentTool) entry packs shapes into params: subagent_type picks role, run_in_background backgrounds, isolation: "worktree" opens an isolated copy, model swaps models (manuscript study/chapters/05-multi-agent.md lines 32–52 citing AgentTool.tsx). Above that sit Coordinator Mode and Agent Teams. Expressive — but every shape is a feature branch inside one product: the delegate is always another Claude Code instance; handing work to another product has no slot. DSH chooses the opposite: product differences sink to the provider layer; above the seam there’s one vocabulary.

Grok Build takes a third path: extract sub-Agent config resolution into a pure-logic crate xai-grok-subagent-resolution, resolving effective config by explicit override > role > persona > parent (that crate’s src/lib.rs lines 7–8), while execution stays in their own shell process. It abstracts what a sub-Agent looks like; DSH abstracts where a sub-Agent runs. Grok’s roles, context inheritance, and tree lineage already have three lessons on this site: Four sub-Agent roles, Context inheritance & depth control, Sub-Agent session lineage.

Classroom Exercise
01

Walk through an out-of-scope delegation

Deploy enables the subagent_claude_code tool; the model’s delegation carries outputSchema (wants structured output). Walk the capability-latch code from this lesson: which line throws, what error code, and did the Claude Code CLI process ever start? Then: if DSH accepted the request but ignored the schema, what goes wrong in the parent’s tool result, and why is that harder to debug than failing loud?

Takeaway: In DSH a sub-Agent is a provider registry — in-process fork and delegating Claude Code share one interface; differences all sink below the seam. Capability mismatches fail loud before start — never accept-then-silently-degrade. Want to add “delegate to another product” to your Agent system? Ask first which layer defines the seam, then how the capability table is checked.