Execution

Last updated on May 21, 2026

The execution block on a Procedure controls how the runtime schedules Phases across workers and slots, and what happens the moment the first failure shows up. You get four levers (workers, slots, strategy, and on-fail behavior) and together they cover everything from a single laptop bench to a multi-slot production fixture.

For the conceptual model behind these levers, see Execution model.

Fields

The example below sets every field at once, so you can see how the block fits together before drilling into each lever.

procedure.yaml
execution:
  strategy: phase_first
  workers: 16
  on_first_failure: stop
  slots:
    - name: USB0
    - name: USB1
FieldPurposeDefault
strategyHow phases interleave across slots. phase_first or slot_first.phase_first
workersMax phases running in parallel within a slot. 1-256.8
on_first_failureBehavior when a phase reports fail. stop or continue.stop
slotsParallel slots. Each tests one unit.[]

Workers

workers sizes the pool the runtime uses for parallel phases inside a single slot, so phases with depends_on serialize against their dependencies and the pool only matters for independent phases in the same stage.

procedure.yaml
execution:
  workers: 16

Match workers to CPU cores for CPU-bound tests, and bump it to 2-3 times the core count for I/O-bound tests (instrument calls, operator prompts, network calls) because the workers spend most of their time waiting. When every phase serializes on the same instrument, the worker count does not help, because the hardware is the bottleneck.

Slots

A slot is an independent test position, so when you declare multiple slots the same procedure runs in parallel against multiple Units on one bench. Each slot has its own unit identification, phase state, and outcome, and one slot failing does not affect the others.

procedure.yaml
execution:
  slots:
    - name: USB0
    - name: USB1
    - name: USB2
FieldPurpose
nameDisplay name. 1-50 characters. Required.
keyIdentifier for plug/phase binding. Defaults to snake_case(name).

Slots are how production benches test four boards at once on a single fixture, so Plugs with scope: each get one instance per slot while plugs with scope: all are shared across slots.

Configure multi-slot through procedure.yaml. CLI support for multi-slot editing is on the roadmap, so edit the YAML directly for now.

Strategy

When you declare more than one slot, strategy controls how phases interleave across them, so you trade off shared-fixture throughput against per-slot timing.

StrategyBehaviorUse when
phase_firstRuns the same phase across every slot before advancing.Phases share a fixture that does one thing at a time.
slot_firstFinishes every phase for one slot before starting the next.Slots are fully independent. Per-slot timing matters more than throughput.

The default is phase_first, because most multi-slot benches share at least one instrument and you want the runtime to keep that instrument busy.

On-fail behavior

on_first_failure controls what happens the first time any phase reports FAIL, so you decide whether the run keeps going or stops at the first sign of trouble.

  • stop cancels the remaining phases for that slot (default).
  • continue runs every phase to completion so you collect every measurement.

This setting only applies to FAIL outcomes, because unhandled exceptions, timeouts, and aborts always interrupt the Run. When the test infrastructure is in an unknown state, finishing on the same instrument is not trustworthy, so the runtime errs on the side of stopping.

A per-phase then.fail rule overrides this setting for that phase only, which is the right escape hatch when one phase is known to recover safely.

Run outcome

After every phase finishes, the runtime resolves the run outcome from the worst phase outcome across the slot, in the order below.

ERROR  →  ABORTED  →  TIMEOUT  →  FAIL  →  PASS

A single ERROR makes the entire run an ERROR regardless of how many phases passed, because an error means the test infrastructure failed and the data is not trustworthy. Analytics treats ERROR runs separately from FAIL runs so they do not pollute your pass-rate charts.

OutcomeCondition
ERRORAny phase threw an unhandled exception.
ABORTEDRun stopped by user or phase.stop().
TIMEOUTAny phase exceeded its timeout.
FAILAny phase failed.
PASSAll phases passed.

How is this guide?

On this page