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.
execution:
strategy: phase_first
workers: 16
on_first_failure: stop
slots:
- name: USB0
- name: USB1| Field | Purpose | Default |
|---|---|---|
strategy | How phases interleave across slots. phase_first or slot_first. | phase_first |
workers | Max phases running in parallel within a slot. 1-256. | 8 |
on_first_failure | Behavior when a phase reports fail. stop or continue. | stop |
slots | Parallel 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.
execution:
workers: 16Match 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.
execution:
slots:
- name: USB0
- name: USB1
- name: USB2| Field | Purpose |
|---|---|
name | Display name. 1-50 characters. Required. |
key | Identifier 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.
| Strategy | Behavior | Use when |
|---|---|---|
phase_first | Runs the same phase across every slot before advancing. | Phases share a fixture that does one thing at a time. |
slot_first | Finishes 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.
stopcancels the remaining phases for that slot (default).continueruns 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 → PASSA 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.
| Outcome | Condition |
|---|---|
ERROR | Any phase threw an unhandled exception. |
ABORTED | Run stopped by user or phase.stop(). |
TIMEOUT | Any phase exceeded its timeout. |
FAIL | Any phase failed. |
PASS | All phases passed. |
How is this guide?