Research / Effects, recovery and replay
P20 · full · draft
Replaying Agent Runs: A Write-Ahead Log That Seals Each Model Decision and States Which Replay Modes the Log Can Support
partial full Effects, recovery and replay
State of the work. Sealed model-decision evidence, an offline reader and a replay-readiness planner are shipped on the Python tier with tests; no replay mode is executable and no evaluation exists yet.
This is a draft, not peer reviewed. Repository references link to
revl at commit 45bec431f.
Planned to be combined with P18 (Time Travel over Typed Effects: Backwards Replay, Forward Replay and Execution Bisect), P19 (Counterfactual Incident Replay: Asking What a Different Policy or Action Would Have Changed, With Zero Live Effects by Import Shape), P21 (Branching Agent Sessions With Rewound Side Effects: Scope-Gated Rewind and a Frozen Parent) into a single paper.
Pitch
A write-ahead log can seal each model decision as a MAC-covered evidence object and then tell you, mode by mode, whether the log is enough to replay the run exactly, with tools only, with another model, or counterfactually, and which inputs are missing when it is not.
Abstract
Agent observability tools capture prompts and responses, but they do not say whether a run could be replayed, nor detect a stored decision altered after the fact by a party without the sealing key. We describe an LLM-aware write-ahead log in revl. Each model crossing writes a durable model-decision entry; when a run engages evidence sealing, the crossing seals a MAC-covered object binding the model digest, placement, prompt binding, input origins, candidate set, chosen candidate, outcome, sampling, policy digest and fallback depth. An offline reader rebuilds the decision from the log alone and returns no reading when any member has been edited. A readiness planner then evaluates four replay modes (exact, tool-only, model-substitute, counterfactual) against a fixed vocabulary of required inputs and reports, per mode, which inputs the log holds and which are missing. A mode is never reported executable offline. The causal trace treats the model completion as a first-class hop, with a salted prompt digest, host-reported cost fields marked unverifiable, and a per-activation identifier because a produced-sequence number is not a causal proof under concurrency. The evidence is structured as tests that edit each sealed member in the WAL file and require refusal, and a mutation-style test that reintroduces a historical MAC bug and shows the tamper suite reddens. The live executor that would actually run a mode is specified and deferred; response text is never written, so exact and tool-only replay are unreachable from a WAL by design. Test coverage: 56 + 33 + 17 + 13 + 61 test functions across five files; no performance or user-study data exists.
Problem and claim
Replay of agent runs is commonly assumed to need full capture of prompts and responses. That capture is the exfiltration channel: a prompt may contain secrets, and a stored digest of a short secret is a confirmation oracle. Meanwhile "we logged the decision" is not accountability if the log can be edited without trace.
The claim: a log can carry enough, sealed and content-free, to rebuild what a model decision was (what answered, how the prompt was bound, what candidates existed, under which rule) and to state exactly which replay modes it supports, without storing prompt or response text. A tampered decision yields no reading rather than a reading with a warning.
The claim is not that agent runs can be replayed. On main the live executor does not exist: revl replay reports readiness only and executable is false for every mode (src/revl/replay_modes.py:365; tests/test_250_replay_modes.py:119). The paper is about the log format and the readiness contract.
Contributions
- A sealed
ModelDecisionevidence object with a closed member list (BODY_MEMBERS,src/revl/model_evidence.py:279), written at the crossing through a callable sealer so the runtime never holds the key (docs/design/552-model-decision-replay.mdsection 2;CrossingSealer,src/revl/model_evidence.py:1020). - A reader that verifies and reconstructs the decision from the WAL alone and returns no decision when verification fails, including a join check that a genuine seal lifted onto another crossing is refused (
reconstruct,src/revl/model_evidence.py:1176; design doc section 3.1). - A readiness planner over four replay modes and a fixed requirement vocabulary (
modelDecisions,responseText,promptDigest,toolCalls,seedsAndClock,liveComponent), with an explicit rule that blocked modes name the missing input (src/revl/replay_modes.py:86;docs/design/250-slice3b-replay-modes.md). - Fail-direction discipline: three states for evidence (not sealed, sealed, refused-and-stopped), the refusal written before the raise so a post-mortem can tell "refused" from "never engaged" (design 552 section 1).
- A causal trace in which the model completion is a hop, with salted prompt digests, secret suppression, honest-degrade fields, and per-activation ids (
src/revl/trace.py;docs/design/121-revl-trace.md;tests/test_trace_121.py). - A non-vacuity method for tamper tests: generated from
BODY_MEMBERSand checked by injecting the historical bug (tests/test_model_evidence_replay_552.py:662).
Evidence on main
| Claim | path:line or test | Status | Note |
|---|---|---|---|
A model crossing writes one model-decision entry after its effect | tests/test_250_model_decision_wal.py:95 | shipped | Slice 3a |
| No prompt or response text reaches the WAL | tests/test_250_model_decision_wal.py:137 | shipped | |
| Pre-3a WAL bytes unchanged | tests/test_250_model_decision_wal.py:227 | shipped | |
| Retries counted on one decision; exhausted budget still writes | tests/test_250_model_decision_wal.py:248, tests/test_250_model_decision_wal.py:269 | shipped | |
| Entry invisible to partition and recover | tests/test_250_model_decision_wal.py:325 | shipped | |
| Four modes planned; none executable offline | tests/test_250_replay_modes.py:107, tests/test_250_replay_modes.py:119 | shipped | Readiness only |
| Exact and tool-only never plannable without response text | tests/test_250_replay_modes.py:135 | shipped | By design |
| Model-substitute and counterfactual plannable when decisions are logged | tests/test_250_replay_modes.py:125 | shipped | "plannable" means informable, not runnable |
| Sealed evidence written by a real run, read back, and refused on any edit | generated tamper suite (tests/test_model_evidence_replay_552.py:662 is the injected MAC-bug test; the lifted-seal join check is tests/test_model_evidence_replay_552.py:634); docs/design/552-model-decision-replay.md section 5 | shipped (py tier only) | The design doc states 53 cases; there are 33 test functions in the file, the rest presumably parametrised |
| Refusal written before the raise, so the artifact names the crossing | tests/test_model_evidence_replay_552.py:337 (test_the_refusal_reaches_the_artifact_before_the_run_stops) | shipped | |
| Engaged with no durable sink refuses | tests/test_model_evidence_replay_552.py:385 | shipped | |
| Placement cross-checked with the model route table | tests/test_model_evidence_replay_552.py:758, tests/test_model_evidence_replay_552.py:799 | shipped | |
revl replay --evidence-key verifies and reconstructs | src/revl/cli/parser.py:1939-1950 | shipped | |
| Verifier and sealer fail closed on each check their tests cover | tests/test_model_evidence_517.py (56 test functions) | shipped | |
| Trace hop with provenance, salted digest, no text field | src/revl/trace.py:1-40; tests/test_trace_121.py (61) | shipped | |
| Per-activation id | src/revl/why_runtime.py:142; src/revl/run.py:1451; tests/test_trace_121.py:705 | shipped | Honest-degrade gating (tests/test_trace_121.py:666) and the fiber-local token were not verified |
Live executor (revl replay branch) running a mode | docs/design/250-slice3b-replay-modes.md section "The live executor (deferred, with its requirements)" | designed, deferred | Roadmap item 250 (docs/v2.0-roadmap.md:1390) still lists revl replay branch as deferred |
| Exact and tool-only replay | docs/design/250-slice3b-replay-modes.md | not reachable by design | response text never written |
| Seed and clock captured | docs/design/250-slice3b-replay-modes.md | not built | counterfactual without it is "a divergent continuation, not a controlled A/B" |
| Non-py tiers write evidence | docs/design/552-model-decision-replay.md section 6 | not built | reads as "not sealed" |
revl canary names the differing evidence member | docs/design/552-model-decision-replay.md section 6 | not built | Slice 4 |
Numbers that exist
None as measurements. Test-function counts from rg -c (not run): 17 (tests/test_250_model_decision_wal.py), 13 (tests/test_250_replay_modes.py), 56 (tests/test_model_evidence_517.py), 33 (tests/test_model_evidence_replay_552.py), 61 (tests/test_trace_121.py). The 552 design doc states "53 tests, all passing, no skips" for its file; they were not run for this draft.
Paper outline
- Introduction: replay is assumed, accountability is assumed; both are unmeasured. State the narrow claim.
- Threat model: prompt text as exfiltration channel, confirmation oracle on digests, shared-secret MAC limits.
- The WAL entry: Slice 3a decision entries and what they leave out.
- Sealed evidence: members, sealer-as-callable, three states, fail direction.
- Reading back: verify, reconstruct, join check, no reading on failure.
- Replay modes as a readiness lattice: requirement vocabulary, plannable vs executable.
- Causal trace of model hops: honest degrade, activation ids.
- Evaluation: tamper suite with injected bug, overhead, (if built) live executor results.
- Related work, limits.
Threats to validity and limits
- No mode is executable. The docs say "
executablealways false offline" and "Reading a decision back is not re-executing it". - The cryptographic claim is weak and the docs say so: the threat model is a shared-secret one, a holder of the key can seal any well-formed entry, there is no key management, rotation or distribution, and the construction has not had a cryptographic review.
- The provider is trusted for what only it can see: model digest, placement digest, candidate set and prompt binding are declared, not observed.
- Only the py tier writes evidence; other tiers read as "not sealed".
- Host-reported cost, tokens and model name are marked unverifiable in the trace.
- A WAL with no
model-decisionentry cannot be told from a run that made no completion. - Prompt digest across runs is meaningful only in the unsalted
content-addressmode, which is legal only for inputs with no secret or confidential origin. - The "counterfactual" mode in this paper is the planning question; the question-answering counterfactual is a separate paper (P19).
Fact-check
Claims in this draft were checked against revl main at commit 45bec431f: 55 checked, 7 corrected.
Cite this draft
@misc{revlp20,
author = {Thomas Moussajee},
title = {Replaying Agent Runs: A Write-Ahead Log That Seals Each Model Decision and States Which Replay Modes the Log Can Support},
year = {2026},
howpublished = {\url{https://inso1337.github.io/revl/research/P20.html}},
note = {Research draft, not peer reviewed. Written against revl commit 45bec431f. Licensed CC BY 4.0.}
}
Text licensed CC BY 4.0, author Thomas Moussajee.