What the journal records
A run’s journal is the ordered list of everything that crossed the sandbox’s walls:
Each entry carries its callsite, the line of the script that caused it, which is how the dashboard puts each call
beside its line. Values (arguments, results, signals, the input and output) are identified by the SHA-256 of their
canonical JSON; values above 64 KiB are stored apart, in S3.
The journal is the only source of truth. The dashboard, the OpenTelemetry traces, the metrics and the webhooks
are projections of it. No feature has a side channel.
Replay
What runs inside the sandbox is computed again; what came from outside is read from the journal. To rebuild a run, a worker starts the script from the beginning with the run’s input and seed. Eachawait that has a recorded outcome
returns it immediately. When the script reaches the end of the journal, it continues live.
This is how a run survives a crash, a deploy, or ten days of sleep: nothing but the journal is needed. Workers keep
recently active runs in memory, so most activations need no replay at all; replay is what makes losing that memory
harmless.
It also means that any run can be re-executed, anywhere, with the same result:
rewind replay <run>re-executes a run locally and checks that it matches its journal.rewind replay <run> --script fixed.tstells whether a corrected script is compatible with that history, and if not, where it diverges.
Forks: fix a run without repeating its effects
A fork is a new run started from an existing one with a corrected script. Rewind replays the new script against the parent’s history: while its calls match the parent’s, the recorded results are reused and no tool is called again. At the first difference, or at the end of the parent’s history, the fork continues live. An agent fixes step 7 of a failed run without redoing the side effects of steps 1 to 6. The model does it with the agent toolretry_run; your code with runs.fork.
Cassettes: tests from real runs
A run’s calls and signals, exported as a cassette, become the mocks of a test. A test against a cassette answers each tool call with what the real tool answered, in virtual time, without calling anything. See Testing.Logical time
Inside a script,Date.now() returns the time of the last event delivered to the run: its start, a tool’s result, a
timer, a signal. Time only moves when the outside world speaks, and it moves identically on replay. After
await sleep("10d"), Date.now() is at least ten days after the start.
In tests (mock mode), time is virtual: the clock jumps straight to the next timer, so a ten-day workflow runs in
milliseconds.
Determinism epochs
A replay only works if the script computes exactly what it computed the first time. The script cannot be non-deterministic: the sandbox controls time, randomness and every input. What can change is Rewind itself: a new V8, a new version of therewind library, a new sandbox global.
These components are versioned together by one number, the determinism epoch, recorded in each run. A build of
Rewind keeps the versions of every epoch that may still have live runs, and CI replays a corpus of recorded journals
for each one.
If a replay ever diverges from its journal, that is a fault of the platform, never of the script. The run is not
failed: it is quarantined, and an operator releases it after a fix, or gives up on it. Quarantined runs appear on
the Overview of the dashboard.
How a run ends
A run never ends while a tool call is in flight: a forgotten
await still lets the call finish and records its
outcome. So when a run ends by itself, every call it made has a recorded outcome, which is what lets a fork know
which effects happened.