Routers control how execution flows through the workflow graph. When a router is reached, it decides which downstream node(s) to activate next. There are four routing modes, each suited to different orchestration patterns.
fan_out_all — run all downstream branches in parallelcondition — pick one branch based on a boolean field from a previous noderound_robin — cycle through branches in order, one per traversalllm — let an LLM decide which branch(es) to takerouter <name>:
mode: fan_out_all | condition | round_robin | llm
LLM routers accept additional properties:
router fix_router:
mode: llm
model: "anthropic/claude-sonnet-4-6" # or backend: "claude_code"
system: routing_prompt # optional prompt ref
user: user_prompt # optional prompt ref
multi: true # select multiple routes (default: false)
Using model makes a direct API call (via the in-process claw backend — supports anthropic/..., openai/..., etc.). Using backend routes through an external CLI agent — claude_code is the recommended CLI backend. (codex is also supported but discouraged; see Delegation for the rationale.) If neither is set, the engine falls back to a built-in default model.
fan_out_all — parallel dispatchThis is the default mode. The router sends execution to every outgoing edge simultaneously. Each target runs in its own branch, and branches converge at a downstream node that declares await: wait_all or await: best_effort.
router review_fanout:
mode: fan_out_all
agent synthesize_reviews:
model: "anthropic/claude-sonnet-4-6"
user: synthesize_prompt
await: wait_all
workflow example:
...
review_fanout -> claude_review
review_fanout -> gpt_review
claude_review -> synthesize_reviews
gpt_review -> synthesize_reviews
synthesize_reviews -> done
The router itself is a pass-through — it forwards its input unchanged to all targets. The number of concurrent branches is bounded by the max_parallel_branches budget setting. For workspace safety, only one mutating branch (an agent or human with tools) is allowed at a time; read-only branches can run freely in parallel.
condition — boolean branchingA condition router picks a single target based on boolean fields in the upstream node’s output. The routing logic is expressed on the edges, not in the router itself.
router decision:
mode: condition
workflow example:
...
judge -> decision
decision -> fix_agent when not approved
decision -> done when approved
When the judge node produces { "approved": true }, the edge decision -> done is taken. When approved is false (or absent), the when not approved edge matches instead. If no conditional edge matches, the first unconditional edge is used as a fallback.
Note: Condition routing is syntactic sugar — the same
when/when notevaluation happens after every node, not just routers. The condition router makes the branching intent explicit in the graph.
round_robin — cyclic alternationEach time the router is traversed, it selects the next outgoing edge in declaration order, wrapping around after the last one.
router refine_selector:
mode: round_robin
workflow example:
...
val_judge -> refine_selector when not ready as refine_loop(4)
refine_selector -> claude_refine
refine_selector -> gpt_refine
| Traversal | Selected target |
|---|---|
| 1st | claude_refine |
| 2nd | gpt_refine |
| 3rd | claude_refine |
| 4th | gpt_refine |
The counter persists across pause/resume cycles — if a run is paused and later resumed, the alternation picks up where it left off. This mode is ideal for alternating between agents from different providers (e.g. a claude_code-delegated Claude and a claw-direct OpenAI model) in a refinement loop, avoiding the need to duplicate nodes.
llm — AI-driven routingAn LLM reads the workflow context and decides which route to take. This is the only mode that makes an LLM call.
["fix_code", "fix_docs", "fix_tests"]).{ "selected_route": "fix_code", "reasoning": "..." }{ "selected_routes": ["fix_code", "fix_tests"], "reasoning": "..." }fan_out_all, but only for the subset chosen by the LLM).prompt routing_prompt:
Based on the review findings, decide whether
the code, the docs, or the tests need fixing.
router fix_router:
mode: llm
model: "anthropic/claude-sonnet-4-6"
system: routing_prompt
workflow example:
...
fix_router -> fix_code
fix_router -> fix_docs
fix_router -> fix_tests
With multi: true, the LLM can select several routes at once. Selected targets run in parallel and converge at a downstream node that declares await: wait_all or await: best_effort.
router fix_router:
mode: llm
backend: "claude_code"
system: routing_prompt
multi: true
workflow example:
...
fix_router -> fix_code
fix_router -> fix_docs
fix_router -> fix_tests
fix_code -> verify_fixes
fix_docs -> verify_fixes
fix_tests -> verify_fixes
agent verify_fixes:
model: "anthropic/claude-sonnet-4-6"
user: verify_prompt
await: wait_all
When using model, the engine resolves the model identifier through this chain:
model field value (with environment variable expansion)ITERION_DEFAULT_SUPERVISOR_MODEL environment variableanthropic/claude-sonnet-4-6When using backend, the named backend (e.g. claude_code) handles the LLM call entirely — no API key or model configuration needed on the Iterion side. (codex is also accepted but discouraged.)
awaitParallel branches — whether from fan_out_all or llm multi-mode — converge at a real downstream node (agent, judge, human, tool, or compute) with multiple incoming edges. That target node declares await: wait_all to require every branch, or await: best_effort to continue with successful branches while tolerating failures.
Routers are fan-out sources and do not declare await: themselves.
The compiler catches common mistakes at compile time:
model, backend, system, user, or multi on a fan_out_all, condition, or round_robin router is an error.model nor backend is set, a warning is emitted (the built-in default model will be used at runtime).