decide step forks the plan: it evaluates a gate — the same gate grammar as an exit gate, spelled if/infer — and runs the then branch when it holds, the else branch otherwise. Execution then rejoins the plan’s normal step sequence. Where exit answers “should this plan stop?”, decide answers “which action is correct next?” — update or create, escalate or summarize, notify or stay quiet.
⑂ junction — the chosen branch’s steps run under its colored guide, the other stays untouched:
A decide fork after a run down its then branch: the inline step list nests under the blue then head; the single-call else was never rendered.
Gates
Exactly one ofif or infer is required — unlike exit, where omitting both means “unconditionally”. The gate grammar is identical to exit gates — decide spells the logical gate if where exit spells it when, because if … then … else reads as the fork it is. if is a logical comparison (eq ne gt lt gte lte empty not_empty contains, with typed splice keeping numbers numeric); infer is a yes/no question judged by the judge model role. An if gate costs zero inference; an infer gate costs one cheap judgment call.
An inferred fork routes on judgment where no single comparison would do — here, whether a status update reads as blocked:
then runs only on a yes, and the model’s reasoning is carried in the step result — {{E2.reason}} — for downstream steps and the finish to use. The verdict mechanics — the forced {verdict, reason} schema, the judge role, and the per-gate model: override (which on decide may itself be a template) — are exactly those of inferred exit gates.
Branches
then is required; else is optional. Each branch is either:
- A single tool call —
{tool_name, input}, no id. Any catalog tool works, includingplan__*andplan_and_execute. - An inline step list — normal steps with ids. They run in authored order, and each may reference plan
input, any earlier top-level step, and earlier steps in the same branch. Branch ids must not reuse top-level ids, and they are invisible outside the branch.
exit steps — a fired exit ends the whole plan from inside the branch (its step in the outcome carries the body path, e.g. E4/then/bail), and a passed gate lets the branch continue to its next step. Branches may also contain agent, ask, and filter steps, with the branch scope reaching the prompt, question, or gate. Branches must not contain decide, map, or reduce. For nested control flow, put it in a plan and call plan__* from the branch — cycle detection and the depth cap apply as usual, and an error-exit inside that sub-plan surfaces as a failure of the decide step.
The step result
The decide step’s result — stored under its own id — is the only thing later steps see:branch— which side ran (nullwhen the gate was false and there was noelse).verdict— the gate outcome;reason— the judge’s explanation, forinfergates.result— the branch’s output: the single call’s result, or the last branch step’s result.
{{E1.result.…}}, {{E1.branch}}), so the rejoined plan looks the same regardless of which branch ran. With no else and a false gate, result is null and the plan simply continues — like a passed exit gate.
Only the chosen branch is rendered
The executor renders the gate first, evaluates it, and only then renders the chosen branch — per step, for inline lists. The non-taken branch’s templates are never evaluated. That’s what makes this pattern safe:{{E0.issues.0.id}} would raise EmptyData on an empty list — the exact case else handles. Deferred rendering means each branch may assume it is the right branch. EmptyData raised inside the chosen branch still degrades normally (errors).
Failure and replanning
A failing branch call fails the decide step — attributed asstep E1 (decide) with the branch and inner tool named in the message. Human-authored plans fail hard as always; planner-authored plans replan with that context.
Branch steps count in steps_executed — a decide with a two-step branch reports three executed steps (the decide plus two).
Semantics summary
plan_and_execute’s planner also has the decide tool, so LLM-authored plans can fork on their own intermediate results.