nodes.yaml declares the deterministic system nodes that orchestrate a flow. Each node is a
map entry keyed by its id.
Node fields
object
required
A map of event name to handler declaration — the only required field. See
Handler fields for every field a handler can declare.
string
Derived from the map key; an explicit
id is accepted only when it equals the map key.string
Derived (always
system_node); an explicit value is accepted for compatibility and carries
no information.list
Derived from the
event_handlers keys; an explicit list is accepted but adds nothing.
Subscriptions never cross a flow boundary — qualified forms
(<flow>/<event>) are retired and fail verify; cross-flow delivery is owned by the
package connect block.list
Derived from the handler emit sites; an explicit list is accepted but the analyzer warns on
drift (
produces_drift).object
Per-node state and accumulator fields, keyed by entity.
string
The state table name for this node.
object
Gate field definitions.
list
Durable timers attached to this node (see below).
list
Node permissions, for example
create_flow_instance.single_node_per_event); two nodes on the
same event is a boot error.
Timers
A timer is a durable time-based trigger declared on a system node. When the delay elapses the platform emits the timer’s event, which routes through the normal event loop. Timers are persisted, so they survive restarts: at boot the platform checks for any whosefire_at has
passed and fires them.
Fields
string
required
Timer identifier, unique within the node.
string
required
Event name emitted when the timer fires. A handler somewhere in the flow must subscribe
to it, or the timer fires into a dead end.
string
required
Duration string (
72h, 30m, 7d) or a policy reference ({{policy.sla_timeout_hours}}h).
How long after start_on the timer waits before emitting.string
required
When the timer begins counting. Must be one of three canonical forms; see below.
string
Optional. When the timer is cancelled before firing. Canonical forms:
state:<name> or
event:<name> (boot is invalid here). If omitted, the timer can only stop by firing or
by entity-terminal cleanup.boolean
default:"false"
When
true, the timer re-fires at every delay interval until cancelled or until the
entity reaches a terminal state. When false (default), the timer is one-shot.start_on / cancel_on canonical forms
Trigger values must be prefixed. Bare strings are boot errors: the platform does not infer state-vs-event intent. Unknown referenced states are boot errors; unknown referenced events are boot warnings.Lifecycle
- Start. Timer begins when its
start_oncondition fires (entity enters the state, the event is delivered, or the platform boots). Platform persists(entity_id, timer_id, fire_at)to the timer store. - Cancel. If
cancel_onfires beforefire_atis reached, the timer is cancelled and removed from the store. The entity reaching a terminal state also cancels active timers. - Fire. When wall-clock reaches
fire_at, the platform emits the configured event into the normal event loop. The event is routed by its declared subscribers like any other event. - Recurring. If
recurring: true, the timer re-arms for the next interval after firing. - Crash recovery. Timers are durable. On restart, the platform checks the store and
fires any whose
fire_atis past, then resumes counting for the rest.
event_id, run_id, entity_id) but no
business payload; handlers read entity for context.
Examples
A one-shot SLA breach timer:Example
nodes.yaml

