Decide with a model
Route a run with a small decision model instead of an agent turn, and say what happens when it cannot answer
Let a small, fast decision model choose the next step. A decide step asks it a structured question about the run and follows the route its answer selects. A deciding fan-out asks it which branches to start. Neither spends an agent turn.
[steps.ready]kind = "decide"state = "Change: {{ diff }}"routes = [{ if = "verdict == 'ship'", to = "steps.done" }]default = "steps.fix"
[steps.ready.questions.verdict]kind = "choice"options.ship = "The change is complete and correct."options.rework = "The change is incomplete or wrong."The default path
Section titled “The default path”Every decide step and every deciding fan-out must name a default path in default: the one step, or the branches, that a run takes when no answer can be used. Validation refuses a step without one. The default path is taken when:
- no decision model is configured, or the step’s
decidernames one that is not; - the model cannot be reached, refuses the request or answers with something unusable;
- any answer’s confidence is below the step’s
threshold.
No agent answers in its place, and no other model is tried. A run on a computer with no decision model set up still finishes; every decision takes its default path, and each one says so on the run page.
Ask a question
Section titled “Ask a question”A decide step is a step with kind = "decide". It declares:
state, orstate_file: what the model is asked about. It is a template, like an agent prompt, and reads{{ context.x }}and{{ inputs.x }}.- One or more questions, each a
[steps.<id>.questions.<name>]table with akindofchoice,scoreornoul(yes or no), and an optionaldescription. - For a
choice, oneoptions.<value>per option. For ascore, onelevels.<value>per level. - Optionally
decider, to name the model, andthreshold, from 0 to 1.
The answers go in the run context as <name> and <name>_confidence. A route tests an answer with if, such as verdict == 'ship', and a later step can read the confidence. A noul answer is true or false. A score answers with the value of its most probable level.
The run tries the routes in the order you list them and follows the first whose condition holds. default takes the place of next.
Put the diff in the question
Section titled “Put the diff in the question”{{ diff }} is the run’s change against its base. Only the state of a decide step or a deciding fan-out can read it. When the diff does not fit the model’s context, Orbital cuts it and ends it with [… diff truncated …]. A model with a small context, such as Laya with 512 tokens, sees only the start of a large change, so give it a question that a small part of the diff can answer.
Choose the model and the threshold
Section titled “Choose the model and the threshold”A step’s decider names a decision model you set up in Settings. A step that names none asks the model marked as the default. The threshold is how sure the model must be of every answer. A step that sets none takes the threshold of its model, which starts at 0.5.
The threshold applies to the whole step. If any answer falls below it, the step takes its default path, even if the other answers were sure. A yes or no takes its confidence from the probability of its side. A score takes it from its most probable level.
Start only some branches
Section titled “Start only some branches”A fan-out that declares state and a question decides which of its branches start. choices.<answer> lists the branches each answer starts, every branch is listed under at least one answer, and default lists the branches that start when no decision is made.
- With one
choicequestion, the option the model picks starts every branch listed under it. - With several
noulquestions, each answered yes starts every branch listed under its name. - When the model picks nothing that starts a branch, or its answer cannot be used, the
defaultbranches start.
The fan-in joins only the branches that started. The fan-out records which branches started, and a run restarted at the fan-out starts the same ones.
Read a decision on the run page
Section titled “Read a decision on the run page”Each decide step shows as a Decision card in the run’s Session tab. It lists each question with the answer and its confidence, marks a confidence below the threshold, says why the run took the default path when it did, and names the step it went to and the model that was asked. The run’s Logs tab records the same decision as a DecisionMade event.
A deciding fan-out writes no card; the Logs tab shows the branches it started.
A restart does not ask again
Section titled “A restart does not ask again”The decision is recorded with the step. A run that restarts, or replays its history, reads the recorded decision and takes the same route, even if the model would answer differently now. A step the run reaches again, in a loop or after a retry, is a new visit and asks again.
Before you start
Section titled “Before you start”You need a project in Orbital and one decision model set up in Settings, or you can run the example without one and watch it take its default path. See Set up decision models.
1. Download the example
Section titled “1. Download the example”Download the example and unpack it into ~/.orbital/workflows/. Keep every relative path.
version = 3entry = "steps.implement"
[steps.implement]prompt = "Make the change the goal asks for: {{ inputs.goal }}"next = "steps.ready"
[steps.ready]kind = "decide"state = "Goal: {{ inputs.goal }}. Change: {{ diff }}"decider = "jev"threshold = 0.7default = "steps.fix"routes = [ { if = "verdict == 'ship'", to = "steps.done" },]
[steps.ready.questions.verdict]kind = "choice"description = "Does the change do what the goal asks, and nothing that breaks it?"options = { ship = "The change is complete and correct.", rework = "The change is incomplete or wrong." }
[steps.fix]prompt = "The change is not ready. Compare it with the goal and fix what is missing: {{ inputs.goal }}"max_visits = 2next = "steps.ready"
[steps.done]kind = "terminal"ready asks one choice question about the change. The ship answer goes to done. The default path goes to fix, which also runs when jev answers with less than 0.7 confidence, and then asks again. max_visits bounds that loop.
2. Validate it
Section titled “2. Validate it”orbital validate ~/.orbital/workflows/decide-review.toml3. Run it
Section titled “3. Run it”Start a run of decide-review with a goal. When the run reaches ready, open the Session tab to read the decision card.
Decide which branches start
Section titled “Decide which branches start”version = 3entry = "steps.implement"
[steps.implement]prompt = "Make the change the goal asks for: {{ inputs.goal }}"next = "steps.areas"
[steps.areas]kind = "fan_out"state = "What the change touches: {{ diff }}"decider = "jev"join = "steps.join"branches = ["steps.security", "steps.docs", "steps.tests"]default = ["steps.tests"]choices.security = ["steps.security"]choices.docs = ["steps.docs"]choices.tests = ["steps.tests"]
[steps.areas.questions.security]kind = "noul"description = "The change touches authentication or secrets."
[steps.areas.questions.docs]kind = "noul"description = "The change touches documentation."
[steps.areas.questions.tests]kind = "noul"description = "The change touches tests."
[steps.security]prompt = "Review the change for security problems."outputs.assessment = "text"next = "steps.join"
[steps.docs]prompt = "Review the documentation the change touches."outputs.assessment = "text"next = "steps.join"
[steps.tests]prompt = "Review the tests the change touches."outputs.assessment = "text"next = "steps.join"
[steps.join]kind = "fan_in"prompt = "Summarise the reviews that ran."next = "steps.done"
[steps.done]kind = "terminal"Download deciding-fan-out.toml
areas asks one yes or no question per area, and each area answered yes starts its review. With no usable answer it starts tests alone, because default lists only that branch.
Related
Section titled “Related”- Decide and Fan-out: every key of the two kinds.
- Set up decision models: Jev, Clef, Laya and Kev.
- Workflow file: every key a workflow and its steps take.
- Add branches: route on a value an agent or a command produced.