Flow execution model
The current ActionFlows run lifecycle: saved graphs, sequential node execution, mappings, control nodes, failures, and persisted results.
Flow execution model
This page describes the current ActionFlows runtime. An ActionFlow is a saved graph with one supported start node, connected nodes, and edges. A run is created from the saved graph; the canvas itself is not the execution contract.
Lifecycle at a glance
- Save the graph in Actionflow Studio or through the ActionFlow API.
- Validate at the ingress surface. Actionflow Studio/API rerun and webhook paths call
validateFlowbefore enqueue. Scheduled ticks and queue-dispatched runs can be admitted and receive a history row first; the queue consumer validates queued items later, and direct dispatch does not have the same pre-enqueue check. - Admit the run. The system reserves organization credits and either dispatches the run directly or places it in the flow queue.
- Execute the saved graph from its start node. Node work is sequential; control nodes select paths and Loop nodes repeat their body.
- Persist the terminal or paused state, selected results, metadata, and credit usage in run history.
A request that fails validation before enqueue may not create a run-history row. A scheduled run can have a history row even when it does not pass the API/webhook pre-enqueue check, and a queued run can have a history row before queue-time validation fails. Once a run is admitted, the run appears in the queue and/or run-history surfaces with an identifier.
One start node
The supported graph shape is one start node:
- Start for Actionflow Studio, dashboard, API, SDK, and hosted-MCP runs.
- Start Scheduled for a scheduled run.
- Start Subflow for the subflow start shape.
API and webhook callers do not add another start-node type. They run a saved Start-compatible graph. A flow webhook is an ingress route on a flow, not a fifth start node. The current runtime also does not dispatch a nested child graph for a subflow start; see the Subflow limitation.
validateFlow rejects a graph without a start node and rejects multiple start nodes that have outgoing connections. For this validation check, any edge with the start node as its source counts; the validator does not inspect whether the handle is an execution or data handle. The runtime selects the first start node when it executes a graph, so additional start nodes are not a supported way to fan out work.
Execution edges and data handles
An edge can carry execution, data, or a control-lane selection. The handle names determine how the runner treats it.
| Connection | Runtime meaning |
|---|---|
A default execution edge, usually node-source or no source handle | Continue to the target after the source completes. |
| A named data edge | Read the source output at sourceHandle and assign it to the target's targetHandle input. |
trueBranch or falseBranch from If | Follow only the selected branch. |
A Switch case_* handle | Follow the matched case or selected default case. |
loop-body-in and loop-body-out | Internal Loop-body boundaries; they are not ordinary continuation edges. |
A node-target connection only sequences execution. It does not satisfy a required input whose definition has a named handle. Connect the source to that named target handle or put a value in the node's saved inputs. Run inputs are ingress-specific and are not a substitute for the pre-enqueue validation used by API and webhook callers.
The current runner has no parallel branch scheduler. When a node has multiple eligible outgoing edges, the runner processes them sequentially and depth-first: a target and its descendants complete before the next eligible edge from the ancestor is visited. A node that has already executed is not executed again merely because another edge points to it.
How inputs are resolved
A node's input record is assembled in this order:
- Start with the values saved in the node's
inputData.inputs. - Apply values from connected data edges. A named target handle is assigned only when the mapped source output is not
undefined; a defined value, includingnull, replaces a saved value with the same input key. Anundefinedresult leaves the saved value in place. - Inject the nearest Loop context for a Loop body:
item,index,isFirst, andisLast. - Resolve
{{nodeId.outputPath}}placeholders recursively in string values, arrays, and objects. - Run the node function with the resulting input object.
The first three sources are distinct from a node's execution edge. A node can receive a value from a data handle while still depending on an execution edge to establish order. Validation can see a matching named target handle as connected, but runtime input assembly still applies that edge only when its resolved mapped value is not undefined.
Static values and run inputs
A static value is saved on the node. Empty required fields on reachable nodes can be exposed as Flow inputs with the key:
{nodeId}_{inputName}For API and webhook runs, inputs are applied to an execution copy of the graph. An Actionflow Studio Run flow submission also saves the entered values before starting the run. Request-envelope fields body, headers, query, and method are merged onto the start node for webhook/API envelopes.
Placeholder mappings
The current placeholder form is:
{{nodeId.outputPath}}Examples include {{startNodeId.body}} and {{generateTextNodeId.data.text}}. The resolver first tries the requested path and then a data.-wrapped path for common node output shapes.
- A string containing a placeholder replaces the reference with a string representation. Objects are JSON-stringified;
nullandundefinedbecome an empty string. - A string that is exactly one placeholder preserves the raw value, including an object, array, or file buffer.
- A missing node, a node that is not reachable from the start, a circular reference, or a missing path produces an empty string in an embedded mapping or an undefined value in an exact mapping.
- Placeholder resolution is depth-capped. Values below the cap can still be unresolved if the graph does not provide the referenced node or path.
A placeholder is not a pure lookup. When the referenced node is reachable, resolution calls executeNode for it and can run its downstream nodes before the normal traversal reaches them. Use an explicit execution edge when a reference must not cause early side effects.
A missing path is not automatically a flow validation error. The downstream node may still run with an empty or undefined value, and it may fail later if its function requires a value. Check the node's input and result rather than assuming that a visible placeholder means the value was found.
Control nodes
If
The If function accepts booleans, numbers, strings, and the supported comparison expression grammar. It returns data.trueBranch or data.falseBranch with success: true. The runner follows the selected lane only. A normal continuation edge can still run after the selected lane.
An invalid or unparseable condition is treated as false by the current evaluator. A missing required condition input is caught by flow validation when the node is reachable.
Switch
Switch normalizes the supplied cases, converts the value to a string, and selects a matching case. It returns the selected case payload plus matchedCase, selectedCase, and value. If no case matches and a default case is configured, the default lane is selected. If neither exists, the node returns success: false; normal execution fails unless the node is configured to continue on error.
Only the selected case lane is followed. The value, matchedCase, and selectedCase fields describe the selection; they are not instructions to fan out through every case.
Loop
Loop accepts manual items as an array or a JSON array string. In inherited mode, items must be one exact node-output reference and that reference must resolve to an actual array; a single object or a JSON string containing an array is not accepted by the inherited parser. Loop returns count, isEmpty, and items, then runs the Loop body sequentially for each item. Each iteration receives a fresh Loop context and can execute the body nodes again.
The placeholder roots depend on the Loop output mode. {{loopNodeId.result}} and paths under it, such as {{loopNodeId.result.count}} or {{loopNodeId.result.items}}, refer to the aggregate Loop result regardless of output mode. Inside a Loop body, outputAsObject: true (the seeded default) uses {{loopNodeId.iteration}} for an object containing the current item, index, and first/last flags. When outputAsObject is false, use {{loopNodeId.item}} for the current item; {{loopNodeId.iteration}} is not the item root in that mode. The per-iteration roots are only available while that Loop's body is executing.
The runner stores prior body results under a node-specific __iterations key when it prepares the next iteration. The history UI can show those repeated occurrences. The current runtime does not parallelize iterations.
Merge
Merge accepts a branches array (or a JSON array string) and returns a flattened mergedData array. It is a data transformation, not a scheduler.
Current limitation: Merge does not wait for multiple graph branches and does not collect results from parallel execution. The runtime is sequential. If you need a value from several paths, make that dependency explicit with available data mappings and a supported control pattern rather than treating Merge as a synchronization barrier.
Wait
ActionFlows-hosted Flow runs use a durable wait. duration must be a finite number from 0 through 3,600 seconds. The node returns the passed data and measured waitTime.
Current limitation: Wait currently implements a timed delay only. It does not wait for an external signal or a resume event.
Failure propagation and partial results
A node can fail in two ways:
- The node function throws an error. The runner records a node failure and stops the normal execution path.
- The node returns an object with
success: false. The runner records the output, then fails the run unless the node hascontinueOnError: true.
The output is added to the partial execution state before the failure gate. A failed run can therefore contain results from nodes that completed before the failure, together with the failing node's error. A downstream node is not guaranteed to run after a failure. continueOnError applies to reported output failures; it does not turn a thrown exception into a successful node.
Non-Wait nodes also have a current soft execution timeout of 120 seconds. Wait nodes use the durable wait path instead. Credit checks can stop a run when the organization has no available credits.
Run states
The product uses both queue states and run-history states. The names can differ between the queue, the execution service, the app, and the public API.
| State | Meaning |
|---|---|
waiting | The run is waiting in the organization queue. The queue item can be cancelled while it remains waiting. |
dispatching | A queue worker is handing the run to the execution service. |
dispatched | The run was submitted to the execution service and is no longer waiting for the queue. |
executing | Nodes are running. |
waitingForHitl | A HITL node paused the prefix run pending a human decision. |
completed | The graph finished successfully. |
failed | The graph or an admitted run could not finish successfully. |
cancelled | A queued or active run was cancelled. |
unknown | The recorded status is missing or not recognized. |
Execution-service aliases commonly shown as QUEUED, EXECUTING, WAITING, WAITING_FOR_HITL, COMPLETED, FAILED, and CANCELED map to these product states. A 202 response means that a run was accepted or queued; it does not mean the graph completed. Scheduled and queue-dispatched runs can have a history row before the relevant pre-enqueue or queue-time validation result is known, so an admitted row is not proof that validateFlow passed.
When a HITL decision is resolved and its continuation is enqueued, the prefix run is not resumed in place. The continuation restores the saved partial results and Loop context and starts at the HITL node. The prefix history row is marked completed once that enqueue succeeds. See HITL approvals.
Result and history limits
There are several different size and visibility boundaries:
- The HTTP Request node caps a remote response at 5 MB (
5 * 1024 * 1024bytes) while reading it. This is a remote HTTP response limit, not a universal cap on every node payload. - The current runtime uses a 100,000 serialized-character cap for loop-body results stored in run history between iterations. A truncated entry carries a
truncatedmarker. This is not a promise that every other history result is capped at 100,000 characters. - Generated artifact handling has a separate artifact-size constant; see Flow artifacts.
Actionflow Studio's active output surface can show the current run and node results. After a run reaches a terminal state, the app loads the persisted run-history payload for the detail view. The history read path sanitizes the payload and redacts sensitive-looking keys; it does not promise a complete raw copy of every request input, provider response, or secret. Use live Actionflow Studio output and targeted logging for immediate inspection, and treat history as a filtered audit view.