Replay & bisect
Re-run a session's prompts against a live model or a recorded cassette, and binary-search for where behaviour diverged.
On this page
Two different tools, one input
arcturn replay and arcturn bisect both start from the same thing — the ordered list of
user prompts pulled out of a stored session by extractPrompts — but they answer
different questions. Replay asks "what happens if I run this again, maybe on a different
model?" Bisect asks "at which turn did behaviour stop matching a known-good recording?"
Neither is hermetic on its own; only bisect, backed by a cassette, is byte-identical.
Both skip steering messages — text injected mid-run via Agent.steer() — since those
aren't original prompts a user submitted while the agent was idle; extractPrompts
distinguishes them structurally (a steering message's parent entry is a tool-result
message, which an original prompt's parent never is) rather than guessing from content.
arcturn replay
arcturn replay <sessionId|file> [--model <id>] [--cwd <dir>]Replay is live: it re-runs the session's prompts against a real provider, one at a
time, in order. --model swaps which one — the whole point is comparing a session's
outcome across models or a config change, not reproducing the original run byte for byte.
Progress goes to stderr; results are one JSON object per turn on stdout, so a replay pipes
straight into jq or a diff without any cleanup:
$ arcturn replay 019c4a2f --model openai/gpt-5.1
arcturn: replaying 6 prompts on GPT-5.1
arcturn: [1/6] rate-limit the login route
{"prompt":"rate-limit the login route","finalText":"Added a 5-req…","toolCalls":["read","edit"],"costUsd":0.0412}
arcturn: [2/6] add a test for the limiter
{"prompt":"add a test for the limiter","finalText":"Added auth.test…","toolCalls":["read","write"],"costUsd":0.0388}
arcturn: replay total $0.2317Each turn's JSON line carries prompt, finalText, toolCalls (call order), costUsd,
and an error field when that turn errored or was aborted — a bad turn doesn't stop the
replay; it's recorded and the next prompt still runs. The command's own exit code is
non-zero if any turn errored.
Because it's live, replay is the tool for cross-model comparison and regression
testing against a real provider — not for reproducing exactly what happened. Two runs
of the same session against the same model can still diverge (temperature, provider-side
changes, tool results that depend on the current state of the world). diffReplays
compares two ReplayResults turn by turn — tool-call sequence match, final-text length
divergence (>20% or empty↔non-empty counts as diverged), and cost delta — useful for
scripting "did this change anything" checks around a replay pair.
Cassettes: the hermetic path
The byte-identical alternative lives in vcr.ts, not in replay. A cassette is a
JSONL recording of everything arcturn doesn't control during a run: every LLM stream event
and every tool result, keyed by a content hash of the request/input that produced it (not
by position) so the recording survives being replayed against a slightly different call
order. Replaying against a cassette touches neither the network nor the real filesystem —
a recorded session that ran rm -rf deletes nothing on replay.
There is no CLI command to record one today. Recording (createCassetteRecorder,
recordingClient, wrapToolsWithRecorder) is SDK-level machinery in vcr.ts, wired up
by a host application, not exposed as an arcturn subcommand or flag. Cassettes are
consumed by arcturn bisect --cassette <file> and by anything you build against vcr.ts
directly — not produced by arcturn itself out of the box.
arcturn bisect
arcturn bisect <session> --cassette <file> [--model <id>] [--cwd <dir>]Bisect binary-searches the session's prompts, replaying prefixes of them against a freshly loaded copy of the cassette for each probe (cassette consumption is stateful, so reusing one loaded cassette across probes would make every probe after the first see an already-partially-consumed recording). Each probe is classified:
- the replay throws a cassette miss →
"bad"— the agent asked for something the recording never produced, the turn-level signature of "behaviour diverged here." - the cassette load or replay hits corruption →
"skip"— undecidable, not good or bad. - the replay completes clean with zero misses →
"good".
$ arcturn bisect 019c4a2f --cassette run.jsonl
arcturn bisect: behaviour first diverges at turn 4 — refactor the token parser.
confident: yes (first divergence at turn 4)
Probe trail (4 probe(s)):
turn 0: good
turn 2: good
turn 3: good
turn 4: badExit code is 0 when no divergence was found, 1 when one was.
The search assumes badness is monotonic — once a turn is bad, every turn after it is
too, the same assumption git bisect makes. A "skip" doesn't count as good or bad; the
search steps outward to the nearest decidable neighbour instead of guessing. A probe
budget (default 64 calls, including outward-stepping and verification) bounds the total
cost, since a real probe means spinning up a runtime and replaying a cassette. Pass
{ verify: true } to bisectTurns (not yet a CLI flag) to re-probe the two turns
straddling the discovered boundary and catch a non-monotonic run rather than silently
trusting it.
Read confident before you read the answer. confident: no means the search ran out
of probe budget, hit a window where every nearby probe returned "skip", or (with
verify) caught a contradiction with monotonicity — firstBadIndex may still be printed
as the best available answer, but it isn't guaranteed correct. Bisecting over a smaller,
cleaner reproduction — or falling back to a linear scan — is the honest move when that
happens, not trusting the number.
Limits and blind spots
- Replay is not hermetic. Two live replays of the same session can print different
finalTexteven on the same model. Only the cassette path is byte-identical. Provider-side model updates, tool results tied to real time/state, and sampling variance are all real sources of driftreplaycannot control for. Reach forarcturn bisect --cassettewhen you need a deterministic answer, and reach forarcturn replay --modelwhen you're deliberately comparing outcomes, not reproducing one. - Bisect can only find a divergence that already has a cassette to diverge from. There's no built-in way to record one from the CLI — see above.
extractPromptssends text only; image content blocks in an original prompt are dropped on replay.
Related
- Sessions, branching & compaction —
extractPromptswalks the same entry list this page's tools are built on. - Provenance & arcturn blame — attributes the lines a replayed prompt would have produced, in the original run.
- Audit & cost — replay reports cost per turn using the same
accounting
/costshows live. - The accountability feature page has both commands' exact output, including the bisect probe trail shown above.