Execution Model
Understanding how the workflow engine executes your automations helps you build reliable workflows and debug issues when they arise.
Execution Lifecycle
Every workflow execution moves through these states:
pending --> running --> completed
--> failed
--> cancelled
--> timed_out
--> suspended (waiting for event/condition) --> running| State | Description |
|---|---|
pending | Execution is queued and waiting for a worker to pick it up |
running | Nodes are actively executing |
completed | All nodes finished successfully |
failed | A node failed and no error handler caught it |
cancelled | The execution was manually cancelled via API or UI |
timed_out | The execution exceeded the 30-minute maximum |
suspended | The execution is paused, waiting for an external event or time condition |
Node Execution Order
Nodes execute based on the DAG (directed acyclic graph) structure:
- The trigger fires and creates an execution in
pendingstate - The engine picks up the execution and sets it to
running - Starting from the trigger, each connected node executes in topological order
- When a node completes, its output is stored and the next connected nodes are queued
- For branching nodes (Condition, Switch), only the matching branch executes
- For Fork nodes, all branches execute concurrently
- Merge nodes wait for all incoming branches before continuing
- Execution completes when all reachable nodes have finished
BullMQ Job Scheduling
The workflow engine uses BullMQ (a Redis-backed job queue) for reliable job scheduling:
- Each workflow execution is a BullMQ job
- Jobs are processed by worker instances with configurable concurrency
- Failed jobs are retried according to the workflow’s error handling configuration
- Scheduled triggers (CRON) create jobs at the specified times
- Delayed nodes (Delay, Wait-for-Event) use BullMQ’s delayed job feature
Suspension and Resumption
Some nodes pause execution until an external condition is met:
Wait-for-Event Node
Suspends the execution until a specific platform event occurs or a timeout is reached.
Modes:
- Time — Resume after a fixed delay (e.g., 30 minutes)
- Event — Resume when a specific event fires (e.g.,
inbox.message.receivedfor the same conversation) - Condition — Resume when a data condition becomes true (polled periodically)
When suspended:
- The execution state changes to
suspended - The engine saves the current execution context to the database
- A listener is registered for the target event (or a timer is set)
- When the condition is met, the execution resumes from the suspended node
- All previously computed variables are restored
Delay Node
Pauses execution for a specified duration. Internally uses BullMQ delayed jobs.
Configuration: Duration in seconds, minutes, hours, or days.
Concurrency Limits
| Limit | Value | Scope |
|---|---|---|
| Max execution time | 30 minutes | Per execution |
| Concurrent executions | 50 | Per workspace |
| Concurrent executions | 10 | Per workflow |
| Max nodes per workflow | 100 | Per workflow |
| Max nested loop depth | 3 | Per execution |
When concurrency limits are reached:
- New executions are queued in
pendingstate - They execute as soon as a slot becomes available
- FIFO ordering is maintained within the queue
Execution Context
Every node receives an execution context with:
{
workspaceId: string; // The workspace this execution belongs to
executionId: string; // Unique ID for this execution run
workflowId: string; // The workflow definition ID
triggeredBy: string; // What triggered this execution
variables: Record<string, unknown>; // Accumulated variables from all previous nodes
}Execution History
Every execution is logged with:
- Start and end timestamps
- Final state (completed, failed, cancelled, timed_out)
- Per-node execution log — input config, output data, duration, errors
- Variable snapshots at each step
Access execution history via:
- The workflow builder UI (Executions tab)
GET /api/v1/workflows/:id/executionsAPI endpoint
Failure Behavior
When a node fails:
- The engine checks for a Try-Catch wrapper — if found, the catch branch executes
- If no Try-Catch, the engine checks for a Retry wrapper — if found, the node is retried
- If no Retry, the engine checks for an On-Error handler — if found, the handler executes
- If no error handler at all, the execution state changes to
failed
The failed node’s error message and stack trace are recorded in the execution log.
Idempotency
Workflow executions are not automatically idempotent. If you need idempotent operations:
- Use the
executionIdas an idempotency key when calling external APIs - Check for existing state before creating resources (e.g., check if a lead already exists before creating)
- Use the Condition node to skip actions that have already been performed
Timeouts
- Execution timeout: 30 minutes total. If exceeded, the execution is marked
timed_outand all in-progress nodes are cancelled. - Node timeout: Individual nodes have a 60-second default timeout for HTTP operations. Some nodes (like AI operations) have extended timeouts.
- Suspended execution timeout: Suspended executions expire after 24 hours if the wait condition is never met.