Run Context

Last updated on October 21, 2026

The run context tells a procedure where it runs: the slot, the station, the procedure and the deployment. A phase reads it on its run parameter, and procedure.yaml reads the same fields through {run.*} markers.

Fields

FieldValue
run.slot.keyThe slot's key from execution.slots. None in a phase that runs once for every slot.
run.slot.nameThe slot's display name, or its key when it has none.
run.station.idThe station's ID. None on a source run.
run.station.nameThe station's name. On a source run, the machine's hostname.
run.procedure.idThe procedure's ID. On a source run, set only when the directory is linked.
run.procedure.nameThe procedure's name.
run.procedure.versionThe version: of procedure.yaml.
run.deployment.idThe deployment the station runs. None on a source run.
run.deployment.environmentproduction, preview or development. Source and Studio runs are development.
run.deployment.commitThe deployed commit SHA.
run.deployment.branchThe branch the deployed commit is on.

A field the CLI does not know reads as None. Every field is read-only.

In Python

phases/report.py
def report(run, log):
    # Read-only context of this run
    log.info(f"{run.station.name} / {run.slot.name} on {run.deployment.environment}")
    if run.retry.count > 0:
        log.warning(f"Retry {run.retry.count} (limit {run.retry.limit})")
    run.operated_by = "jdoe"  # the only writable field, with run.metadata

Besides the fields above, run carries run.retry.count (0 on the first attempt) and run.retry.limit, plus the writable run.operated_by and run.metadata.

In procedure.yaml

{run.<field>} stands for a field in the values that are kept as written until the run starts. A string that is exactly one marker takes the field's value and type; a marker inside a longer string is written in as text.

procedure.yaml
execution:
  slots: 4

plugs:
  - name: Fixture
    python: plugs.fixture:Fixture
    with:
      nest: "{run.slot.key}"
      label: "{run.station.name}-{run.slot.name}"

unit:
  serial_number:
    default_value: "BURNIN-{run.slot.key}"

Where markers are allowed

The markers resolved at run start ({run.*}, {config.*} and {secrets.*}) are allowed only in:

  • with: of a phase or a plug (config: included),
  • env: of the procedure, a phase or a plug,
  • a unit field's default_value, and the root operated_by:'s.

Anywhere else they stay as written, with a warning: `{run.slot.key}` is kept as text: markers resolve only in `with:`, `env:` and a unit field's `default_value`. Typed fields such as limits, timeout and retry are checked when the procedure loads, before these values exist.

A sequence call's with: is resolved when the procedure loads, so it cannot pass them either. Declare the key in the sequence file and read it there.

{inputs.*} markers of a sequence are resolved at load and work in any field of the sequence file.

Braces naming any other namespace are text, so a plug keeps its own format strings ("{record.levelname}: {record.message}"). A name close to a marker namespace, such as {configs.MES_URL}, gets a warning that suggests the marker.

Slot fields

{run.slot.*} needs a slot. It is a load error where one instance serves every slot:

PlaceWhy it has no slot
The procedure's env:Shared by every process of the run
with: or env: of an execution or station plugOne instance serves every slot, or every run
A setup or teardown phase with scope: executionRuns once for all slots

An unknown field is a load error that lists the valid ones and suggests the closest, for example unknown field `{run.slot.nme}`… did you mean `{run.slot.name}`?.

Old names

The names below still work and nothing is scheduled to remove them. In procedure.yaml, the old spellings load with a deprecation warning at their line; the old Python attributes are kept without one.

OldNew
{slot}{run.slot.key}
{slot_name}{run.slot.name}
run.slot_idrun.slot.key (<shared> in a phase that runs once for every slot)
run.retry_countrun.retry.count
run.retry_limitrun.retry.limit
config: on a plugwith:

{slot} and {slot_name} are aliases only where run-start markers resolve. Elsewhere, for example in a phase description, and under a plug's old config: key, they stay plain text as before. Rename config: to with: to have them resolve.

Environment variables

env: sets environment variables on the processes of the run. Set it on the procedure, a phase or a plug:

procedure.yaml
env:
  MES_URL: "{config.MES_URL}"
  LOG_LEVEL: debug
  PORT: 8080 # read as the text "8080"

plugs:
  - name: DMM
    python: plugs.dmm:Dmm
    env:
      VISA_LIBRARY: "@py"

main:
  - name: Flash
    executable: ./flash.sh
    env:
      FLASH_BAUD: "921600"
LevelApplies to
Procedure env:Every phase worker and plug process of the run
Plug env:That plug's process, over the procedure's
Phase env:That phase while it runs (its module import included), over the procedure's; restored after

Values are text: a number or a boolean is read as its text. Markers work as in with:, except {run.slot.*} in the procedure's env:. Keys are letters, digits and _, not starting with a digit, and TOFUPILOT_* is reserved.

A station plug serves runs of several procedures, so it only receives the procedure's env:. A plug env: on it is a load error.

{secrets.X} in env: loads with a warning: every child process inherits the environment. Pass a secret through with: or read secrets in the phase instead.

How is this guide?

On this page