The second PR of the subagent seam: the two in-process backends that run a
child agent on the same cordis context, reusing the agent factory's quiescent
AgentHandle teardown. Both register on ctx.subagents (PR1's named-provider
registry) and share one run driver.
- dsh-subagent-spawn: a FRESH child via ctx.agents.create — own session, the
parent's model by default (overridable), zero inherited conversation. Also
exports the shared in-process run driver (startInProcessRun): mint ids, stamp
cwd/parentSession-lineage/depth, drive the one-shot (send → whenIdle), read
the last assistant/message + turn/end reason, dispose to quiescence.
- dsh-subagent-fork: a child SEEDED with the parent's balanced completed-turn
prefix (the log up to and including its last turn/end), so the child inherits
context. The in-flight unbalanced turn is excluded — a raw seed would fail the
invariants replay. Proven: a regression test goes red if the boundary seeds
the open turn.
- Seam extension: CreateAgentOptions.seed, threaded through AgentLoop.createAgent
→ ctx.sessions.prepare({ seed }) (the primitive resume already used). This is
the fork-lineage path the TODO(sub-agents) markers anticipated.
- Depth: a merge-extensible AgentOptions.subagentDepth (0 top-level, parent+1 for
a child); the depthLimit capability refuses a spawn past request.maxDepth.
Tests: real-loop unit tests for both backends (mock MODEL only, real loop +
invariants), a multi-subagent test (one parent drives a fork AND a spawn child
then keeps working), and a with-key e2e (a real parent delegates via the
`subagent` tool to a real child that writes a file on disk — world-verified).
100% per-file coverage. The coding-agent demo wires the spawn backend + tool.
Snapshot coverage of nested agents is deferred to a stacked follow-up
(TODO(subagent-snapshots)): dsh-llm-replay is a single global positional cursor
that cannot route calls to a parent vs. a child on one context. Recorded in the
RFC's deferrals and a new AGENTS.md rule: designing a subsystem must design its
test infrastructure END TO END up front, verifying the snapshot/e2e harness can
express the new shape — a gap this plan hit.
@deepseek-ai/dsh-llm-replay
A replay LLM plugin for keyless snapshot tests. It installs a single llm/stream waterfall listener that short-circuits the waterfall (never calls next()) and yields model streams reconstructed from a recorded session JSONL fixture — so a test can boot the real agent against a fixed model transcript with no API key.
Its consumer is the ACP snapshot harness in examples/acp-agent, which loads this plugin (via cordis.snapshot.yml) in place of a real LLM adapter. The package exists so its derive/parse/replay logic falls under the per-file 100% coverage gate on packages/*/src (the same logic, while it lived under examples/, was outside the gate).
How the fixture works
The fixture IS the persisted session log (<scenario>/session.jsonl). Its assistant/chunk events carry every StreamChunk, so grouping them by (turn, step) reconstructs each stream() call's chunk sequence (one model call per loop step). Recording is therefore "run the real agent once and harvest the .jsonl", done by the snapshot harness — this plugin does not record.
Two failure modes are not reconstructable from assistant/chunk alone — a pure throw before any chunk (e.g. an HTTP 401, where the log holds only a turn/end {error} and no chunks) and a cancel/hang (timing, not chunk content). A scenario that needs those supplies an optional sidecar (<scenario>/replay.override.json: a ReplayEntry[]) that REPLACES the derived script.
Config
| Key | Type | Default | Notes |
|---|---|---|---|
file |
string | $DSH_SNAPSHOT_FILE |
Path to the per-scenario session.jsonl fixture. Required (config or env). |
overrideFile |
string | $DSH_SNAPSHOT_OVERRIDE |
Optional path to a ReplayEntry[] sidecar that replaces the derived script. |
- id: llm-replay
name: '@deepseek-ai/dsh-llm-replay'
# file/overrideFile default to $DSH_SNAPSHOT_FILE / $DSH_SNAPSHOT_OVERRIDE,
# set by the snapshot harness per scenario.
Exports
installLlmReplay(ctx, config)— install thellm/streamlistener; returns the disposer (HMR safety). Use this in tests to drive replay without the Loader or env vars.loadReplayScript(config)— resolve theReplayEntry[]for a scenario (sidecar override if present, else derived from the JSONL; fail-loud if the fixture is missing).deriveReplayScript(events)/parseSessionLog(text)— the pure helpers that turn a recorded session log into a script. A derived group must end in afinishchunk; a group without one is the fingerprint of a thrownstream()and must instead be expressed via an override sidecar.- Types
ReplayEntry/ReplayConfig/Config.
Plugin export shape
Named name / inject / Config / apply, with no default export: the cordis Loader's unwrapExports does exports.default ?? exports, so a stray default would collapse the module to the bare function and drop the inject namespace (see docs/postmortem/0001).