Skip to content
Orbital

Context

What run context holds, where each value comes from, and when it is available

Look up what a run’s context holds, where each value comes from and when a step can read it. Routes and prompts read context by dotted path.

review.toml
[steps.review]
kind = "probe"
observe = "github.pr"
routes = [{ if = "github.pr.state == 'MERGED'", to = "steps.merged" }]
next = "steps.wait"

Context holds the goal, the declared inputs, accepted agent outputs, command facts, probe and action records, and each step’s outcome. context.github.pr.state and github.pr.state read the same value.

Each [inputs.<name>] table declares an input. An input is required unless it sets required = false or a default. Names are lowercase snake_case and cannot occupy the namespaces Orbital and its probes write. goal is built in; [inputs.goal] may set only label, hint and default. Starting a run requires a goal and every required input, and rejects undeclared inputs. An absent optional value renders empty.

[inputs.notes]
kind = "text"
label = "Notes"
hint = "Anything the plan should take into account"
default = "Keep it short"

label and hint give the input the label and hint the new-run form shows. An input without a label shows its name. A value given when the run starts replaces the default. Input kinds lists every kind and its keys.

A ticket or epic input is looked up when the run is created, so a ticket Orbital cannot read refuses the run rather than failing a step later. Templates then read its fields, such as {{ inputs.ticket.title }} or {{ inputs.epic.body }}, and {{ inputs.ticket }} on its own renders the reference the run was given. The run keeps the fields under internal.inputs.<name>, so a loop restart keeps them with the original inputs.

Inputs are copied into context when the run starts, so {{ context.work }} can later change while {{ inputs.work }} keeps the original. Loop restart restores the original inputs. Routes read context only, never inputs.*.

An agent step declares its outputs under outputs and finishes by calling the handoff tool with them, for example:

{"verdict":"pass","findings":[]}

That call fits these declarations:

outputs.verdict = { kind = "choice", options = ["pass", "reject"] }
outputs.findings = "json"

Orbital gives the agent the exact output contract and accepts only declared keys with matching types.

Kind Contract
choice One of the declared options. Without options, one of the values the step’s routes test.
text A non-empty string. The editor calls it Paragraph.
short_text A non-empty string with no line break.
list An array of objects, each with exactly the list’s item fields, stored as JSON text in context. It must hold an item unless it sets allow_empty = true.
number A number, kept as a number in context, so size == 3 compares it.
yes_no true or false, kept as a boolean in context, so risky == true compares it.
json Any JSON value, unchecked, stored as JSON text in context.
work A work scope, one of the four kinds below.
ticket A ticket URL that Orbital checks it can read. Later templates read its fields like a ticket input.

Work scopes are Leaf with a ticket URL and a Parent or None parent, Hierarchy with a ticket and a hierarchy, Candidate with a description and evidence, or Goal with goal text. Required descriptive values are non-empty. Ticket URLs use HTTP or HTTPS, and a leaf cannot be its own parent. {{ current_work }} renders the saved scope for shared delivery prompts. A missing scope does not silently fall back to a previous ticket.

Malformed output triggers up to two correction turns in the same session. If correction fails, the step fails and follows failure routing. Context changes only after acceptance. The final prose stays available as {{ context.response.<id> }}, where a grouped or imported step’s id is dotted, such as context.response.reviews.clarity.

A command step writes exactly the dotted names its facts table declares, plus <id>.failed. It cannot overwrite the paths probes and actions write or the failure paths.

A probe step writes per-folder facts under the folder name, such as web.github.pr.raw. Selected pull request and thread facts also roll up, including github.pr.state and github.threads.unresolved. <folder>.github.threads.open holds only the unresolved review threads as readable text, so quote it rather than <folder>.github.threads.raw in a prompt that addresses feedback; the raw value keeps every thread, resolved ones included. git.diff and git.worktree facts stay per folder. git.change and tracker.tickets are read once for the run.

Probe Writes
github.pr github.pr.state, github.pr.checks_green, github.pr.checks_total, github.pr.checks_pending, github.pr.checks_failing, github.pr.required_checks_*, and per folder <folder>.github.pr.raw, <folder>.github.pr.head, <folder>.github.pr.url and <folder>.github.pr.new_feedback, the open review feedback no step has handled yet, with each comment’s author.
github.threads github.threads.unresolved, and per folder <folder>.github.threads.open and <folder>.github.threads.raw.
git.diff Per folder <folder>.git.diff.*, such as <folder>.git.diff.changed_lines.
git.change git.change.diff and git.change.unpublished.
git.worktree Per folder <folder>.git.worktree.*.
tracker.tickets tracker.tickets.text, tracker.tickets.total and tracker.tickets.unreachable.

Before format version 3 these facts had no probe name in front, such as pr.state and <folder>.diff.raw. Those old names still work as aliases in conditions, needs and prompts, and the loader warns with the name to write instead. Converting a DOT workflow writes the new names.

The worktree.remove action writes run-wide git.worktree.clean and git.worktree.status. The github.pr.open action writes github.pr.url, github.pr.number and github.pr.state, the same paths the github.pr probe uses.

Pull request routing prioritises MERGED, CLOSED, CONFLICTS, CI_FAILED, CHANGES_REQUIRED, then WAITING. Across several repositories the rollup takes the worst state in the order CLOSED, CONFLICTS, CI_FAILED, CHANGES_REQUIRED, WAITING, MERGED, and reports MERGED only when every observed repository has merged. A pull request that merges or closes while the probe reads it is observed once more and routed as MERGED or CLOSED instead of failing the probe. CI_FAILED means a check’s job ran and failed. When GitHub never ran a failed check’s job, because of billing or a spending limit, no runner, or a job it did not start or cancelled before any step ran, the probe takes no route. The run is Paused on the probe step and shows “Waiting on you”, with GitHub’s reason and the names of those checks. Fix the cause on GitHub, then resume the run to measure the pull request again. Raw pull request evidence gives repair prompts the check details; open review feedback is in <folder>.github.threads.open, and every thread in <folder>.github.threads.raw.

For each folder, the github.pr probe’s github.pr.checks_total, github.pr.checks_pending, github.pr.checks_failing and github.pr.checks_green describe required checks only. The corresponding github.pr.required_checks_* summaries are exact aliases. The top-level roll-ups combine the repositories’ results, with failing and pending counts becoming booleans. Optional checks remain in raw diagnostics and never affect these summaries or the delivery state.

A readable observation with no required checks sets github.pr.required_checks_observed=true and github.pr.required_checks_settled=true, with totals and pending and failing counts at zero. The green flags remain false because no check passed. If the probe falls back to Actions workflows, requirement information is unknown. It sets github.pr.required_checks_observed=false, omits the summary families and does not claim settlement. Raw output retains workflow diagnostics and the unknown requirement status.

After each step, Orbital writes the step’s outcome to outcome, as succeeded or failed, with failure_reason empty on success. Both are readable from routes and prompts.

The validator checks that a value is produced on every path leading to the step that reads it. A step’s needs can give a value a default, so a path that does not produce it still validates. A branch cannot read a sibling’s outputs, or the results the previous round of its fan-out left. After the join, read branch values through parallel.results, whose entries carry node, outcome, failure_reason and context, in the order of the fan-out’s child steps. Numeric dotted segments address them, for example parallel.results.0.context.assessment; in templates the index is quoted.