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

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

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

## Inputs and defaults

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.

```toml
[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](/reference/workflow-file/#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.*`.

## Agent outputs

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

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

That call fits these declarations:

```toml
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](/reference/error-handling/). 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`.

## Command facts

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.

## Probe and action records

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

### Required checks

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.

## Step outcomes

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.

## Availability

The validator checks that a value is produced on every path leading to the step that reads it. A step's [`needs`](/reference/workflow-file/#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.

## Related

- [Add branches](/workflows/add-branches/): how routes read these values.
- [Template variables](/reference/template-variables/): how prompts read these values.
- [Pass context between steps](/workflows/pass-context/): outputs feeding later steps, in a worked example.
- [Workflow file](/reference/workflow-file/#complete-example): a complete workflow that validates.
