This document describes how Sim Studio implements loop and parallel execution patterns within workflows. Loops enable iterative execution of block sequences, while parallel blocks enable concurrent execution across multiple branches. Both are implemented as container blocks that house child blocks and manage their execution context via a specialized orchestrator system.
For information about general workflow execution, see Workflow Execution Engine (3.4) For details on the DAG-based execution model, see Workflow Fundamentals (3.1)
Sim Studio provides two special block types for advanced control flow:
for, forEach, while, doWhile) [apps/sim/executor/orchestrators/loop.ts:90-185].Both are implemented as subflow containers that:
parentId relationships [apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts:71-72].start and end) [apps/sim/executor/utils/subflow-utils.ts:25-39].LoopScope and ParallelScope [apps/sim/executor/orchestrators/loop.ts:54-86], [apps/sim/executor/orchestrators/parallel.ts:50-68].Sources: [apps/sim/executor/orchestrators/loop.ts:1-185], [apps/sim/executor/orchestrators/parallel.ts:1-183], [apps/sim/executor/utils/subflow-utils.ts:1-58]
The LoopOrchestrator handles the initialization and continuation of loops. It evaluates the loop type and sets the maxIterations or condition within a LoopScope [apps/sim/executor/orchestrators/loop.ts:54-86].
| Loop Type | Description | Implementation Detail |
|---|---|---|
for | Fixed iteration count | Validates against DEFAULTS.MAX_LOOP_ITERATIONS [apps/sim/executor/orchestrators/loop.ts:91-114]. |
forEach | Iterate over collection | Resolves array input via VariableResolver and resolveArrayInput [apps/sim/executor/orchestrators/loop.ts:117-178]. |
while | Pre-check condition | Evaluates JS expression before the first iteration [apps/sim/executor/orchestrators/loop.ts:180-183]. |
doWhile | Post-check condition | Executes body once, then evaluates condition [apps/sim/executor/orchestrators/loop.ts:185-195]. |
Loop and condition blocks use the function_execute tool to evaluate JavaScript expressions in an isolated VM [apps/sim/executor/handlers/condition/condition-handler.ts:38-58]. This ensures variable resolution (e.g., {{block.output}}) is consistent with the rest of the engine [apps/sim/executor/handlers/condition/condition-handler.ts:24-29]. The ConditionBlockHandler specifically filters source outputs to prevent passing internal metadata like _pauseMetadata or providerTiming into the evaluation context [apps/sim/executor/handlers/condition/condition-handler.ts:165-172].
Sources: [apps/sim/executor/orchestrators/loop.ts:91-195], [apps/sim/executor/handlers/condition/condition-handler.ts:19-78], [apps/sim/executor/handlers/condition/condition-handler.ts:165-172]
Unlike loops which iterate sequentially, parallel blocks use a ParallelExpander to dynamically clone the subflow DAG for each branch [apps/sim/executor/orchestrators/parallel.ts:41-41], [apps/sim/executor/orchestrators/parallel.ts:134-183].
| Parallel Type | Description | Configuration |
|---|---|---|
count | Fixed number of branches | Resolved via resolveBranchCount [apps/sim/executor/orchestrators/parallel.ts:74-75]. |
collection | Branch per item | Distribution resolved via resolveArrayInput [apps/sim/executor/utils/subflow-utils.ts:192-205]. |
Branches are identified using specialized ID patterns to maintain unique execution states:
blockId₍1₎) built via buildBranchNodeId [apps/sim/executor/utils/subflow-utils.ts:80-82].{originalId}__obranch-{index} [apps/sim/executor/utils/subflow-utils.ts:123-125].stripCloneSuffixes or extractBaseBlockId to resolve variables back to the original block definitions during data collection [apps/sim/executor/utils/subflow-utils.ts:83-85], [apps/sim/executor/utils/subflow-utils.ts:114-118].Sources: [apps/sim/executor/orchestrators/parallel.ts:134-183], [apps/sim/executor/utils/subflow-utils.ts:80-133], [apps/sim/executor/utils/block-data.ts:78-83]
The visual representation of containers is managed by tracking hierarchy depths and relative offsets.
UI to Code Entity Mapping: Canvas Nesting
Key UI Properties:
getNodeDepth which recursively walks the parentId chain [apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts:67-77].headerHeight (50px), leftPadding (16px), and topPadding (16px) [apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts:164-173].getBlockDimensions uses CONTAINER_DIMENSIONS for loops and parallels, falling back to estimated dimensions for standard blocks [apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts:31-59].Sources: [apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-node-utilities.ts:17-176], [apps/sim/lib/workflows/blocks/block-dimensions.ts:1-10]
The ExecutionEngine interacts with loops and parallels through Sentinel Nodes which mark the boundaries of subflows [apps/sim/executor/orchestrators/node.ts:66-74].
initializeLoopScope or initializeParallelScope and evaluates the initial condition to determine if the body should be skipped [apps/sim/executor/orchestrators/node.ts:99-112], [apps/sim/executor/orchestrators/node.ts:156-176].evaluateLoopContinuation (for loops) or aggregateParallelResults (for parallels) to decide whether to iterate again or exit the container [apps/sim/executor/orchestrators/node.ts:114-137], [apps/sim/executor/orchestrators/node.ts:178-186].The EdgeManager handles the complex logic of determining which outgoing edges are active based on block results and subflow state [apps/sim/executor/execution/edge-manager.ts:15-37].
UI to Code Entity Mapping: Control Flow
Dead-end Handling: If a condition block inside a loop/parallel selects a path that leads to no further blocks (a dead-end), the EdgeManager identifies the "Enclosing Sentinel" via isEnclosingSentinel to ensure the subflow can still complete [apps/sim/executor/execution/edge-manager.ts:151-175].
Sources: [apps/sim/executor/orchestrators/node.ts:85-190], [apps/sim/executor/execution/edge-manager.ts:9-108], [apps/sim/executor/execution/edge-manager.ts:161-175]
Every block executed within a container is provided with an IterationContext [apps/sim/executor/utils/iteration-context.ts:1-10]. This is built using buildContainerIterationContext and contains:
parentIterations tracks the indices of all enclosing loops/parallels for nested variable resolution [apps/sim/executor/utils/iteration-context.ts:51-60].When a block inside a loop references {{loop.item}}, the VariableResolver looks up the LoopScope associated with the current nodeId [apps/sim/executor/orchestrators/loop.ts:173-177].
Result Aggregation:
When a loop or parallel finishes, the end sentinel returns a results array containing the outputs of all iterations/branches [apps/sim/executor/orchestrators/node.ts:131-136], [apps/sim/executor/orchestrators/node.ts:180-186]. The LoopOrchestrator aggregates these into aggregatedResults [apps/sim/executor/orchestrators/loop.ts:38-43].
Sources: [apps/sim/executor/utils/iteration-context.ts:1-60], [apps/sim/executor/orchestrators/node.ts:114-186], [apps/sim/executor/utils/subflow-utils.ts:146-165]
Refresh this wiki