Record & replay

A recording is not a video and not a log of what a test did. It is the whole event stream, appended raw, plus periodic graph keyframes — so replaying it rebuilds the identical graph through the identical reducer, and every consumer surface works on it unchanged.

What a recording is

A .sqrec file is an append-only container holding the raw protocol frames a session produced, interleaved with periodic graph keyframes so a scrubber can seek without folding from the beginning. It is a black-box recording: everything the app emitted, not a curated subset chosen at record time. That is the premise — you cannot know at record time which event will turn out to matter.

Replay feeds those frames into a normal client. Not a special replay-mode client — the client, with the same fold path a live session uses. Which is why the inspector, the analytics screen and the performance center all work on a recording without knowing they are looking at one.

Inside the container

Preamble
A SQREC magic number and a format version. Truncated tails — a crash mid-append — are tolerated on read, and unknown record types are skipped, so a file written by a newer build is diagnosable rather than a parse error.
Header
Session identity, the wall-clock anchor, the protocol version, and the surface that armed it — cli, desktop or mcp. Provenance is first-class here rather than buried in a metadata bag, because three front doors share one directory.
Frames
The raw protocol frames, verbatim. No re-encode, so there is no second schema to drift from the first one. Each carries its offset on the session-monotonic event clock, never on wall-clock — which is what makes two replays of the same file identical.
Keyframes
A faithful snapshot of the consumer graph plus the index of the last frame folded into it, cut every five seconds and at stop. A seek restores the nearest one and folds forward from exactly the next frame, which is why scrubbing a long session is instant.

Recording

Terminal
squiggle record                      # until Ctrl-Csquiggle record checkout --duration 60squiggle record -o ./repro.sqrec

The desktop’s ○ Record button in Simulator Studio arms the same recorder on the live session, and squiggle_record_start does it from an agent. All three write to .squiggle/recordings/ and stamp the producing surface into the file header, so a shared directory stays honest about who recorded what.

Recording is relay-first, like everything else — it coexists with an open Studio rather than fighting it for the port. When idle, the tap that watches for frames is a single no-op branch, so an armed recorder costs nothing until it is recording.

Replaying

Terminal
squiggle replay                         # list available recordingssquiggle replay checkout                # by namesquiggle replay ./repro.sqrec --speed 2squiggle replay checkout --assert flows/checkout.yaml

Default speed is max — there is no device to wait for, so the only reason to pace a replay is that a human is watching it.

Replay Studio

The desktop app opens the same file on a timeline: scrub (keyframe-restore then fold forward), play/pause, ×1/×2/×4/max, navigation markers, and a renders-per-second chart aligned under the track — all driving the reused inspector graph canvas rather than a second viewer written for recordings.

Its playhead is two-way scrub-synced with the Performance Center’s unified timeline: drag one and the other follows, so a frame drop in a chart and the interaction that caused it are one gesture apart.

Asserting against a recording

--assert re-runs a flow’s assertions against the recorded timeline. Action steps are skipped, because a recording is read-only. This is a genuinely different kind of check from a live run:

Two evaluation windows

Positive assertions are evaluated over the whole timeline
route, visible, exists, store and query pass if they held at any point, and the report names the first event-time where they did. So a multi-screen journey validates even though no single frame satisfies every assertion at once.
Negative and window assertions stay end-state
notVisible, rerenders and droppedFrames are evaluated at the end, because “never happened” and “happened fewer than N times” are not point-in-time questions.

${VAR} resolves from the flow’s env:, overridable with --env. The process exits non-zero on a failed assertion, so a recording can be a CI gate that needs no simulator.

Diffing two of them

squiggle diff a b answers “what changed between these two builds” with something other than a screenshot, and it needs no app running:

Terminal
squiggle diff before.sqrec after.sqrecsquiggle diff before.sqrec after.sqrec --json | jq '.renders'

It compares renders, routes, network calls, spans, actions and store mutations. Because both sides fold through the same reducer, a difference in the output is a difference in what the app did — not a difference in how two runs were observed.

What replay connects to

This is the part that is easy to miss: a recording is not a feature on its own, it is the substrate the rest of the product reuses.

Authoring
squiggle flow --from-recording derives a draft flow from a journey you drove by hand — including a comment listing the taps nothing durable identified.
Behavioural diff
squiggle diff a b compares two recordings — renders, routes, network, spans, actions, mutations — with no app running. This is how “what changed between these two builds” gets an answer that is not a screenshot.
Agent gating
Before an agent actuates anything, its program is checked against the newest relevant recording: do these assertions hold in territory we have already seen? A program aiming at a screen that does not exist is stopped at that tier, with zero live actions. If no recording covers the territory, the gate degrades to static-only and says so — it never silently passes.
Inferred contracts
What held across every green recording of a screen becomes a contract nobody wrote and nobody maintains. A violation is reported as contract_drift next to the goal, with its confidence (“held in 3 of 3” reads differently from “40 of 40”), and never as a hard failure — these are inferred, so they can be wrong.
Re-projection
squiggle reproject folds recordings back into the perf store and checks the store agrees with the canonical log. The projection is derived, so it can be rebuilt — and a mismatch is a bug worth catching.
Deterministic sessions
squiggle_deterministic_session pins the conditions that make two replays comparable, and named fixtures restore app-data preconditions, so a comparison is not confounded by the app starting from somewhere else.

Where they live

Recordings land in .squiggle/recordings/ (gitignored — they are machine-local evidence, not source), and each finished file is indexed into perf.db’s recordings table so the desktop, the CLI and an agent can list them without walking the directory.

Related: Performance reads the same store, and Agents & MCP explains the gate pyramid recordings feed.