ServerFlows
Execution Engine
Graph traversal, execution loop, safety guardrails, and concurrency model.
FlowExecutionEngine
Located in flow-execution.engine.ts, the engine uses Effect-TS to manage concurrency and error propagation.
Execution Loop
Steps
- Initialize Context: Prepare
ExecutionContextwith trigger event data - Persistence: Create
FlowRunrecord in database - Queue Nodes: Start with
Startnode - Traversal:
- Load current node and definition
- Create
FlowExecutionNode(status:PENDING) - Execute node logic (status:
SUCCESSorFAILED) - Resolve outgoing edges based on result
- Queue next nodes
- Finalize: Mark
FlowRunasCOMPLETEDorFAILED
Safety Guardrails
The engine enforces limits in definitions.ts:
| Limit | Default | Description |
|---|---|---|
maxSteps | 5000 | Total nodes visited in single execution |
maxNodeVisits | 50 | Times a single node can be visited |
parallelism | 6 | Concurrent branches in Parallel nodes |
timeout | 300000ms | Max time for single node execution |
Loop Detection
The maxNodeVisits limit prevents infinite loops:
if (nodeVisitCount > limits.maxNodeVisits) {
throw new FlowError("MAX_NODE_VISITS_EXCEEDED");
}Concurrency Model
The engine uses Effect-TS concurrency:
// Parallel nodes use unbounded concurrency, limited by config
Effect.all(nodes, { concurrency: "unbounded" })
.pipe(Effect.withConcurrencyLimit(config.parallelism));Parallel Execution
Nodes marked as parallel execute concurrently:
{
"type": "parallel",
"branches": [
{ "id": "branch1", "nodes": [...] },
{ "id": "branch2", "nodes": [...] }
]
}Error Handling
Retry Strategy
interface RetryConfig {
maxAttempts: number;
delayMs: number;
backoff: "linear" | "exponential";
}Dead Letter Queue
Failed executions not retryable go to dead-letter:
await FlowDeadLetter.create({
flowId,
executionId,
error: errorMessage,
nodeId,
});State Transitions
PENDING → RUNNING → SUCCESS
→ FAILED
→ RETRYING
PENDING → RUNNING → DEAD_LETTER (after max retries)