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.

If / Else
LLM Response
Template
Exit
  • Execute + Data
The branches exclude each other: one fires, and the exit starts on the OR-join.

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.

LLM Response
HTTP Request
Wait all
Exit
  • Execute + Data
Parallel branches are merged by a barrier, not by a shared input port.

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.