How a graph runs
The engine is event-driven. It does not queue the nodes in advance and it does not read the canvas left to right. It starts exactly one node itself — entry (inside a composite body, that body’s input port). Everything else is started by an incoming edge.
An edge is the trigger
Different wires do different things:
- exec — order only: it triggers the next node without carrying a value;
- execute_data — trigger and value on one edge, the most common case;
- data — value only: the target reads it, but the edge does not start it;
- link_llm / link_memory / link_extension — a reference to a configuration (a model, memory, a tool); it has nothing to do with execution order.
The consequence that matters: a node no exec or execute_data edge reaches never
runs, however many templates point at it. Writing {{ nodes.<id>.output }} reads a
value — it does not start the node.
Merging branches: everything is awaited by default
A node with several incoming dependencies runs once all of them have fired — a plain AND-join. Parallel branches behave the same way: independent chains run at the same time, and how many nodes may run concurrently is a plan limit.
Branching kills edges
if and switch pick exactly one leg. The legs not taken are marked dead — nothing will ever arrive along them. A node whose inputs are all dead becomes dead itself and passes that on, which is how a whole branch is skipped. Why a particular node never ran is shown on its card in the run — see statuses and skip reasons.
OR-join: one input fired, the rest are dead
When both legs of an if converge on one node, waiting for “all of them” is
pointless: the second input will never arrive. Such a node runs as soon as at
least one input has fired and every remaining one is dead.
- Execute + Data
The node runs once and receives the value of the branch that actually fired;
the other branches’ values are simply absent. The validator says so with the
wire.andjoin_partial_payload warning — advice rather than an error, because this
is usually the intent.
A barrier for branches that run together
If the branches do not exclude each other and genuinely run in parallel, they must
not share one input handle: they race for it, and the value that arrives last
wins. The validator rejects that with wire.fanin_needs_barrier.
The correct shape is wait_all: every branch goes into
its barrier_in handle, and the graph continues from its output.
- Execute + Data
The barrier waits for every input to be resolved: either it fires, or it turns
out to be dead. A skipped switch branch therefore does not block it. How the
values are combined is decided by merge_mode (first, last, merge).
A pure data leg
A data edge carries a value but does not start the node. There is one exception:
when the target has no other scheduler dependency at all and the source is
itself driven, the engine lifts that edge into the scheduler — and the target runs
right after its source. This is what makes the most natural split-port shape work:
code_javascript[data] → exit.
When there is nothing to lift and the exit is not reachable over any exec path, the
run would finish with an empty result. The validator rejects such a graph with
exec.exit_no_exec_trigger before it starts.
A failure in one branch
A failed node does not cancel the other branches — they play out to the end. What
happens to its own continuation depends on whether an on_error branch is wired;
the run’s status is decided after every branch has stopped.