mix continuum.replay answers "what does this run do next, against the code on this node?" without running it. Nothing is written: the run keeps its lease, its history, and its pending signals.

Reach for it when a run is wedged and you want to know where it stopped, when a run failed with a Continuum.ReplayDriftError and you want the exact cursor, or before a deploy when you want to know what a code change would do to runs already in flight.

mix continuum.replay RUN_ID --repo MyApp.Repo
mix continuum.replay RUN_ID --repo MyApp.Repo --format json
mix continuum.replay RUN_ID --repo MyApp.Repo --no-snapshot
mix continuum.replay RUN_ID --repo MyApp.Repo --against MyApp.Orders.Checkout
mix continuum.replay RUN_ID --repo MyApp.Repo --strict

The outcome is one of four:

OutcomeMeaning
completedThe history carries the workflow to a return value. The report says whether it agrees with the stored terminal result.
suspendedThe workflow stops at a pending effect. A history_exhausted reason names the cursor and the effect it would have performed next.
continuedThe workflow tail-calls continue_as_new/1.
driftCode and history disagree, at a named cursor, with the journaled event on one side and the effect the code asked for on the other.

A version this node cannot load is reported as "this node cannot replay this version", not as drift — the mismatch is in the deploy, not in the run. Deploy the release that ran it, or use --against to replay through a module you do have.

--against is how you rehearse a code change against a run already in flight: replay a suspended run through the module you are about to deploy and see whether it drifts before the deploy makes that everyone's problem.

--no-snapshot ignores stored snapshots and replays from events alone, which is how you confirm that a snapshot and the events it compacted still agree.

Why replay takes a journal adapter, not a flag

Replay is read-only structurally: Continuum.Replay hands the context Continuum.Runtime.Journal.ReadOnly, which raises on every callback, and Continuum.Runtime.Effect refuses to compute a live tail against it. Both stock adapters mutate on the path the code calls replay — the in-memory one executes a real activity body inline once the workflow steps past the journaled tail, and the Postgres one resolves a tail signal_awaited by consuming a pending signal row inside a transaction. Effect tells them apart by journal module identity, so a distinct module is what closes those routes; a flag on Postgres would not.

Continuum.Replay.run/4 is the same kernel for a history you already hold, and Continuum.Test.replay/4 delegates to it.