The dependency graph
advances_to, sets_gate, and data_accumulation have no causal order among themselves, so
the engine may run them in any order; everything else follows the arrows.
YAML order is cosmetic
The order you write fields in has no effect. A handler that listson_complete before
accumulate still runs accumulate first. Re-arranging fields to “fix” behavior does nothing;
change the fields themselves.
One handler, one transaction
Every side effect of a single handler execution, the state advance, gate writes, data writes, and event persistence, commits in one database transaction. No observer sees a partial handler, and a crash mid-handler rolls the whole thing back. Because the writes commit together, their relative order is irrelevant.What overrides what
These rules interact, so read them as a unit:- Guards see pre-handler state. A guard evaluates entity state as it stood before this
handler’s writes. A
data_accumulationwrite in the same handler does not affect that handler’s own guard; it affects the next handler’s guard, after this transaction commits. - A branch or rule selects the emit site and can override the handler-level fields.
Handler-level
advances_to,sets_gate, anddata_accumulationare defaults. When anon_completebranch or a matchedrulespecifies one of them, the branch value overrides the handler-level value for that field only. rulesand handler-level fields combine, except emit. Withrules, the matched rule runs first and owns the emit. Handler-leveldata_accumulationthen runs and supplements the rule’s writes in the same transaction. But a handler-levelemitalongsiderulesis ambiguous and fails at boot: payload ownership must live on the active emit site.advances_tois skipped ifon_completealready advanced. A branch that advances the state wins; the handler-leveladvances_todoes not also fire.- Projection runs after the branch writes and before
emit.fields. Amaterialize_fromfield is written after the selected branch’sdata_accumulation, so anemit.fieldsexpression in the same handler observes the projected value. See Accumulation and projection.
When a handler stops early
- Guard fails. The handler stops and runs the guard’s
on_failaction:reject(default, marks the event rejected),discard(drop silently),kill(advance the entity to a terminal state), orescalate:{event}(emit an escalation instead). No state advance, no emit, no data writes. Guard failures are business logic, not transient, so they are not retried. - Accumulation is incomplete. The handler records the arrival and stops; the remaining steps run only when the completion condition is later met.
Failures, retries, and dead letters
A handler that throws rolls back its whole transaction. Transient errors are retried (bounded, with backoff); guard failures, validation errors, and business-logic failures are not retried and go to a dead letter. Chain-depth overflow behaves unusually. If an emit would push the event chain past the limit (default 50), the platform intercepts that downstream event before it enters the loop and writes adead_letters row for it, but the handler that emitted it still reports success and
its other side effects (state, gates, data) still commit. So a handler can succeed while the
event it meant to emit never fires. If a downstream step silently never happens, check the dead
letters for chain_depth_exceeded (see Reliability for what a
dead-lettered chain looks like).
Agents are not part of the handler transaction
Everything above is the system-node handler model. An agent’s emit is a standalone event publication: the platform validates its payload againstevents.yaml (missing required fields
fail the emit; undeclared fields fail rather than being stripped) and publishes it, but it is
not inside any node handler’s atomic boundary. Treat an agent emit as its own unit of work,
not as part of a surrounding transaction.
Handler fields
Every field a handler can declare, with its schema and constraints.
Accumulation and projection
Waiting for many events, and copying them to a typed entity field.

