Skip to content

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.

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.

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.

For each node, a conforming executor MUST perform the following steps in order:

  1. 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.
  2. Resolve skills from the node’s skills list (see Skills).
  3. Collect tools from resolved skills that have tools.
  4. Collect MCP servers from resolved skills that have mcp. Wire them for this node’s execution session only (node-scoped, not global).
  5. Build context: the accumulated context object, viewed per context_mode for the model.
  6. Build instruction — the node’s base instruction, augmented with input rules and context fields (see Nodes — Input Augmentation), then skill instructions from resolved skills that have instruction.
  7. 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.
  8. Capture the result as a NodeResult.
  9. Emit execution events (see Execution Events below).
  10. Resolve the next node via edge routing.

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.
Field Type Description
tool string Tool name.
input unknown Input passed to the tool.
output unknown Output returned by the tool. Absent on error.

A dry run never takes a write action. When the workflow input contains dryRun: true, a conforming executor MUST:

  1. Pass only read tools to every node. A skill tool reaches a node only when it declares access: "read" (Tool Object). Tools with access: "write" and tools with no access are withheld: they are not registered for the node, so the model can neither see nor call them.
  2. 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).
  3. 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.
  4. Execute nodes through unconditional edges, and stop at the first node that has conditional outgoing edges (edges with when clauses), without evaluating them.
  5. 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.

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.

For each node, events MUST be emitted in this order:

  1. node:enter
  2. Zero or more tool:call / tool:result pairs and node:progress events (interleaved).
  3. node:exit
  4. route (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.

A conforming executor MUST produce an ExecutionTrace after execution completes. The trace records the complete ordered path through the workflow, including loop iterations.

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).
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.

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.

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.

  • A node that fails MUST produce a NodeResult with status: "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.

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.

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 } where id is the target node ID and description is the when clause text.

Returns the selected choice id (target node ID).