How the Graph Becomes a Run
For n8n, the important execution question is not simply which box comes after which. It is how a saved workflow becomes a particular run with a valid start, usable inputs, branch-aware data, and a record of what happened. This chapter follows that transition from graph representation to execution stack, so the reader can reason about joins, partial runs, retries, and waiting without treating the canvas as a topological drawing that the server merely replays.
A workflow is a graph with two directions
A workflow starts as a collection of node instances and connections, but n8n turns that collection into a runtime object with several kinds of state. Each node has a name, type, version, parameters, and optional credentials. The node type supplies the description that tells n8n what the node accepts and returns, as well as the method that performs an operation, receives a webhook, polls for data, or supplies an AI-related value. The workflow also has settings, static data, an active state, and—when relevant—pinned data for manual work.
When the runtime constructs this object, it resolves known node types and applies their default parameter values. That step is easy to overlook because it is not visible as a new box on the canvas. It is what allows a saved workflow to be interpreted consistently even when a user left an optional property implicit. Unknown node types are not immediately fatal during construction; the workflow can still be formed so that expressions or unrelated portions do not fail merely because an unused node cannot be resolved. Readiness checks later identify unresolved types when they are on the path being executed. The distinction between “the graph can be loaded” and “this path is executable” is another reason the runtime object matters.
Connections are more than a line between two boxes. They identify a destination node, a connection type, and an input index. The Workflow object retains connections by source and derives the inverse view by destination. The source view answers, “Which nodes can become ready after this output?” The destination view answers, “Which upstream nodes must be considered before this node can run?” Keeping both views is a small design choice with large consequences: scheduling can move forward, while readiness, expression lookup, and partial execution often need to move backward without repeatedly reconstructing the graph.
The distinction becomes visible when a reader asks to run only one action at the end of a large workflow. A destination-limited run does not need to execute every leaf that shares an earlier parent. n8n can identify the destination and its relevant ancestors, include the non-main parents needed by AI tools, filter out unrelated nodes, and start from an appropriate trigger or already available parent data. Partial execution then cleans stale run data, handles disabled nodes and cycles, and recreates an execution stack for the selected subgraph. The result is not a different workflow; it is a constrained run of the same graph.
A graph may also have a trigger or poll node that has no ordinary predecessor. n8n can query node types for those roles and choose a start node, while a destination-specific run can first walk upward to find the highest usable ancestors. This is a graph judgment rather than a coordinate judgment: the node at the top-left of the editor is not necessarily the node that supplies the first runtime input. Disabled nodes are skipped when the engine builds that path, so a visually connected but disabled component does not silently become a source of execution.
That behavior depends on the node contract. A node description defines inputs, outputs, properties, credentials, and versions; the runtime implementation defines what happens when the contract is invoked. A Webhook node has no ordinary input because it is an entry point, while an HTTP Request node consumes input items and returns response data. A node can also declare specialized AI connections, such as a language model, memory, tool, or output parser. The graph can therefore express both ordinary dataflow and the dependencies of an AI component without flattening them into the same undifferentiated edge.
Versioning matters because a saved node is not just a type name. n8n’s built-in nodes use versioned implementations, and the workflow stores the node’s type version. The runtime resolves the matching description and behavior before executing it. That gives a workflow a degree of historical stability: changing a node’s current implementation does not necessarily mean that every saved workflow silently changes its semantics. It also creates a maintenance obligation. A workflow may contain versions with different input rules or response behavior, so “the HTTP node” or “the Webhook node” is not always a sufficient technical description.
The graph can own state beyond the current item stream. Static data is associated with the workflow across executions and can hold workflow-specific values, such as information needed after registering a webhook. Pinned data lets a manual run use known output instead of performing the upstream operation again. Settings can affect timezone, execution order, binary handling, and other runtime choices. These are not decorations on the canvas. They are conditions under which the graph is interpreted.
The run is a state machine, not a topological sort
Once n8n has a workflow and a start condition, the core engine initializes an execution stack. Each stack entry contains a node, input data, and information about the source of that input. The engine repeatedly takes the next entry, adds paired-item lineage to incoming items, checks whether the node has the data it needs, invokes the node contract, records the result, and schedules downstream connections. A successful run is therefore a sequence of state transitions, not a single precomputed order.
The central loop can be reduced to this model:
The stack is not merely a convenient implementation structure. It is where n8n can retain the difference between work that is ready and work that is merely connected. A downstream node can be discovered before its inputs are complete, but it should not be treated as runnable until the required data has arrived. The stack entry carries the source information needed to revisit that decision without losing which branch or run produced the input.
Consider a join node with two main inputs. The first upstream branch may finish while the second is still running. If n8n immediately invoked the join with only the first value, the visual connection would suggest a relationship that the data did not satisfy. Instead, the engine stores the available input in a waiting structure keyed by node and run. When the other branch arrives, the engine combines the inputs and places a complete task on the execution stack. If a required input has finished with no items, an empty result can still carry meaning: it says that the branch ran and produced nothing, rather than that the branch has not run yet.
This distinction matters for optional inputs as well. In the newer execution-order path, the node description can specify how many inputs, or which input indexes, are required. A node can then proceed when its contract says enough information has arrived, while an all-input join remains deferred. The engine may also remove an empty waiting entry without running a node when every relevant branch has ended with no usable data. That prevents a visually downstream node from firing simply because the graph contains an edge.
This is why execution order cannot be inferred from screen position alone. n8n retains legacy and newer execution-order behavior. In the newer v1 path, ready downstream entries can be ordered using their graph positions, and the engine avoids automatically forcing every missing upstream path to execute. In the legacy path, the engine uses different rules for adding and checking inputs. The practical lesson is not that one order is universally correct; it is that execution order is a workflow setting with observable effects, especially around joins, optional inputs, and loops.
The item moving through this loop is also richer than a JSON value. An execution item may contain JSON, binary data, an error, paired-item information, and metadata. When an output is produced, n8n can associate it with the input item that produced it. In straightforward cases—one input and one output, or matching item counts—the engine can infer that relationship. When a node aggregates several items or changes their shape in a way that makes the origin ambiguous, automatic repair stops. The workflow author or node implementation must preserve the relationship if later steps need to trace it.
Suppose a filter sends one item down a true branch and another down a false branch. A later expression that refers to the filter must read the branch connected to the current node, not an arbitrary first output. The data proxy uses the graph’s connection indexes and the item’s paired information to make that choice. Conversely, after an aggregation has collapsed many inputs into one record, there may be no single correct source item. n8n’s errors around missing paired-item information are therefore useful signals: they identify an ambiguity in the data relationship rather than hiding it behind a coincidentally available value.
Expressions make that lineage visible to a workflow author. The data proxy allows a node to read current input and, where the graph permits it, data or parameters from another node. The proxy chooses the relevant branch based on how the nodes are connected and checks whether the referenced node has run. It raises an error when a node is missing, has not executed, has no data on the requested branch, or has too few items for the requested index. That behavior is less forgiving than returning an empty value, but it prevents a workflow from silently using the wrong execution context.
The execution record mirrors this stateful behavior. n8n stores task data by node name, with potentially multiple runs of the same node. A task records start time, execution index, source connections, output data, status, errors, and metadata. The record can therefore answer questions that a final response cannot: which branch ran, whether an upstream node produced no items, which attempt failed, and what data was available when a node was invoked. This is the foundation for debugging and for the editor’s execution view, but it also has a cost: retaining detailed execution data consumes storage and can expose sensitive values unless saving and redaction policies are configured carefully.
Failure does not always end the graph immediately. A node can be configured to retry, with bounded retry parameters and a delay between attempts. It can continue on fail or route error information through an error output. If it returns no data, the engine can end that branch without treating the whole workflow as a success with an invisible result. If a node is disabled, the engine can pass through input data; a resumed waiting webhook is handled specially so that all resolved output branches are forwarded rather than restarting the wait.
A wait is another state transition. A node can set a future wait time, which causes the run to finish the current processing pass with a waiting status instead of a completed status. The engine retains the execution stack and waiting data, and when the run resumes it clears the wait marker, prevents the waiting node from starting the same wait again, and continues from the saved state. This is why a wait cannot be explained as a delayed JavaScript promise. It requires a representation that can survive a process boundary and still identify the correct node, run, inputs, and source relationships.
The distinction between waiting for data and waiting for time is also important. A join waits because another path may still supply an input during the current execution. A human approval or webhook wait can suspend the whole run until a later external event. Both use state that must be restored, but they have different triggers and failure surfaces. Treating them as the same kind of delay would obscure whether n8n is waiting for a graph dependency, a scheduled time, or a caller’s response.
Cancellation has a related but different path. The execution owns an abort signal that nodes can observe. When cancellation occurs, the engine marks running tasks as canceled, aborts the signal, and reports the partial run through lifecycle hooks. A timeout can stop the loop between node executions, but the source does not imply that every external operation is instantly interruptible. A node or client that ignores cancellation may remain active until its own operation returns or the surrounding process takes further action.
At the end of the loop, n8n derives a status rather than merely checking whether the stack is empty. An execution can finish successfully, finish with an error, be canceled, or remain waiting with a future time. The engine can run lifecycle hooks, save changed static data, retain the last executed node, and return a full run record. Those final actions matter because the control plane must expose not only the output but also the condition under which the output was produced.
The result is a useful operating model for n8n. The saved graph determines what may be connected and which node contract is available. The execution stack determines what is ready now. Waiting structures preserve incomplete joins. Paired-item data preserves the meaning of values. Task records preserve the history of transitions. Errors, retries, waits, and cancellation change the state of a run without rewriting the graph itself.
That model also sets a standard for n8n’s AI features. If an agent’s model chooses a tool, the important question is whether that choice enters this same machinery—with an input, a source, a task record, a failure path, and a possible resumption. The next chapter follows that handoff, showing how n8n turns nodes into typed tools and makes an agent’s decision ordinary graph work rather than an opaque side effect.