Skip to main content
Cerulion recordings are standard MCAP files. One recording is one .mcap file, uncompressed, carrying every frame exactly as it was published — wire sequence and timestamp included. There are three ways to get one, and they answer different questions:

Record a run

Add --record to the run. The bare flag writes into ./recordings/; a custom directory needs the = form.
The recording keeps the network on — the robot stays viewable while you record — and finalizes on shutdown, including when the run is stopped with Ctrl+C. Recording requires the live clock, so it cannot be combined with --time-source virtual or external, and it is refused together with --no-rings.
A recorded run captures the graph, the environment, and the run’s own record of what fired when. That is what makes it re-executable later — see Re-execute your nodes.
The recorder also picks up live producers the graph never declared and records them alongside your topics. Those extra channels are not replay-grade — the bag says so when it is created, cerulion bag info names anything the recorder could not cover, and a re-execution skips them instead of verifying them. Set CERULION_RECORD_DISCOVERY=off to record only the graph’s declared topics. By default the environment is captured as names for everything and values only for Cerulion’s own variables, so a bag never leaks a secret you happened to export. --record-env all captures every value verbatim for full replay fidelity, and warns loudly that the bag now embeds them.

Record topics that are already live

cerulion bag record taps this machine’s shared memory. It never opens the network, so recording a robot’s topics means running it on the robot and copying the file afterwards.
Exactly one selection source is allowed — combining a positional list with -a or -e is an error, because the merge you would assume (“union? intersection?”) is not knowable. Narrow with -x instead. To describe a run rather than a topic set, attach to a live one:
The bag then carries that run’s effective graph, environment, and recorder manifests, and picks up its scheduler trace from the attach point. A bare --run means the sole live run; naming one needs the = form, since every word after a bare --run is read as a topic.

Capture the recent past

Every serving graph on Unix already holds a rolling window of roughly the last 30 seconds in memory. cerulion flashback writes that window out — plus the following 15 seconds — as an ordinary bag. Nothing has to be armed in advance.
Captures land in recordings/flashbacks/. Add --pin to keep one out of routine retention, and --no-wait to return as soon as the request is accepted instead of waiting for the file. CERULION_FLASHBACK=off turns the capture plane off entirely.

Inspect a bag

state: finalized is the one to read first: playback needs the summary footer that a clean recorder shutdown writes, so a bag from a killed recorder is refused by bag play while bag info still reports what it can read.

Play a bag back

Playback republishes the recorded frames onto shared memory, so everything downstream — topic echo, topic hz, Cerulion Studio, a consumer node — sees what a live robot would.
Two behaviors are worth knowing before you interpret a playback:
  • A topic that a live producer already owns is refused by name, and the rest of the bag still plays. Topics are single-writer, so a player must not compete with a live producer.
  • Each topic keeps its own recorded rate. Stamps from different producers share no number line, so playback runs one timeline per topic, anchored on that topic’s own first frame.

Re-execute your nodes

--resim is the other half of the verb: instead of republishing recorded frames, it re-executes the bagged graph’s nodes against the recorded input, on the recording’s own clock.
--resim all re-executes every node; a node subset is not supported yet and is refused by name rather than quietly widened. A bare --resim makes no claim about whether the result matches the recording — divergence is the product, so a completed re-execution exits 0. --verify is the verdict: it byte-compares every produced frame and exits non-zero on divergence. Add --report PATH for a machine-readable verdict, --tolerance PATH for fields that may legitimately differ, and --strict-state to refuse the run unless every node’s state was restored.
A passing --verify is a strong statement: your current code, re-run against real recorded input, produced the same bytes the robot produced. That is the property to put in CI.
Two things will make a re-execution refuse or produce nothing, and both are by design:
  • Environment divergence is reported, not ignored. A variable that was present at record time and absent at replay is a warning naming the variable; the run continues.
  • A bag recorded with bag record is not replay-grade. It carries frames, not the graph and trace a re-execution needs. Use graph run --record for a bag you intend to re-execute, and cerulion bag migrate to bring an older replay-grade bag forward.

Next steps

Visualize a running graph

Watch a live run, or a bag playing back, on the Studio stage.

Run a graph across processes

The default deployment shape, and what it means for recordings.