> For the index of every page in Orbital's docs, read https://docs.beta.runorbital.dev/llms.txt.

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

```toml title="decide.toml"
[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

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 `decider` names 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

A decide step is a step with `kind = "decide"`. It declares:

- `state`, or `state_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 a `kind` of `choice`, `score` or `noul` (yes or no), and an optional `description`.
- For a `choice`, one `options.<value>` per option. For a `score`, one `levels.<value>` per level.
- Optionally `decider`, to name the model, and `threshold`, 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

`{{ 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

A step's `decider` names a [decision model you set up in Settings](/settings/decision-models/). 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

A [fan-out](/reference/step-kinds/#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 `choice` question, the option the model picks starts every branch listed under it.
- With several `noul` questions, 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 `default` branches 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

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

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

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](/settings/decision-models/).

## 1. Download the example

[Download the example](/decide-review.zip) and unpack it into `~/.orbital/workflows/`. Keep every relative path.

```toml title="decide-review.toml"
version = 3
entry = "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.7
default = "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 = 2
next = "steps.ready"

[steps.done]
kind = "terminal"
```

[Download decide-review.toml](/examples/decide-review/decide-review.toml)

`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

```sh
orbital validate ~/.orbital/workflows/decide-review.toml
```

## 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

```toml title="deciding-fan-out.toml"
version = 3
entry = "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](/examples/deciding-fan-out/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

- [Decide](/reference/step-kinds/#decide) and [Fan-out](/reference/step-kinds/#fan-out): every key of the two kinds.
- [Set up decision models](/settings/decision-models/): Jev, Clef, Laya and Kev.
- [Workflow file](/reference/workflow-file/): every key a workflow and its steps take.
- [Add branches](/workflows/add-branches/): route on a value an agent or a command produced.
