Execution Model
This section defines the behavioral semantics of executing a Workflow. It specifies how context flows between Nodes, how routing decisions are made, and what observable events and traces a conforming executor MUST produce.
Context Accumulation
Section titled “Context Accumulation”At each node, a conforming executor MUST provide a context object containing:
input— the original workflow input passed to the executor.- One key per previously completed node, where the key is the node ID and the value is that node’s result data.
context = { input: <original workflow input>, nodeA: <nodeA result data>, nodeB: <nodeB result data>, ...}This context is available to the node’s instruction execution and to edge condition evaluation.
Context is append-only: each completed node adds its result to the context. If a node executes multiple times (via a bounded cycle), the context contains the most recent result for that node ID.
What a node receives
Section titled “What a node receives”The context above is what requires gates read, always in full. What the AI model sees at a node is a view of it, chosen by the workflow’s context_mode:
context_mode |
Prior nodes included | Each prior node’s value |
|---|---|---|
bounded (default) |
Nodes this node can depend on: its graph ancestors via edges (itself too, on a cycle), nodes its requires paths name, and node IDs its instruction mentions. |
For a node that succeeded and declares output.properties: only the declared fields, plus the executor’s error / fail_soft markers. Free-text fields such as an adapter’s summary are dropped. Otherwise the full data. |
full |
Every previously completed node. | The full result data. |
Every value keeps the runtime evals (and safe_outputs receipts) namespace. Prior results reach the model inside an untrusted-data fence in both modes.
Why bounded is the default: in full mode node N re-sends the complete output of nodes 1 to N-1, prose included, so prompt size per run grows quadratically and one verbose node inflates every later prompt. Declaring an output schema is how a node says which of its fields matter downstream. Set context_mode: full on a workflow whose later nodes read prose a schema’d node does not declare.
Node Execution Sequence
Section titled “Node Execution Sequence”For each node, a conforming executor MUST perform the following steps in order:
- Resolve Sources (once, before any node executes). Every Source in the workflow — node instructions, input rules/context, templates — is resolved. Files are read; URLs are fetched. Results are recorded in
trace.sources. - Resolve skills from the node’s
skillslist (see Skills). - Collect tools from resolved skills that have
tools. - Collect MCP servers from resolved skills that have
mcp. Wire them for this node’s execution session only (node-scoped, not global). - Build context: the accumulated context object, viewed per
context_modefor the model. - Build instruction — the node’s base instruction, augmented with input
rulesandcontextfields (see Nodes — Input Augmentation), then skill instructions from resolved skills that haveinstruction. - Invoke the AI model with: instruction, context, tools (from step 2 + MCP tools from step 3), and output schema (if present). The model is resolved per Model Selection (
node.model→workflow.model→ executor default); when none is specified the executor uses its implementation-defined default. - Capture the result as a
NodeResult. - Emit execution events (see Execution Events below).
- Resolve the next node via edge routing.
NodeResult
Section titled “NodeResult”The result of executing a single node.
| Field | Type | Description |
|---|---|---|
status |
"success" | "skipped" | "failed" |
Outcome of this node’s execution. |
data |
Record<string, unknown> | Output data. When the node has an output schema, this conforms to it. Available to downstream nodes via context. |
toolCalls |
ToolCall[] | Record of tools invoked during execution. |
skippedWrites |
string[] | Dry run only. Names of the node’s tools withheld as write or unclassified. Absent when nothing was withheld. |
ToolCall
Section titled “ToolCall”| Field | Type | Description |
|---|---|---|
tool |
string | Tool name. |
input |
unknown | Input passed to the tool. |
output |
unknown | Output returned by the tool. Absent on error. |
Dry-Run Semantics
Section titled “Dry-Run Semantics”A dry run never takes a write action. When the workflow input contains dryRun: true, a conforming executor MUST:
- Pass only read tools to every node. A skill tool reaches a node only when it declares
access: "read"(Tool Object). Tools withaccess: "write"and tools with noaccessare withheld: they are not registered for the node, so the model can neither see nor call them. - Expose no other write path. The agent runtime MUST NOT give the node external MCP servers from any source, including user or project settings and plugins (their tools are unclassified, so they count as writes), or built-in tools that can edit files, run shell commands, spawn subagents, or fetch arbitrary URLs (a GET can still trigger a webhook or exfiltrate data).
- Record withheld tools on the node result as
skippedWrites(tool names), and tell the model which tools were withheld so it can describe what it would have written. - Execute nodes through unconditional edges, and stop at the first node that has conditional outgoing edges (edges with
whenclauses), without evaluating them. - Return the execution result with all nodes completed up to that point.
Rules 1 to 3 are what make a dry run side-effect free, on every workflow shape, including linear workflows with no conditional edges. Rule 4 keeps the dry-run path deterministic: the nodes visited depend only on the graph, never on a model’s routing decision, and action branches that could only describe withheld writes are not run.
A dry run is still useful: analysis nodes run with their read tools (log queries, code search, issue lookups) and produce real output.
Execution Events
Section titled “Execution Events”A conforming executor MUST emit the following events during execution. Events are delivered to an observer callback. If the observer throws, the executor MUST NOT crash — observer errors are silently swallowed.
| Event Type | Payload | Emitted When |
|---|---|---|
workflow:start |
{ workflow: string } |
Before the first node executes. |
sources:resolved |
{ sources: Record<string, ResolvedSource> } |
Once, after the Source resolution phase completes, before the first node executes. |
node:enter |
{ node: string, instruction: string } |
Before a node begins execution. |
tool:call |
{ node: string, tool: string, input: unknown } |
Before a tool is invoked. |
tool:result |
{ node: string, tool: string, output: unknown } |
After a tool returns. |
node:progress |
{ node: string, message: string } |
During node execution (streaming status). |
node:exit |
{ node: string, result: NodeResult } |
After a node completes. |
route |
{ from: string, to: string, reason: string } |
After an edge is selected for routing. |
workflow:end |
{ results: Record<string, NodeResult> } |
After all execution is complete. |
Event Ordering
Section titled “Event Ordering”For each node, events MUST be emitted in this order:
node:enter- Zero or more
tool:call/tool:resultpairs andnode:progressevents (interleaved). node:exitroute(if a next node exists)
The first event of any execution is workflow:start, followed by sources:resolved, then the first node:enter. The last is workflow:end.
Execution Trace
Section titled “Execution Trace”A conforming executor MUST produce an ExecutionTrace after execution completes. The trace records the complete ordered path through the workflow, including loop iterations.
TraceStep
Section titled “TraceStep”| Field | Type | Description |
|---|---|---|
node |
string | Node ID. |
status |
"success" | "failed" | "skipped" |
Outcome. |
iteration |
number | 1-based iteration count. 2 means this node executed a second time (via bounded cycle). |
TraceEdge
Section titled “TraceEdge”| Field | Type | Description |
|---|---|---|
from |
string | Source node ID. |
to |
string | Target node ID. |
reason |
string | Why this edge was followed — the when condition text, or "only path" for unconditional edges. |
rung |
string | For a conditional route, who chose it: expr, decider or agent. Absent for unconditional edges. See Decision models. |
ExecutionResult
Section titled “ExecutionResult”The return value of a workflow execution.
| Field | Type | Description |
|---|---|---|
results |
Map<string, NodeResult> | Final result per node. If a node executed multiple times (bounded cycle), this contains the last result. |
trace |
ExecutionTrace | Ordered record of all steps and routing decisions. |
trace.sources
Section titled “trace.sources”Every resolved Source is recorded under trace.sources, keyed by field path (e.g. nodes.investigate.instruction, input.rules[0]). The value is a ResolvedSource containing content, kind, hash, and provenance.
This enables diffing runs to detect drift in externally-hosted content.
Error Handling
Section titled “Error Handling”- A node that fails MUST produce a
NodeResultwithstatus: "failed". - Whether the executor continues to subsequent nodes or terminates is implementation-defined.
- The trace MUST record the failure.
- Tool handler errors are surfaced to the AI model as tool results; they do not terminate execution.
AI Model Interface (Informative)
Section titled “AI Model Interface (Informative)”This section is informative, not normative. It describes the abstract interface the reference implementation uses. Alternative implementations MAY use different interfaces as long as observable behavior (context accumulation, routing, events, trace) conforms to this specification.
The reference implementation defines two AI model operations:
Execute a node. Takes:
instruction— the built instruction string.context— the accumulated context object.tools— resolved tool list from the node’s skills.outputSchema— optional JSON Schema for structured output.onProgress— optional callback for streaming status messages.
Returns a NodeResult.
evaluate
Section titled “evaluate”Route at a conditional edge. Takes:
question— a routing prompt (e.g., “Based on the results so far, which condition is true?”).context— the accumulated context object.choices— an array of{ id: string, description: string }whereidis the target node ID anddescriptionis thewhenclause text.
Returns the selected choice id (target node ID).