Skip to main content
A run’s behavior depends only on its script, its input and what the outside world answered. So a script is tested by answering its calls with mocks: no tool is called, and time is virtual. The complete program of this page is in examples/testing.

Type-check

A script with only warnings passes. runs.start checks the script anyway, unless you pass check: false.

Scenarios

A scenario runs a script in mock mode: its calls are answered by mocks, its signals are sent on a schedule, and time is virtual, so a 3-day timeout or a 10-day sleep passes at once. It reports a trace and the expectations that do not hold.
Record<string, unknown>
Static responses per tool: the result, or { "$error": "message" } for a failure.
string
TypeScript mock handlers, run in an isolate of their own so they cannot break the script’s determinism. The module default-exports its handlers by full tool name or by namespace; each receives the arguments and { tool, runId, seq, attempt, idempotencyKey }, and returns the result or throws.
object
The calls and signals of a real run (below). Tried first, then the mock handlers, then the static responses.
{ name, payload?, when? }[]
Signals sent during the run, in order. when is "blocked" (the default: once the run can advance no further without the outside world), or a duration of virtual time after the start, such as "2h".
Expectations
What must be true at the end; what is not given is not checked. status; output, which the output must contain (objects compared key by key); error, the name of the error and part of its message; calls, the number of calls per tool: list every tool the script calls.
Mock handlers
A run that waits for a signal with a timeout is never blocked: virtual time moves straight to the timeout. Give such a signal a when shorter than the timeout, like "1h" above; with the default "blocked", it would arrive after the timeout fired.

Test against a real run

A run’s calls and signals, exported as a cassette, answer each call with what the real tool answered. Record a cassette from a run that went well, and test the next version of the script against it:
The model does the same with the agent tool test_script and scenario.from_run.

Fix a failed run

A fork re-executes a corrected script against a failed run’s history: while it makes the same calls, their recorded results are reused, then it continues live. In the example, the first version of the script fails on an issue without labels, after it posted to Slack. The fork does not post again:

In CI, without a server

The rewind CLI runs scenarios and replays in its own process, without a server. A scenario file holds the same fields, with paths instead of contents:
triage.scenario.json
See the CLI.