The five nouns
Workspace
The project container: a Cargo workspace with
graphs/, nodes/, and
schemas/ directories.Node
A unit of computation: a Rust struct annotated with
#[cerulion_node],
compiled to a dynamic library.Graph
A
.yaml file that wires node instances together. Topology only.Topic
A named shared-memory channel, created automatically from your graph wiring.
Schema
The shape of a message: a ROS 2 message type or a workspace-defined schema.
Workspace
A workspace is the project container for everything you build. It is a Cargo workspace plus three Cerulion directories. You create one withcerulion workspace create <name> (or cerulion workspace init in place), and
most commands work by discovering the workspace around your current directory.
my_robot
Cargo.toml
.cargo
graphs
perception.yaml
nodes
camera
detector
schemas
Cargo.toml declares a [workspace] with members = ["nodes/*"],
so every node is its own crate built by the same cargo invocation. Each
directory has one job:
graphs/: one<name>.yamlper graph (the wiring).nodes/: one crate per node type (the code).schemas/: one<Name>.yamlper workspace-defined message shape.
Cerulion discovers a workspace by walking upward for a
Cargo.toml that
contains [workspace] alongside a graphs/ directory. Most node and graph
commands need this; workspace create/init and the topic commands do not.Node
A node is a unit of computation: it reads inputs, does work each time it fires, and writes outputs. You define one as a Rust struct annotated with#[cerulion_node], paired with an adjacent impl block whose tick() method
is annotated with #[cerulion_node_impl].
#[input(...)] fields are subscriptions,
#[output] fields are publications. Inside tick(), reading and writing those
fields reads and writes shared memory directly; there is no separate publish or
subscribe call to make. Every node is imported from cerulion_core::prelude and
compiled to a dynamic library (cdylib) so the runtime can load it at run time.
For the full attribute set, see the node macro reference.
Node type vs. node instance
This distinction is the heart of Cerulion’s model.- A node type is the code: the crate under
nodes/<type>/, defined once by the#[cerulion_node]macro. The type declares its ports and its behavior. - A node instance is a use of that type inside a graph: an entry with a
unique
idundernodes:in a graph.yaml. You can stage the same type into a graph more than once, each with its ownidand its own wiring.
Trigger policy lives on the type, not the graph
A node’s trigger policy decides when it fires: every N milliseconds, when data arrives on a trigger input, when several trigger inputs line up in a time window, or on an external command. This policy is part of the node type. You set it with the#[cerulion_node(...)] macro attributes in src/lib.rs (and, at
scaffold time, with the --policy flag on cerulion node create).
This is deliberate: behavior lives in one place (the code), and the graph stays a
pure description of how instances connect. See
trigger policies for the full grammar and
the defaulting rules.
Graph
A graph is a.yaml file under graphs/ that wires node instances together.
It is the source of truth for topology: which instances exist, and how each
instance’s inputs connect to other instances’ outputs.
nodes: is an instance: id is its unique name, type is the
node-type folder it comes from, and inputs/outputs declare its ports. An
input’s source is written <node_id>/<output_name>. Here, detector’s
image input reads camera’s image output. The optional prefix namespaces
the topics this graph creates; if you omit it, Cerulion resolves it to the host
name at load time.
You run a graph with cerulion graph run <name>, which loads each instance’s
compiled library, wires the topics, and drives execution. See
wire and run a graph for the workflow.
Topic
A topic is a named channel that carries messages from a publisher to its subscribers over shared memory. You do not create topics by hand; Cerulion derives them from your graph wiring. Whendetector’s image input reads
camera/image, the runtime sets up the underlying shared-memory channel for you.
Topics are how you observe a running system from the outside. Because discovery
happens through the shared-memory transport, the topic commands work without a
workspace:
cerulion topic list: show active topics.cerulion topic echo <topic>: print messages as they arrive.cerulion topic hz <topic>: measure the publish rate.

Schema
A schema is the shape of a message: its fields and their types. A schema gives a topic a known layout so publishers and subscribers agree on what the bytes mean. Cerulion gives you two sources of schemas:- ROS 2 message types from the
native_ros2_messagescrate, for examplesensor_msgs/Imageorgeometry_msgs/Vector3. Import them withuse native_ros2_messages::<package>::<Type>;. These cover the common robotics message families out of the box. - Workspace schemas you define yourself under
schemas/<Name>.yaml, created withcerulion schema create <name>, for message shapes specific to your project.
How it runs
Putting the nouns together: you write node types, wire instances of them in a graph, and run the graph. The graph determines the topology; each node’s trigger policy determines when it fires; the runtime moves messages between instances over topics carrying typed schemas. Two properties of that runtime are worth understanding as benefits, even though you never configure them directly.Zero-copy

Determinism

Next steps
Why Cerulion
How Cerulion compares to ROS 2 and the performance story behind zero-copy.
Define a node
Create a node type, add ports, choose a trigger policy, and write
tick().Wire and run a graph
Stage instances, wire inputs, validate, and run.
CLI reference
Every command and flag, in one place.