- Rewrite the snapshot-test RFC's replay-plugin section to state current
reality directly (the plugin is the @deepseek-ai/dsh-llm-replay package,
under the coverage gate) instead of keeping the old example-local text with a
"superseded" note bolted on.
- Add docs/rfc/implemented/AGENTS.md (+ CLAUDE.md symlink): an implemented RFC
must be kept current with what actually shipped — update paths/names/structure
in the same change that moves the code, in place, not as an append-only
changelog of its own drift. A reversal of the DECISION is still a new RFC.
- Reconcile docs/rfc/README.md: the "never edited into a different decision"
rule now distinguishes tracking where a decision lives (required) from
flipping the decision (forbidden), and the implemented/ bullet points at the
new convention.
Round 1 of Codex review on the extraction PR.
- (B) EOF-exit race: the 200ms flush-then-exit setTimeout was untracked, so a
fiber/HMR dispose within that window could not cancel it and the process
would still exit. Track the handle and clear it in the disposer; coalesce
re-entrant maybeExit() calls onto the one pending timer. Regression tests for
both (dispose-within-window cancels; repeated idle schedules once).
- (B/doc) ui-stdio rendering is global, not scoped by config.agent (faithful to
the original copies — agent scopes only input + the EOF-exit gate). Corrected
the README + Config JSDoc, which overclaimed "drive and render".
- (C) createStdioChat is exported and driven directly by tests/programmatic
callers that bypass schemastery validation, so default welcome/agent in the
helper (?? 'ready.'/'main') instead of trusting the cast. Test for empty config.
- (A/doc) docs/rfc/.../acp-snapshot-tests.md asserted the replay plugin
deliberately stays in examples/ ("don't split preemptively") — now false since
this PR packages it. Added a superseding note with the why (coverage gate).
All gates green: typecheck, lint, test:coverage (891, 100%), doc-sync,
test:e2e (6 keyless pass), test:snapshot unaffected.
Establishes the standard way to give a snapshot scenario a non-empty starting
workspace: an optional `<scenario>/workspace/` directory whose contents the
harness copies into the temp cwd before the run (for both record and replay),
so the agent's bash tools see the seeded files. The cwd is normalized in the
goldens, so seeded paths stay stable.
The new `workspace-edit` scenario demonstrates the full read→write→verify cycle
on a seeded file: it ships `workspace/greeting.txt` ("hello"), prompts the agent
to append a WORLD line and cat it back. The recorded log captures the real bash
edits (`echo WORLD >> greeting.txt`, then `cat` showing `hello\nWORLD`), and it
replays deterministically with no key.
Also hardens runScenario teardown (Codex review): workspace seeding and spawn
now run inside the try whose finally removes both temp dirs, so a seeding/spawn
failure can't leak them. Documents the convention in the RFC + example README.
The snapshot replay config duplicated most of base.yml + the acp tail just to
swap llm-deepseek → llm-replay. Factor the shared pieces:
- examples/base-core.yml: the providerless provider/tool core (llm, sessions,
system-prompt, tools, agents, invariants, bash-local, tool-bash). base.yml is
now base-core + the llm-deepseek adapter; the snapshot replay config is
base-core + llm-replay. The replay config no longer hand-copies the core.
- examples/acp-agent/acp-tail.yml: agent-loop (no pre-created agents) +
persistence + the ACP bridge/system-prompt, shared by cordis.yml and the
replay config so the three acp-agent configs can't drift. Its persistence root
is `$DSH_SNAPSHOT_SESSIONS_ROOT ?? ./.sessions`.
- Deleted cordis.snapshot-record.yml: recording now reuses the normal cordis.yml
(real adapter), with the harness redirecting the persistence root via env.
start.ts maps DSH_SNAPSHOT=record → cordis.yml.
Verified: snapshot replay 8/8 keyless; record path works through cordis.yml;
ACP e2e no-key boot green through the doubly-nested include (cordis.yml →
base.yml → base-core.yml); coding-agent boots clean; all gates pass.
The goldens now mirror the shape of the surfaces they capture — one compact
JSON record per line — matching the wire (NDJSON stdout) and disk (JSONL
session log) formats, renamed *.golden.jsonl. They stay grep/jq-able and
faithful to what the agent emits, where the prior pretty-printed .txt was a
reformatted representation. Both normalizers drop the 2-space indent; the
normalizer spec asserts the compact form. All 11 goldens regenerated; replay
remains deterministic (8/8 across runs).
Per a design revision, the per-scenario snapshot fixture becomes EXACTLY the
persisted session JSONL (<scenario>/session.jsonl) rather than a hand-authored
llm.json. The log already holds all LLM behavior (assistant/chunk carries every
StreamChunk) AND all harness behavior (tool/call, tool/result, turn/*, usage),
so one artifact drives replay and doubles as a behavioral golden.
llm-replay becomes replay-only (the record-tee is removed; recording is now
"run the real agent once and harvest the .jsonl", done by the harness in a
later commit). deriveReplayScript(events) groups assistant/chunk by (turn,step)
in log order — exact because the loop makes one ctx.llm.stream() call per step
and tags each chunk with the current (turn,step). The two failure modes the log
can't express (a thrown stream — no terminal finish; cancel/hang — timing) use
an optional replay.override.json sidecar.
Hardens against a Codex review finding: a derived group is only valid if it
ends in a `finish` chunk. A group without one is the fingerprint of a thrown
stream() and is NOT silently replayed as a clean stop — deriveReplayScript
throws, naming the (turn,step), so a missing sidecar override fails loud.
Updates the unit tests (parse/derive/load helpers, sidecar override, finish-
terminated grouping, HMR), the example README, and the RFC prose to the JSONL
format. Two goldens (stdout transcript + re-persisted JSONL) and the harness
wiring land in the next commit.
Records the decision to add a third test tier: snapshot tests that boot the
real acp-agent subprocess over ACP stdio, record the LLM's streamed responses
once against the real API, then replay them deterministically so the full
stdout transcript can be diffed against a committed golden — keyless in CI.
Captures the design choices hardened in a Codex (xhigh) review: record at the
provider-neutral llm/stream waterfall; a discriminated fixture entry schema
(chunks/throw/hang) that honors both LLM failure branches; positional replay
with a one-in-flight-stream constraint; per-stream atomic fixture flush (the
subprocess is SIGKILLed, so dispose-time flush would never run); a providerless
replay config; normalize-then-snapshot parsed frames; normalization over an OS
sandbox now with the rootless bwrap/sandbox-exec tier reserved via the
BashExecutor capability seam. Cross-links the proposed determinism RFC
(complementary: internal history invariant vs external protocol contract).