A run is one execution of a procedure. It is launched by a declarative run request that binds each role of the procedure to a target, in an environment, with values for its inputs. The request is resolved against the current compiled configuration into a self-contained resolved run, which the executor interprets while logging every transition. The log is the run: its state, its evidence and its report all derive from it.
The run request#
# examples/config/runs/hot-standby.yaml
run: Hot standby test
environment: AIT
targets: {sat: sim-1, psu: psu-sim-1}
inputs: {tcu: TCU2, bus_voltage: 28 V}| Key | Required | Meaning |
|---|---|---|
run | yes | The procedure |
library | when several libraries have a procedure of that name | The library of the procedure (run::ambiguous-procedure otherwise) |
environment | yes | The environment of the topology the run is engaged in |
targets | yes | The target of each role: exactly one per role |
inputs | when the procedure has inputs | The value of each input |
plan | no | The pass the run is bound to; set by the scheduler for a scheduled run |
faults | no | Faults of the simulations to switch on during the run, simulated targets only: see Faults scheduled by a run |
Input values#
Values are written as in YAML, in the type of the input:
| Input type | Value |
|---|---|
Quantity (f32 V) | With its unit: 28 V, or 28000 mV, converted into the unit of the input |
| Number without unit | 3, 27.5 |
| Integer | A whole number within the bounds of its type |
bool | true, false |
| Enum | A value of the enum: TCU2 |
bytes | Hexadecimal: 0a1b or 0x0a1b |
file_id of <type> | The on-board identifier of the file: 12 |
file | The SHA-256 of a content stored with stellar put or POST /v1/uploads |
Launching a run#
stellar run hot-standby.yaml # launch, then follow the log to the end
stellar run hot-standby.yaml --detach # print the run identifier and returnstellar run sends the request to the API (POST /v1/runs), prints the warnings of the reply,
then follows the log until the run finishes. It exits with 0 when the procedure succeeds, 1
otherwise, which makes it usable in scripts. Like every command that talks to the API, it takes
--api, --as, --role, --token and --api-ca (see the CLI Reference).
The same request, as JSON, launches a run through the API:
curl -X POST http://localhost:8080/v1/runs -H 'X-Stellar-User: alice' \
-d '{"run": "Hot standby test", "environment": "AIT",
"targets": {"sat": "sim-1", "psu": "psu-sim-1"},
"inputs": {"tcu": "TCU2", "bus_voltage": "28 V"}}'{"run": "01K6A3D3Y4Q8K1P9W2N6R5T7XZ", "ir": "5c1f…", "warnings": []}The API resolves the request, stores the resolved run in stellar_ir under its hash (ir),
submits it to the executor on stellar.run.submit with the identifier of the run as message id,
and answers 202 Accepted. See the runs API.
What is checked at launch#
Resolution: 422#
The request is resolved against the current snapshot, with every error at once:
| Code | Refused because |
|---|---|
run::unknown-environment | The environment does not exist |
run::unknown-procedure, run::unknown-library, run::ambiguous-procedure | The procedure cannot be found, or not uniquely |
run::not-allowed | The procedure is not allowed in this environment |
run::draft-not-allowed | The library, or the catalogue of a target, is a draft in an environment without allow_draft |
run::missing-target, run::unexpected-target | A role without target, or a target for no role |
run::unknown-target | The target does not exist |
run::wrong-platform | The target does not implement the platform of its role, nor imports components of it |
run::catalogue-version | The target implements (or imports) another version of the platform than the one the library was compiled against |
run::not-imported | The procedure uses a component of its role's package that the platform of the target does not import |
run::target-not-in-environment | The target is not engaged in this environment |
run::missing-input, run::unexpected-input, run::invalid-input | An input without value, an unknown input, a value of the wrong type or unit |
run::unknown-link, run::link-component | A link of via or link[…] that the target does not have, or that does not carry the component |
run::fault-target, run::fault-step, run::fault-not-simulated | A scheduled fault names no role of the run, or a step it never enters, or is requested in in_orbit: see Faults scheduled by a run |
run::plan-required | The procedure sends a hazardous telecommand, the environment requires a plan, and the request has none |
run::must-be-scheduled | Same, with a plan: such a run starts only from a schedule (POST /v1/schedules) |
run::parameter-undefined | A configure … for <mode> or param … in <mode> reads a parameter that has no value on the target in that mode (see Modes and Parameters) |
Identity: 401#
The environment of the run decides who may launch it: anyone without human orchestration, a
declared identity or a token with identity: declared, a verified OIDC token with
identity: jwt (api::anonymous, auth::jwt-required, auth::invalid-token). See
Identity and Roles.
Passes: 409#
| Code | Refused because |
|---|---|
run::unknown-pass (422) | plan names no pass of a target of the run |
run::pass-cancelled | The pass of plan is cancelled |
run::not-booked | In in_orbit, the pass of plan is not booked |
run::before-aos | The pass has not started yet |
run::window | The run may end after LOS minus scheduler.margin |
run::receive-only | The pass has no uplink and the run sends telecommands |
run::no-link | A manual run in in_orbit while the default link of a target is not bound, or its gateway reports no link |
A manual run in in_orbit that may outlast a pass in progress is accepted with a warning in
warnings, which stellar run prints. See Passes and Scheduling.
At start, by the executor#
The executor logs started, then checks that every target is ready (its links bound, see
Links and Bindings) and takes a lease on every target. A target not
ready, or held by another run, ends the run at once, failed, with the reason (the holder is
named). See Leases and Crash Recovery.
How a run proceeds#
sequenceDiagram
participant O as Operator
participant A as API
participant E as Executor
participant T as Targets
O->>A: POST /v1/runs (request)
A->>A: resolve, check policies and passes
A->>E: stellar.run.submit {run, ir, by}
A-->>O: 202 {run, ir, warnings}
E->>E: started, readiness, leases
loop each action
E->>T: telecommands (acknowledgement chain)
T-->>E: acknowledgements, samples
E->>E: log the events
end
E->>E: finished, leases released, report archived- The actions of the procedure run in order; a failure skips the actions up to the next
if failedblock, which runs, and the procedure ends failed (seeif failed). - Within a step, the first statement that fails ends the attempt; retries follow the retry policy.
- Every telecommand carries the run and the step in its
PENDINGevent, and the environment of the run. - At the end, the leases are released and the executor archives the report (see Evidence and Reports).
The run log#
Each event of the log is published on stellar.run.evt.<run_id> (stream RUNS), with its
position seq from 1, its time at, and its kind event. Its message id <run_id>-<seq> makes
a republished event a duplicate. Steps and procedures are designated by their path, their
names separated by /, an unnamed inline step noted #<n>:
Hot standby test / Check TCU / TCU answers.
| Event | Fields | Meaning |
|---|---|---|
started | procedure, library (name@version), environment, targets, ir, by, inputs | The run starts; inputs are the resolved values |
procedure_started | path | A procedure, or sub-procedure, starts |
procedure_finished | path, ok | It ends |
step_started | path, attempt, inputs | An attempt of a step starts, with its resolved inputs |
telecommand_sent | path, tc, target, telecommand (component[instance].name), changes_state, retry_of | A telecommand is about to leave: its identifier is logged before |
telecommand_finished | path, tc, state, detail, acks | Its final state and its acknowledgement chain |
checked | path, statement (expect, check, wait_until), condition, ok, reason, samples | A condition judged, with the samples it was judged on |
logged | path, message, samples | Values written by a log: the text with each value in its place (unknown when missing, (stale) when too old), and the samples read |
asked | path, prompt, role | A question to the operator; role for a hazardous confirmation |
answered | path, accepted, by, value, role | Its answer; no by when acknowledged automatically |
answer_refused | path, by, reason | An answer not taken |
mode_changed | path, target, mode | A configure succeeded: the target is now in mode (see Modes and Parameters) |
decision_required | path, reason | The run waits for replay, skip, fail or abort |
suspended | path, by | Suspended by an operator; leases released |
continued | path, by, choice | Resumed, with the choice of the operator |
taken_over | by | Another executor instance took the run up |
step_finished | path, attempt, ok, reason | An attempt ends |
finished | ok, reason | The run ends |
{"run": "01K6A3D3Y4Q8K1P9W2N6R5T7XZ", "seq": 7, "at": "2026-10-02T10:15:04.211Z",
"event": "checked", "path": "Hot standby test / Power the PPU", "statement": "expect",
"condition": "psu.output_enabled is true", "ok": true,
"samples": [{"target": "psu-sim-1", "component": "psu", "measure": "output_enabled",
"value": true, "time": "…", "ground_time": "…"}]}by in started is the identity of the requester: the operator, schedule <id> for a run fired
by the scheduler, alarm <key> for an alarm reaction.
Following a run#
stellar watch <run> # replay the log, then follow it to the end
stellar status <run> # the state, oncestellar watch recognizes a run identifier (a ULID) from a target name. It prints one line per
event and, when the run waits for someone, the command expected:
2026-10-02T10:15:01Z started « Hot standby test » in AIT (sat=sim-1, psu=psu-sim-1) by alice
2026-10-02T10:15:01Z step Hot standby test / Power the PPU
2026-10-02T10:15:01Z send psu.set_voltage to psu-sim-1
2026-10-02T10:15:02Z → VERIFIED
2026-10-02T10:15:04Z expect psu.output_enabled is true: PASSED
2026-10-02T10:15:04Z PASSED
…
2026-10-02T10:15:21Z finished: PASSEDThrough the API, GET /v1/runs/{id}/events is a WebSocket that replays the log, follows it and
closes after finished (see WebSocket Streams).
State of a run#
stellar status <run> and GET /v1/runs/{id} compute the state from the log:
| Field | Meaning |
|---|---|
procedure | The procedure |
step | The step running, or the last one |
suspended | Whether it is suspended |
pending | A question (kind: ask, with its prompt and the role awaited) or a decision (kind: decision) waiting for an operator |
finished, reason | The outcome once finished, and why it failed |
run 01K6A3D3Y4Q8K1P9W2N6R5T7XZ « Blank firing »
step: Blank firing / Fire the blank
running
waiting for an answer (ask) on Blank firing / Fire the blank: Fire the blank on the TCU?GET /v1/runs?limit=N lists the most recent runs, newest first (20 by default, 200 at most),
with their environment, targets, requester, start and last event; the web console shows them.
From Python#
The Python SDK has an API client that launches a run, follows its events, answers its questions
and fetches its report from a script: see Python SDK. stellar generate client writes typed functions for the procedures of a library, one per procedure, on top of that
client: see Code Generators.
Next#
- Answer questions and decisions, suspend, resume and abort: Questions, Decisions and Control.
- Evidence and test reports: Evidence and Reports.
- Hazardous telecommands and supervisors: Hazardous Confirmations.