Sequences
Last updated on October 19, 2026
A sequence is a group of phases written once in its own YAML file and called from a procedure with uses:. Product A runs groups 1, 2 and 3, product B runs groups 2, 4 and 5: each group is a sequence file, and each procedure calls the ones it needs.
A call has its own outcome, and the run page shows its phases nested under it. Stations need CLI 2.2.0 or newer.
Call a sequence
Add a uses: entry to setup, main or teardown, where a phase would go. The value is the sequence file, relative to the file that calls it.
name: Product A
main:
- name: Power On
python: phases.power_on
- uses: sequences/calibration.yaml
- name: Final Check
python: phases.final_checkname: Calibration
main:
- name: Zero
python: phases.zero
- name: Check
python: phases.checkA sequence file holds name, an optional description, inputs, plugs and the lists setup, main and teardown. version, execution, unit and operated_by belong to the procedure and are refused in a sequence file. Python references resolve against the sequence file's directory. A dotted reference with no matching file there is imported as an installed module.
Name and key
A call shows the file's name and takes its key from it, unless it states name: or key:. Its phases are keyed under it: Check above is calibration/check in the dashboard and the run report. In the procedure, depends_on names the call by its key. State a key: on a call that other entries depend on, so renaming the file's name does not re-key it.
Call the same file twice with two keys to run it twice:
main:
- uses: sequences/calibration.yaml
name: Calibration before soak
key: calib_before
- name: Soak
python: phases.soak
depends_on: [calib_before]
- uses: sequences/calibration.yaml
name: Calibration after soak
key: calib_after
depends_on: [soak]Where the file can be
| Form | Example | Resolves against |
|---|---|---|
| Relative path | sequences/calibration.yaml, ../shared/bench.yaml | The directory of the file holding the uses: entry |
| Package path | acme_bench:sequences/calibration.yaml | A Python package installed in the procedure's environment |
- Absolute paths and
~are refused. - A relative path must stay inside what a deployment ships: the procedure directory, or the uv workspace around it. The validator reports a file outside it.
tofupilot deploypacks the procedure directory only and refuses a pack that leaves a sequence file behind. A workspace deploys through a git integration.- A sequence in a Python package has no such limit: it is installed with the procedure's dependencies.
Inputs
A sequence declares the values it needs with inputs:, and the call gives them with with:. An input without a default is required.
name: Calibration
inputs:
target_voltage:
type: number
description: Supply voltage applied during calibration
samples:
type: integer
default: 10
main:
- name: Check
python: phases.check
measurements:
- name: Voltage
validators:
- operator: ">="
expected_value: "{inputs.target_voltage}"main:
- uses: sequences/calibration.yaml
with:
target_voltage: 12.0def check(phase, inputs, measurements):
# inputs.target_voltage is the value the call gave, 12.0 here
# inputs.samples is the default, 10
readings = [read_voltage() for _ in range(inputs.samples)]
measurements.voltage = min(readings)An input's type is string, number, integer or boolean. The validator refuses a value of another type, an input the file does not declare, and a required input left unset.
{inputs.<name>} stands for a value in any field of the sequence file. A field that is exactly one marker takes the typed value, so expected_value above is a number. A marker inside a longer string is written in as text. {slot}, {slot_name} and regex quantifiers like {3} are left alone.
In Python, a phase takes inputs as a parameter and reads inputs.samples or inputs["samples"].
Plugs
Only the procedure creates plugs. A sequence declares the plugs its phases need, and the call binds each one to a plug of the procedure.
name: Calibration
plugs:
- name: Multimeter
key: dmm
python: plugs.Keysight34461A:Keysight34461A
main:
- name: Check
python: phases.check # def check(phase, dmm): ...plugs:
- name: Bench DMM
key: bench_dmm
python: plugs.Keysight34461A:Keysight34461A
main:
- uses: sequences/calibration.yaml
plugs:
dmm: bench_dmmA requirement whose key matches a procedure plug's key binds without an entry. Its python is optional: when given, a bound plug of another class is reported as a warning. config: belongs to the procedure's plug.
Setup, main and teardown
Inside a call, setup runs first, main waits for all of it, and teardown runs after main whatever the outcome, also when the sequence was cut short. Phases of one list run in parallel, ordered by depends_on.
A sequence runs where it is called: from main, once per slot. A call in the procedure's setup or teardown takes scope: slot (the default) or scope: execution, and every phase of the sequence inherits it. scope: inside a sequence file is refused.
setup:
- uses: sequences/bench_warmup.yaml
scope: execution # once for all slots, like a shared setup phaseDependencies
depends_on names entries of the same file. In the procedure, a phase can wait for a call by its key, which means for all of its phases. In a sequence file, phases name each other by their local keys, and never the procedure's phases, so the file stays reusable.
Nesting
A sequence file can call other sequences, up to 8 levels deep. A file that calls itself, directly or through another file, is refused.
Failure and outcome
A failing phase stops its sequence: the remaining phases are skipped and the sequence's teardown runs. The procedure then applies the call's then:, or on_first_failure. A failing nested call stops its parent the same way.
main:
- uses: sequences/calibration.yaml
then:
fail: continue # the procedure goes on to the next phases
- name: Final Check
python: phases.final_checkOn a call, then: takes fail, error and timeout, each continue or stop. A call is not retried. A failure in a setup list always stops its scope, a then: continue included. A phase that asks to stop (phase.stop(), then: pass: stop) ends the run as it does outside a sequence.
The call's outcome follows the run outcome rule applied to its phases, plus SKIP:
| Outcome | If |
|---|---|
| ERROR | A phase of the sequence raised |
| ABORTED | The run was stopped while the sequence ran, or a phase asked to stop |
| TIMEOUT | A phase of the sequence exceeded its timeout |
| FAIL | A phase of the sequence failed |
| PASS | Every phase that ran passed |
| SKIP | No phase ran: the call is disabled, or an earlier failure skipped it |
In the dashboard
The run page nests a call's phases under a collapsible row with its duration and outcome, and the unit page lists each run's top-level calls. Phase analytics identify a phase by its sequence path and name, so calib_before/check and calib_after/check are two phases, with a Sequence filter for procedures that use calls. Measurement Control still groups measurements by phase name across calls.
Layout
Keep sequence files next to the procedures that call them, in a sequences/ folder or a shared folder of the same repository. See Monorepos for a layout shared between several procedures.
How is this guide?