Files
deepseek-harness/examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl
Tianyi Cui e68496fd79 Add per-session snapshot replay for nested agents (PR2.5)
The snapshot tier was built single-session: dsh-llm-replay served calls from
one global positional cursor, and the harness harvested one session log. A
subagent runs as a second agent with its own session, so a parent→child
scenario could neither replay deterministically nor harvest the child's log.
This resolves the TODO(subagent-snapshots) deferral from the subagent RFC.

- Stamp the calling session id onto the model request: GenerateOptions.sessionId
  (typed Branded<'SessionId'> to avoid the dsh-llm↔dsh-session cycle), set by the
  agent loop from agent.session.id. Adapters ignore it; an llm/stream listener
  routes by it.
- Key replay per session: dsh-llm-replay loads the parent log plus one per child
  (childFiles / $DSH_SNAPSHOT_CHILD_FILES), derives a script per recorded session,
  and binds each live (freshly-random) session to a recorded script by first-call
  order — parent first (earliest createdAt, first to stream). Keys by WHO calls,
  so it survives a future concurrent/backgrounded subagent; a global cursor would
  not. An unrecorded extra session fails loud.
- Harvest every log: the harness collects all .jsonl across cwd buckets, ordered
  primary-first (top-level, then children by createdAt), and RunResult exposes the
  plural sessionLogs. The spec writes each back on record (session.jsonl +
  session.<n>.jsonl) and diffs each against its fixture on replay.
- Wire the subagent seam + spawn + fork + tool into the acp-agent example (both
  cordis configs) and add two nested scenarios recorded against the real API:
  subagent-spawn (parent + 1 child) and subagent-multi (parent + 2 children, 3
  sessions). Both replay keyless in the default gate.

A new RFC documents the design (docs/rfc/implemented/testing/). Single-session
replay is unchanged (a call with no sessionId is one anonymous primary session).

TODO follow-up: a dedicated branded-ids package could own the SessionId brand and
dissolve the cross-package cycle note; out of scope for this testing PR.
2026-06-22 08:39:36 +08:00

34 lines
4.8 KiB
JSON

{"type":"session","version":0,"id":"52bb4e53-1cf4-4680-b954-4ad941a9e986","createdAt":1782087752261,"cwd":"/tmp/acp-snap-cwd-v6PaeC","parentSession":"2a50c62e-1d77-4b0e-bfab-3a9285e3aa32"}
{"type":"turn/start","seq":0,"time":1782087752262,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1782087752262,"data":{"content":[{"type":"text","text":"Reply with exactly the word BETA and nothing else."}],"source":{"kind":"user"}}}
{"type":"step/start","seq":2,"time":1782087752262,"data":{"turn":1,"step":1}}
{"type":"assistant/chunk","seq":3,"time":1782087752714,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"assistant/chunk","seq":4,"time":1782087752714,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
{"type":"assistant/chunk","seq":5,"time":1782087752857,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
{"type":"assistant/chunk","seq":6,"time":1782087752875,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" asked"}}}
{"type":"assistant/chunk","seq":7,"time":1782087752909,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
{"type":"assistant/chunk","seq":8,"time":1782087752909,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
{"type":"assistant/chunk","seq":9,"time":1782087752910,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
{"type":"assistant/chunk","seq":10,"time":1782087752910,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
{"type":"assistant/chunk","seq":11,"time":1782087752910,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" exactly"}}}
{"type":"assistant/chunk","seq":12,"time":1782087752941,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" the"}}}
{"type":"assistant/chunk","seq":13,"time":1782087752941,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" word"}}}
{"type":"assistant/chunk","seq":14,"time":1782087752942,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
{"type":"assistant/chunk","seq":15,"time":1782087752942,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"B"}}}
{"type":"assistant/chunk","seq":16,"time":1782087752942,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ETA"}}}
{"type":"assistant/chunk","seq":17,"time":1782087752942,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
{"type":"assistant/chunk","seq":18,"time":1782087752974,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
{"type":"assistant/chunk","seq":19,"time":1782087752975,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" nothing"}}}
{"type":"assistant/chunk","seq":20,"time":1782087752975,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" else"}}}
{"type":"assistant/chunk","seq":21,"time":1782087752975,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
{"type":"assistant/chunk","seq":22,"time":1782087753004,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"assistant/chunk","seq":23,"time":1782087753004,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"B"}}}
{"type":"assistant/chunk","seq":24,"time":1782087753004,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"ETA"}}}
{"type":"assistant/chunk","seq":25,"time":1782087753004,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user asked me to reply with exactly the word \"BETA\" and nothing else."}}}}
{"type":"assistant/chunk","seq":26,"time":1782087753004,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"BETA"}}}}
{"type":"assistant/chunk","seq":27,"time":1782087753005,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":67,"outputTokens":21,"cacheReadTokens":896,"reasoningTokens":18}}}}
{"type":"assistant/chunk","seq":28,"time":1782087753005,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":29,"time":1782087753005,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user asked me to reply with exactly the word \"BETA\" and nothing else."},{"type":"text","text":"BETA"}],"usage":{"inputTokens":67,"outputTokens":21,"cacheReadTokens":896,"reasoningTokens":18}}}
{"type":"step/end","seq":30,"time":1782087753005,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":31,"time":1782087753005,"data":{"turn":1,"reason":{"kind":"completed"}}}