# How the n8n Workflow Execution Engine Works Internally: Architecture Deep Dive

> Explore the n8n workflow execution engine's internal architecture. Learn how it processes nodes sequentially, manages state, and handles special node types for efficient automation.

- Repository: [n8n - Workflow Automation/n8n](https://github.com/n8n-io/n8n)
- Tags: internals
- Published: 2026-02-24

---

**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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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.

```typescript
// 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.

```typescript
// 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 of `IExecuteData` objects 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 populates `resultData.runData` upon 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.

```typescript
// 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:

1. **Hook Execution** – Invokes `nodeExecuteBefore` unless the node is being resumed from an AI tool (indicated by `metadata?.nodeWasResumed`).
2. **Input Resolution** – `prepareConnectionInputData` extracts the first non-empty main input (or all inputs for non-execute nodes).
3. **Node Execution** – Depending on the node type, the engine calls:
   - `executeNode` for standard nodes (invoking the node's `execute` method).
   - `executePollNode` for polling triggers.
   - `executeTriggerNode` for event-based triggers.
   - `executeDeclarativeNodeInTest` for 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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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`](https://github.com/n8n-io/n8n/blob/main/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** – `handleDisabledNode` passes through the first input without execution, maintaining data flow continuity.
- **Execute-Once** – `handleExecuteOnce` truncates input arrays to a single item when `node.executeOnce === true`.
- **Retry Logic** – The engine respects `node.retryOnFail`, `node.maxTries`, and `node.waitBetweenTries` for 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:

1. **Trigger Location** – `findTriggerForPartialExecution` identifies the nearest trigger capable of starting the sub-run.
2. **Sub-Graph Construction** – `findSubgraph` builds the reachable portion of the graph, filtering disabled nodes.
3. **Data Cleaning** – `cleanRunData` removes stale data from dirty nodes and their children.
4. **Stack Recreation** – `recreateNodeExecutionStack` repopulates `nodeExecutionStack` with nodes requiring execution.
5. **Index Advancement** – `getNextExecutionIndex` advances `additionalData.currentNodeExecutionIndex` to the highest previous index plus one, ensuring unique execution tracking.

```typescript
// 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`](https://github.com/n8n-io/n8n/blob/main/execution-lifecycle-hooks.ts) for UI integration and custom extensions:

- **`workflowExecuteBefore`** / **`workflowExecuteResume`** – Fires before execution starts (resume variant used when `restartExecutionId` is 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** – `NodeOperationError` or `NodeApiError` are captured in `runExecutionData.resultData.error` and re-thrown after recording.
- **Workflow Errors** – `WorkflowHasIssuesError` throws when `checkReadyForExecution` detects misconfigured nodes before the run begins.

## Programmatic Execution Example

Developers can trigger the n8n workflow execution engine programmatically without the UI:

```typescript
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`](https://github.com/n8n-io/n8n/blob/main/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 `processRunExecutionData` processes nodes from `nodeExecutionStack`, resolving inputs and storing outputs in `runData`.
- **Type-Specific Handling** – Special logic manages **triggers**, **pollers**, **AI tools** (via graph rewiring), **disabled nodes**, and **retry configurations**.
- **Partial Execution** – The `runPartialWorkflow2` system 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) and `WorkflowHasIssuesError` (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`](https://github.com/n8n-io/n8n/blob/main/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.