Edges & Routing
Edges define the execution flow between Nodes in a Workflow. Each edge connects a source node to a target node, with an optional natural language condition that determines whether the edge is followed.
Fields
Section titled “Fields”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
from | string | REQUIRED | — | Source node ID. MUST reference a key in nodes. |
to | string | REQUIRED | — | Target node ID. MUST reference a key in nodes. |
when | string | OPTIONAL | — | Natural language routing condition. |
max_iterations | integer (≥ 1) | OPTIONAL | unlimited | Maximum times this edge can be followed. |
Routing Algorithm
Section titled “Routing Algorithm”After a node completes, a conforming executor MUST resolve the next node using the following algorithm:
- Collect all outgoing edges from the completed node (
fromequals the current node ID). - Exclude edges that have exhausted their
max_iterationscount (i.e., the edge has been followedmax_iterationstimes already). - If zero edges remain: execution is complete. The current node is a terminal node.
- If exactly one edge remains and it has no
whenclause: follow it unconditionally. The executor MUST NOT invoke the AI model. - Otherwise: present all remaining edge conditions to the AI model. The model evaluates each
whenclause against the accumulated context (workflow input + all prior node results) and selects one edge. The executor follows the selected edge.
Unconditional Edges
Section titled “Unconditional Edges”An edge without a when clause is unconditional.
If a node has exactly one unconditional outgoing edge and no conditional edges (after filtering exhausted edges), the executor MUST follow it without invoking the AI model. This is a deterministic transition — no evaluation required.
edges: - from: gather to: investigateConditional Edges
Section titled “Conditional Edges”An edge with a when clause is conditional. The when field contains a natural language condition.
edges: - from: investigate to: create_issue when: novel_count is greater than 0 AND highest_severity is medium or higher - from: investigate to: skip when: novel_count is 0, OR highest_severity is lowA conforming executor:
- MUST evaluate conditions by providing the AI model with the accumulated context (workflow input + all prior node results) and the list of conditions as choices.
- MUST NOT evaluate conditions programmatically. The AI model is the evaluator.
- MUST follow the edge selected by the AI model.
When a node has a mix of conditional and unconditional outgoing edges, the unconditional edge serves as a default path. The executor presents the conditional edges as choices, with the unconditional edge as a “none of the above” fallback.
Route-eval view of prior node results
Section titled “Route-eval view of prior node results”When a prior node declares an output JSON Schema with a non-empty properties block, a conforming executor:
- MUST scope the routing view of that node’s result data to the keys declared in
output.properties. Non-schema fields (free-formsummaryprose, conversational rationale, debug commentary the model adds alongside the structured payload) MUST NOT reach the route evaluator. - MUST preserve the
evalsnamespace on the routing view when present, sowhenconditions can referencepriorNode.evals.<name>.pass. - MUST continue to expose the full prior-node
data(including any non-schema fields) to downstream node prompts via context accumulation. The schema-strict filter is exclusively for route evaluation.
When a prior node has no output schema, or the schema declares no properties block, the executor MUST expose the full data to the route evaluator (back-compatible behavior).
Rationale: a natural-language route evaluator can be swayed by any prose it sees in the context. Declaring an output schema is the author’s contract for “these are the fields that drive routing”; the runtime treats the schema as authoritative for routing without changing what downstream nodes can read.
Nested properties
Section titled “Nested properties”The schema-strict filter is top-level. If a declared property is itself an object (type: "object" with nested properties), the entire nested value passes through unchanged. The filter does not recurse. Authors who need to hide internal fields of a nested object MUST omit them from the parent object’s output, not rely on this filter.
Terminal Nodes
Section titled “Terminal Nodes”A node with zero outgoing edges (after filtering exhausted edges) is a terminal node. Execution ends when a terminal node completes.
A workflow MUST have at least one reachable terminal node (otherwise it would loop forever or have an unbounded cycle, which is structurally invalid).
Bounded Cycles
Section titled “Bounded Cycles”The max_iterations field limits how many times an edge can be followed, enabling controlled retry loops.
edges: - from: review to: fix when: review found issues that need fixing max_iterations: 3 - from: review to: done when: review passed, no issues found - from: fix to: reviewA conforming executor:
- MUST track how many times each edge has been followed (keyed by
from/topair). - MUST exclude an edge from routing once it has been followed
max_iterationstimes. - If excluding an exhausted edge leaves zero remaining edges, the node becomes terminal.
Self-Loops
Section titled “Self-Loops”A self-loop is an edge where from equals to. Self-loops MUST have max_iterations set. A workflow containing a self-loop without max_iterations is structurally invalid.
edges: - from: retry to: retry when: operation failed and retries remaining max_iterations: 3 - from: retry to: done when: operation succeededUnbounded Cycles
Section titled “Unbounded Cycles”A cycle in the graph where no edge in the cycle has max_iterations is invalid. A conforming validator MUST detect and reject unbounded cycles.
The detection algorithm: remove all edges that have max_iterations set (and all self-loops), then check the remaining subgraph for cycles using depth-first search. If a back-edge is found, the cycle is unbounded.
Examples
Section titled “Examples”Linear Flow
Section titled “Linear Flow”edges: - from: gather to: investigate - from: investigate to: notifyExecution: gather → investigate → notify (terminal).
Conditional Branching
Section titled “Conditional Branching”edges: - from: investigate to: create_issue when: novel_count is greater than 0 AND highest_severity is medium or higher - from: investigate to: skip when: novel_count is 0, OR highest_severity is low - from: create_issue to: notify - from: skip to: notifyAfter investigate completes, the AI model evaluates both conditions against the investigation results and selects the matching branch. Both branches converge at notify.
Bounded Retry Loop
Section titled “Bounded Retry Loop”edges: - from: implement to: test - from: test to: implement when: tests failed max_iterations: 3 - from: test to: done when: all tests passedThe test → implement edge can be followed at most 3 times. After 3 retries, the edge is excluded. If all tests passed also doesn’t match, the node becomes terminal.