> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cerulion.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a graph across processes

> How Cerulion splits a graph into worker processes by default, how to declare or derive process groups, and what happens when a worker dies.

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

```bash theme={null}
cerulion graph run perception
```

| Situation                              | What runs                                                   |
| -------------------------------------- | ----------------------------------------------------------- |
| The graph declares `process_groups:`   | Exactly those groups, no derivation, no prompt.             |
| No `process_groups:`, Unix, live clock | Cerulion **derives** a partition and runs it multi-process. |
| `--single-process`                     | One process — the opt-out, available anywhere.              |
| `--time-source virtual` or `external`  | One process.                                                |
| Non-Unix host                          | One process, with a loud notice.                            |

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.

### Persisting a derived partition needs your consent

| How you ran it                | The graph file                          | The run                                              |
| ----------------------------- | --------------------------------------- | ---------------------------------------------------- |
| `--yes`                       | Written, with a `.bak` backup beside it | Multi-process, derived groups                        |
| Interactive, you answered `y` | Written, same                           | Multi-process, derived groups                        |
| Interactive, you answered `N` | Untouched                               | Still multi-process, groups held in memory           |
| Non-interactive, no `--yes`   | Untouched                               | Still multi-process, groups in memory, with a notice |

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.

<Steps>
  <Step title="Measure the graph" icon="gauge-high">
    ```bash theme={null}
    cerulion graph profile perception --duration 20
    ```

    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.
  </Step>

  <Step title="Derive the partition" icon="scissors">
    ```bash theme={null}
    cerulion graph partition perception --dry-run
    ```

    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.
  </Step>

  <Step title="See what the runtime sees" icon="bars-staggered">
    ```bash theme={null}
    cerulion graph levels perception
    ```

    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`.
  </Step>
</Steps>

## Writing `process_groups:` by hand

```yaml theme={null}
process_groups:
  - [camera, rectifier]
  - [detector]
  - [logger]
```

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.

<Warning>
  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.
</Warning>

## When a worker dies

The supervisor's default is to keep the robot up:

| `--peer-loss`        | Behavior                                                                                            |
| -------------------- | --------------------------------------------------------------------------------------------------- |
| `continue` (default) | The dead group is dropped and the survivors continue — degraded, and loudly.                        |
| `fail`               | The whole deployment stops. Use it in CI and replay runs, where a partial run is worse than no run. |

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](/cerulion/reference/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

<CardGroup cols={2}>
  <Card title="Record and replay a run" icon="circle-dot" href="/cerulion/guides/record-and-replay" color="#0080FF">
    Turn a run into an MCAP bag and re-execute it.
  </Card>

  <Card title="Backpressure and deadlines" icon="gauge-high" href="/cerulion/guides/backpressure-and-deadlines" color="#0080FF">
    The `block` policy and the constraint it puts on partitioning.
  </Card>
</CardGroup>
