How the n8n Workflow Execution Engine Works Internally: Architecture Deep Dive
n8n executes workflows by converting the JSON definition into a directed graph, then processing nodes sequentially via a central while-loop that manages execution state, handles special node types like triggers and AI tools, and supports partial re-execution of sub-graphs when nodes change.
The n8n workflow execution engine orchestrates automation runs by traversing a graph representation of your workflow. According to the n8n-io/n8n source code, the core orchestration logic resides in packages/core/src/execution-engine, where the WorkflowExecute class manages the entire lifecycle from graph construction to result persistence.
From Workflow Definition to Directed Graph
Before execution begins, n8n transforms the static workflow definition into a traversable data structure. The Workflow object (packages/workflow/src/workflow.ts) represents the JSON stored in the database, containing nodes and connections.
The engine converts this into a DirectedGraph using DirectedGraph.fromWorkflow(workflow) located in packages/core/src/execution-engine/partial-execution-utils/directed-graph.ts. This graph maps each workflow node as a vertex and each connection as a directed edge.
// packages/core/src/execution-engine/workflow-execute.ts
const graph = DirectedGraph.fromWorkflow(workflow);
Determining Start and Destination Nodes
The engine identifies the entry point using workflow.getStartNode(destinationNode?.nodeName). If no destination is specified, execution begins at the trigger node. When a specific destination is provided, the engine constructs a run-node filter containing all parent nodes (including non-main parents) to limit execution to the necessary sub-graph.
// Inside WorkflowExecute.run()
if (destinationNode) {
runNodeFilter = [
...workflow.getParentNodes(destinationNode.nodeName),
...workflow.getParentNodes(destinationNode.nodeName, 'ALL_NON_MAIN')
];
if (destinationNode.mode === 'inclusive') runNodeFilter.push(destinationNode.nodeName);
}
Execution Data Structures and State Management
The engine maintains several critical data structures to track execution progress. These are initialized via createRunExecutionData in the n8n-workflow package:
runExecutionData– The container holding start data, the node execution stack, waiting data, and final result data.nodeExecutionStack– A queue ofIExecuteDataobjects representing nodes ready for immediate processing.waitingExecution/waitingExecutionSource– Buffers partial data for nodes with multiple inputs until all prerequisites arrive.runData– The final per-node output that populatesresultData.runDataupon completion.
The Main Execution Loop
The heart of the n8n workflow execution engine is processRunExecutionData, which contains a while-loop that continuously processes nodes until the execution stack empties.
// packages/core/src/execution-engine/workflow-execute.ts
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift()!;
// … prepare data, call runNode(), handle errors, update runData …
}
Each iteration performs the following operations:
- Hook Execution – Invokes
nodeExecuteBeforeunless the node is being resumed from an AI tool (indicated bymetadata?.nodeWasResumed). - Input Resolution –
prepareConnectionInputDataextracts the first non-empty main input (or all inputs for non-execute nodes). - Node Execution – Depending on the node type, the engine calls:
executeNodefor standard nodes (invoking the node'sexecutemethod).executePollNodefor polling triggers.executeTriggerNodefor event-based triggers.executeDeclarativeNodeInTestfor declarative nodes in test mode.
The result (IRunNodeResponse or EngineRequest) merges into runExecutionData.resultData.runData, and downstream nodes with satisfied dependencies are pushed onto the execution stack.
Handling Special Node Types
The n8n execution engine contains specialized logic for different node categories, implemented in packages/core/src/execution-engine/workflow-execute.ts:
Triggers and Pollers
When mode === 'manual', triggers execute via TriggersAndPollers.runTrigger (packages/core/src/execution-engine/triggers-and-pollers.ts). Poll nodes invoke their poll functions in manual mode; otherwise, the engine passes through existing poll results.
AI Tools and Agent Nodes
If NodeHelpers.isTool identifies a node as an AI tool, the engine rewires the graph to insert a virtual ToolExecutor node using logic from partial-execution-utils/rewire-graph.ts. The destination swaps to the executor, enabling the tool result to feed back into the workflow context.
Execution Controls
- Disabled Nodes –
handleDisabledNodepasses through the first input without execution, maintaining data flow continuity. - Execute-Once –
handleExecuteOncetruncates input arrays to a single item whennode.executeOnce === true. - Retry Logic – The engine respects
node.retryOnFail,node.maxTries, andnode.waitBetweenTriesfor automatic failure recovery.
Partial Execution and Sub-Graph Re-runs
When editing a node or receiving AI tool output, the engine uses runPartialWorkflow2 to execute only the affected sub-graph rather than the entire workflow:
- Trigger Location –
findTriggerForPartialExecutionidentifies the nearest trigger capable of starting the sub-run. - Sub-Graph Construction –
findSubgraphbuilds the reachable portion of the graph, filtering disabled nodes. - Data Cleaning –
cleanRunDataremoves stale data from dirty nodes and their children. - Stack Recreation –
recreateNodeExecutionStackrepopulatesnodeExecutionStackwith nodes requiring execution. - Index Advancement –
getNextExecutionIndexadvancesadditionalData.currentNodeExecutionIndexto the highest previous index plus one, ensuring unique execution tracking.
// packages/core/src/execution-engine/workflow-execute.ts
this.additionalData.currentNodeExecutionIndex = getNextExecutionIndex(runData);
Lifecycle Hooks and Observability
The engine exposes hooks via execution-lifecycle-hooks.ts for UI integration and custom extensions:
workflowExecuteBefore/workflowExecuteResume– Fires before execution starts (resume variant used whenrestartExecutionIdis set).nodeExecuteBefore/nodeExecuteAfter– Surrounds each node execution (skipped when resuming AI tools).workflowExecuteAfter– Signals completion or cancellation of the entire run.
These hooks enable progress indicators, logging, and custom side-effects in the n8n editor interface.
Error Handling and Validation
The engine distinguishes between node-level and workflow-level failures:
- Node Errors –
NodeOperationErrororNodeApiErrorare captured inrunExecutionData.resultData.errorand re-thrown after recording. - Workflow Errors –
WorkflowHasIssuesErrorthrows whencheckReadyForExecutiondetects misconfigured nodes before the run begins.
Programmatic Execution Example
Developers can trigger the n8n workflow execution engine programmatically without the UI:
import { Workflow } from 'n8n-workflow';
import { WorkflowExecute } from '@n8n/core';
// 1. Build workflow definition
const wf = new Workflow({
id: 'my-wf',
nodes: [...], // n8n node definitions
connections: {...},
active: true,
nodeTypes, // from n8n node packages
});
// 2. Prepare execution context
const additionalData = {
// Contains hooks, abort signals, and execution metadata
// Use Helpers.WorkflowExecuteAdditionalData for testing
};
// 3. Execute workflow
const executor = new WorkflowExecute(additionalData, 'manual');
const run = await executor.run({ workflow: wf });
console.log('Result run data:', run.resultData.runData);
The n8n CLI (packages/cli/src/workflows/workflow.service.ts) implements this pattern while adding database persistence, credential resolution, and UI hook integration.
Summary
- Graph Construction – The engine converts workflow JSON into a DirectedGraph via
DirectedGraph.fromWorkflow()to establish execution paths. - Stack-Based Processing – A while-loop in
processRunExecutionDataprocesses nodes fromnodeExecutionStack, resolving inputs and storing outputs inrunData. - Type-Specific Handling – Special logic manages triggers, pollers, AI tools (via graph rewiring), disabled nodes, and retry configurations.
- Partial Execution – The
runPartialWorkflow2system enables efficient re-runs by constructing sub-graphs and cleaning stale data when nodes change. - Hook Integration – Lifecycle hooks (
nodeExecuteBefore/After,workflowExecuteBefore/After) provide observability for the editor and extensions. - Error Boundaries – Distinct handling for
NodeOperationError(node-level) andWorkflowHasIssuesError(workflow-level) ensures robust failure management.
Frequently Asked Questions
How does n8n determine which node to execute first?
The engine calls workflow.getStartNode(destinationNode?.nodeName) to identify the trigger node that initiates the workflow. If a destination node is specified for partial execution, the engine calculates a run-node filter containing all parent nodes of the destination to establish the starting boundary.
What happens when a node fails during execution?
When a node throws NodeOperationError or NodeApiError, the engine captures the error in runExecutionData.resultData.error before re-throwing it. If the node has retryOnFail enabled, the engine automatically retries according to node.maxTries and node.waitBetweenTries settings before failing the execution.
How does n8n handle AI tool nodes differently from regular nodes?
AI tool nodes trigger graph rewiring via partial-execution-utils/rewire-graph.ts. The engine inserts a virtual ToolExecutor node and redirects the destination to this executor, allowing the tool's output to integrate back into the workflow context. This mechanism specifically handles the partial execution requirements of AI Agent workflows.
Can I execute only part of a workflow without running the entire graph?
Yes, through partial execution (runPartialWorkflow2). The engine locates the relevant trigger via findTriggerForPartialExecution, builds a sub-graph using findSubgraph, cleans obsolete data with cleanRunData, and recreates the execution stack via recreateNodeExecutionStack. This efficiently re-runs only the affected nodes when you edit a specific node or receive new AI tool output.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →