Skip to main content
On Unix, a live cerulion graph run executes your graph as several OS processes β€” one per process group β€” running in lockstep. You get fault isolation for free: a node that crashes takes down its own group, not the whole robot. Execution is identical either way: the workers stay in step at every execution level, so the run is byte-identical to running everything in one process. Multi-process buys isolation, not latency β€” nodes fused into one process still take the fastest path between each other.

What happens by default

The derived partition is the cost-aware optimum when graphs/<name>.costs.yaml exists (written by cerulion graph profile), and otherwise a process-per-node baseline β€” maximum fault isolation. Declining is not aborting: the run proceeds with exactly the deployment a written file would have produced. Ctrl+C is how you abort.

Choose the split deliberately

Two commands turn the default into a decision you own.

Measure the graph

Runs the graph locally and writes graphs/perception.costs.yaml β€” per-node median tick cost, per-edge fire rates, and a frozen compute budget. A node that never sampled enough fires is reported as such rather than guessed at; give a slow node a target with --fires N.A profile run is a measurement, not a deployment, so it stays local-only by design.

Derive the partition

Prints the derived groups and the YAML diff without writing. Drop --dry-run to write (--yes skips the confirmation), and pass --budget-ns N to override the artifact’s frozen per-group budget.

See what the runtime sees

Prints the execution levels derived from the wiring, which nodes sit in each, and β€” when the graph declares process_groups: β€” which group covers which levels. It exits non-zero on an invalid partition, which makes it a useful CI check next to graph validate.

Writing process_groups: by hand

Each group is a list of node ids, and the groups must be a partition of the graph’s nodes β€” every node in exactly one group.
A block backpressure edge is never split across groups: deferring a producer on a consumer’s behalf only works inside one process, so a hand-written partition that splits one is refused before any process spawns, naming the fix. The derived partition co-locates the whole flow of a block topic automatically.

When a worker dies

The supervisor’s default is to keep the robot up: If you are chasing the last microseconds at level boundaries, CERULION_BARRIER_SPIN_US tunes how long a worker waits actively before it sleeps; see Environment variables.

Recording a multi-process run

Every multi-process run keeps the record of what fired when, whether or not you asked it to record β€” that is what makes a run you never planned to record still re-executable afterwards. cerulion graph run --no-rings declines that bookkeeping on a memory-tight robot; because nothing captured without it could be re-executed, such a run takes no captures at all, and --no-rings is refused together with --record.

Next steps

Record and replay a run

Turn a run into an MCAP bag and re-execute it.

Backpressure and deadlines

The block policy and the constraint it puts on partitioning.