Skip to content
Orbital

Add branches

Send a run to different steps with routes that test what it has learned

Send a run to different steps depending on what it has just learned. Give the step a list of routes, each with a condition and a step to go to, and the run follows the first one whose condition holds.

conditional.toml
[steps.classify]
prompt = "If the goal asks for a greeting, choose greet. Otherwise choose explain."
outputs.route = { kind = "choice", options = ["greet", "explain"] }
routes = [
{ if = "route == 'greet'", to = "steps.greet" },
{ if = "route == 'explain'", to = "steps.explain" },
]

Routing lives on the step that decides, not in a separate decision step. routes is an ordered list of tables. Each route has an if, the condition, and a to, the step to go to. next is the fallback the run takes when no route holds. A route without if is refused, because that is what next is for.

When a step succeeds, Orbital tries its routes in the order you wrote them and takes the first whose condition holds. Only then does it take next. Put the most specific route first.

A condition compares one context value with a literal, such as verdict == 'pass', verdict != 'pass' or attempts == 3. It can also test a value on its own, such as git.worktree.clean, or its negation, !git.worktree.clean. The context. prefix is optional, and an input reads by its name, such as mode == 'quick'. You cannot join conditions with and or or or compare two context values, so every route stays readable in the file. Conditions lists every form a condition takes.

The workflow editor canvas shows arrows leaving an agent step, each labelled with its condition, such as verdict == 'reject', and a dashed arrow labelled else.
Each route shows its condition; the dashed else arrow is the fallback.

Decide first what produces the value you branch on. The answer changes the cost and the reliability of the branch.

  • Use a probe step when Orbital already observes the fact. The ticket workflow routes on github.pr.state without asking an agent whether a merge happened.
  • For another exact observation, use a command step with declared bool, number or text facts, such as facts.tests.pass = "bool". Its last line of output must be the JSON object exactly as declared. Keep the script read-only when it only inspects something.
  • For judgement, give an agent a choice output. Orbital refuses any answer outside its options before it reaches context. So outputs.verdict = { kind = "choice", options = ["pass", "reject"] } accepts nothing else.

A probe or a script gives an exact answer at no agent cost. The example below branches on judgement, so an agent makes the choice.

A choice can describe each option, which helps the agent pick the right one. Give options as a table of values and descriptions in the output’s own table; the options keep the order you write them in:

[steps.pick.outputs.next]
kind = "choice"
options.ticket = "A ticket is ready to ship"
options.none = "Every ticket is shipped"
options.blocked = "The remaining tickets wait on people"

A choice without options offers the values its routes test.

To let a small decision model make the choice instead, with a default route when it cannot, see Decide with a model.

A branching step needs next, unless its routes answer every option of the choice output they test. A run where nothing matches stops with an error, so validation asks for next before the run starts.

You need a harness you have signed in to, and a project in Orbital. Any project works. It does not need to be a Git repository, unless you run the example with Codex, which needs one. See Projects and folders.

Download the example and unpack it into ~/.orbital/workflows/. Keep every relative path.

classify selects greet or explain; both reach done.

conditional.toml
version = 3
entry = "steps.classify"
[steps.classify]
prompt = "If the goal asks for a greeting, choose greet. Otherwise choose explain."
outputs.route = { kind = "choice", options = ["greet", "explain"] }
routes = [
{ if = "route == 'greet'", to = "steps.greet" },
{ if = "route == 'explain'", to = "steps.explain" },
]
[steps.greet]
prompt = "Write a friendly greeting."
next = "steps.done"
[steps.explain]
prompt = "Explain the goal in one sentence."
next = "steps.done"
[steps.done]
kind = "terminal"

classify declares route as a choice of greet and explain, and Orbital refuses any other answer before it reaches context. Its routes are tried in order, and between them they answer both options, so this step needs no next. greet and explain each name done as their next.

Download conditional.toml

Terminal window
orbital validate ~/.orbital/workflows/conditional.toml

Choose New task, pick any project, and choose the conditional workflow and your harness. One project serves every example. Use a greeting as the goal to select greet, or any other goal to select explain.

Exactly one response step runs before done. The run’s Context tab shows each value the run holds and the step that set it.

The run page's Context tab lists the values held by the run, each with the step that set it on the right.
The Context tab shows which step set each value.