> ## 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.

# Execution order

> See how your graph determines node order, trigger timing, process layout, and replay behavior.

Cerulion reads your graph file's topology and each node's trigger policy before
a run; the graph determines execution order rather than the OS scheduler.
That gives you a user-visible execution plan: which nodes share a level, which
topics trigger the next level, and which process group owns each level band.

## See the order your graph derives

Levelization follows the graph's triggering edges. A camera publishes an image,
the detector fires when that image arrives, and a downstream node runs after
the detector's output reaches its trigger input.

```mermaid theme={null}
flowchart LR
  Camera[Camera] -->|sensor_msgs/Image| Detector[Detector]
  Detector -->|detections| Planner[Planner]
```

*Figure: Triggering topic edges carry a camera frame through the graph in a derived order.*

Run the read-only inspection command from your workspace:

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

The command prints the graph identity, the levelization source, a node and level
count, one line per level with each node's trigger policy, and triggering topic
edges leaving each level:

```text theme={null}
graph: perception  prefix: robot1
levels source: derived (trigger-aware Kahn levelization)
nodes: 3  levels: 3
level 0: camera [period(33ms)]
  -> /robot1/camera/image  camera -> detector (level 1)
level 1: detector [data(image)]
  -> /robot1/detector/detections  detector -> planner (level 2)
level 2: planner [data(detections)]
```

The graph file supplies the `prefix:` value; if it is omitted, Cerulion derives
it from the hostname, so `robot1` and the topic names in this sample are
illustrative.

If the graph contains
`process_groups:`, the report also prints the number of groups, each group's
rank, its owned level band, its node members, and a partition verdict.

## Trigger policies choose when nodes fire

Level order tells you where a node sits. Its trigger policy tells Cerulion when
that node fires:

* **Period** — fires every N milliseconds, configured with
  `#[cerulion_node(period_ms = N)]` or `--policy period_ms=N`.
* **Data** — fires when data arrives on its trigger input, configured with
  `data_trigger=NAME` or a `#[input(trigger)]` port.
* **Sync** — waits for a complete aligned set of trigger inputs within a
  `sync_window_ms=N` window.
* **External** — represents a self-triggering ingress or driver node, such as a
  device file descriptor or blocking source. The node supplies an
  `external_source()` implementation.

Trigger policy belongs to the node type, not the graph YAML. The graph supplies
the wiring that connects the policy's inputs and outputs. See
[trigger policies](/cerulion/reference/trigger-policies) for the complete
grammar.

## Multi-process is the Unix default

Under the real clock on Unix, an unpartitioned graph derives a process
partition by default. Cerulion runs one worker process per derived group in
barrier lockstep. When a cost snapshot is available, the partition is
cost-aware; otherwise, the baseline is process-per-node. A graph with an
existing `process_groups:` block uses those declared groups.

Use `--single-process` to force the monolith instead:

```bash theme={null}
cerulion graph run perception --single-process
```

Use the graph tools to inspect or prepare the layout:

```bash theme={null}
cerulion graph levels perception
cerulion graph profile perception
cerulion graph partition perception --dry-run
```

* `graph levels` shows the derived levels and trigger edges.
* `graph profile` measures a live graph and writes a cost snapshot.
* `graph partition --dry-run` previews the process groups without rewriting
  the graph YAML.

## Choose the clock for the job

`graph run` uses the real clock by default. It is the live, event-driven path:
the runtime wakes when a publish arrives.

Use the virtual clock for deterministic `graph run` executions and
benchmarking. It uses a deterministic clock with a 1 ms poll loop rather than
waiting on live time.

Bag resimulation does not use this selection: `--resim all` runs on the
recording's own clock.

The external clock is accepted by the CLI but inert today: it starts at 0 and
does not advance, so time-based triggers never fire. It parses as
`--time-source external`; no external time feeder is wired yet.

<Warning>
  An `external` trigger policy and `--time-source external` are different
  controls. The policy describes a node's ingress source; the time-source flag
  selects the clock for the graph run.
</Warning>

## Why this makes replay byte-comparable

The graph supplies the same topology, trigger policies, and execution order
each time. A recorded run can re-execute your nodes against its bag with
`--resim all --verify`; verification compares every produced frame
byte-for-byte with the recording.

That is why you can use the real clock for a live robot, the virtual clock for
deterministic `graph run` executions and benchmarking, and the recording's own
clock for byte-comparable resimulation without changing the graph's execution
contract. For the complete workflow, see
[record and replay a run](/cerulion/guides/record-and-replay).

## Next steps

<CardGroup cols={2}>
  <Card title="Wire and run a graph" icon="diagram-project" href="/cerulion/guides/wire-and-run-a-graph" color="#0080FF">
    Stage node instances, wire inputs, validate, and run.
  </Card>

  <Card title="Run across processes" icon="server" href="/cerulion/guides/run-multi-process" color="#0080FF">
    Declare process groups, inspect partitions, and choose the monolith when needed.
  </Card>

  <Card title="Record and replay" icon="circle-dot" href="/cerulion/guides/record-and-replay" color="#0080FF">
    Record a run and verify a byte-comparable re-execution.
  </Card>
</CardGroup>
