Background steps
A background step runs a plugin action in a parallel thread while the main test sequence continues. Use them for environmental monitors, pre-charge tasks, data loggers, or any hardware control that should not block the main DUT test loop.
Lifecycle: spawn → stop / join
Every background task follows a three-step lifecycle, each controlled by a separate step in the sequence:
| Mode | What it does |
|---|---|
spawn | Starts the plugin action in a background thread and immediately continues. |
stop | Sends an abort signal and waits up to 5 s for the task to finish cleanly. |
join | Waits indefinitely for the task to finish on its own — no abort signal. |
A sequence must have exactly one spawn per handle ID. Any background task still running when the sequence ends is aborted automatically.
JSON format
Spawn:
{
"id": "start_monitor",
"name": "Start Power Monitor",
"plugin": "power_monitor",
"action": "record_continuous",
"inputs": { "channel": 1, "sample_rate_hz": 100 },
"timeout_ms": 0,
"background": {
"mode": "spawn",
"id": "power_monitor_handle"
}
}
timeout_ms: 0 means the background task runs until stopped or the sequence ends.
Stop:
{
"id": "stop_monitor",
"name": "Stop Power Monitor",
"plugin": "", "action": "", "inputs": {},
"timeout_ms": 5000,
"background": {
"mode": "stop",
"id": "power_monitor_handle"
}
}
Join (wait for natural completion):
{
"id": "wait_for_charge",
"name": "Wait for Pre-Charge Complete",
"plugin": "", "action": "", "inputs": {},
"timeout_ms": 30000,
"background": {
"mode": "join",
"id": "precharge_handle",
"propagate_failure": true
}
}
Config fields
| Field | Required | Description |
|---|---|---|
mode | Yes | "spawn", "stop", or "join" |
id | Yes | Handle ID — unique name for the task |
propagate_failure | No (default false) | join only: fail the step if the background task failed |
Pass/fail accounting
| Mode | Counts toward sequence result? |
|---|---|
spawn | Yes — passes if the worker starts successfully |
stop | No — a stopped task is discarded from pass/fail totals |
join | Yes if propagate_failure: true; otherwise no |
Sequence Editor
Open any step and scroll to Background Execution:
- Check Enable background execution.
- Select the Mode (
Spawn,Stop, orJoin). - For
spawn: the Handle ID is auto-populated as{step_id}_handleand is editable. - For
stop/join: a dropdown lists all declared spawn handles. If none exist yet, a free-text field is shown. - For
join: an optional Propagate failure checkbox appears.
Constraints
- A background task cannot be spawned more than once per sequence run.
- Background steps do not support retry — the task is stopped after the first attempt.
stopandjoinsteps do not execute a plugin;plugin,action, andinputsare ignored.
User prompt steps
A user_prompt step pauses the sequence and displays a full-screen modal to the operator. The engine waits for the operator to press a button before continuing. No plugin is involved.
When to use
- Visual inspections: "Verify all LEDs are green before continuing."
- Manual assembly confirmations: "Confirm DUT is seated correctly."
- Process gates: "Scan OK/NG after jig inspection."
- Any step where a human decision is required mid-sequence.
JSON format
{
"id": "visual_check",
"name": "LED Visual Inspection",
"timeout_ms": 60000,
"on_pass": {},
"on_fail": { "jump_to": "teardown" },
"prompt": {
"title": "Check LED Bar",
"body": "All 8 LEDs must be lit green. Press PASS to continue or FAIL to abort.",
"media": {
"type": "image",
"path": "led_bar_ok.png"
},
"buttons": [
{ "id": "pass", "label": "PASS", "color": "green", "key": "F1", "action": "pass" },
{ "id": "fail", "label": "FAIL", "color": "red", "key": "F2", "action": "fail" }
],
"button_layout": "right_first"
}
}
Field reference
| Field | Required | Description |
|---|---|---|
title | Yes | Large heading text shown to the operator |
body | No | Instruction paragraph below the title |
media | No | Image, GIF, or video shown above the text |
buttons | Yes | 1 to 4 buttons |
button_layout | No | right_first (default) or left_first |
Button actions
action | Step result | Jump behavior |
|---|---|---|
"pass" | Step passes | Uses button jump_to if set; otherwise step-level on_pass.jump_to |
"fail" | Step fails | Uses button jump_to if set; otherwise step-level on_fail.jump_to |
"abort" | Job aborted immediately | No jump — job stops |
Per-button jump_to allows fine-grained routing — for example a "Retry Step" button that fails the step but jumps back to a specific earlier step rather than the generic on_fail target.
Media
Place media files under config/assets/. Reference them by filename (or subdirectory path within assets/). Supported extensions: .png, .jpg, .jpeg, .gif, .mp4, .webm.
Keep video files short — 15 s or less recommended. Files over 50 MB produce a warning at sequence load time but are not rejected.
Keyboard shortcuts
Buttons with a key field can be triggered by keyboard. Values: "F1" through "F12", "Enter", single letters. Matched case-insensitively.
Validation rules (enforced at sequence load)
titlemust not be empty.- 1 to 4 buttons; all button IDs must be unique within the prompt.
- At least one button must have
action: "pass"— abort-only prompts are rejected. - Button
jump_totargets must reference an existing step ID. timeout_msmust be > 0.
Pool groups
Pool groups allow multiple parallel jobs to share a set of hardware stations where each station can only serve one job at a time. Every job must complete all steps in the group for its own DUT, but jobs are free to pick whichever station is currently unoccupied.
When to use
- N functional test stations shared between M fixture slots, where M > N.
- A single calibrator port that handles one DUT at a time across multiple parallel fixtures.
- Any group of steps where hardware count is less than active job count and every job must run every step.
If every job has its own dedicated hardware, pool groups are unnecessary — use resource locks instead.
JSON format
Add "pool_group": "<name>" to each step that belongs to the pool:
{
"id": "station_rf",
"name": "RF Calibration",
"plugin": "rf_calibrator",
"action": "calibrate",
"inputs": { "band": "2.4GHz" },
"timeout_ms": 15000,
"validation": { "type": "numeric", "key": "power_dbm", "operator": ">=", "threshold": -10 },
"pool_group": "calibration"
}
All steps sharing the same pool_group name form one pool. Steps must be contiguous in the sequence definition.
How it works
When a job reaches the first step of a pool group, it enters the pool loop. It scans pool steps in definition order and acquires the first unoccupied slot. If all slots are occupied, it waits until one is released, then retries. Each job completes all steps in the group before moving on.
A job running alone never blocks — all slots are free.
Example: 4 jobs, 3 stations
JIG1: [init] → [station_rf ] → [station_audio ] → [station_optical] → [final_check]
JIG2: [init] → [station_audio ] → [station_optical] → [station_rf ] → [final_check]
JIG3: [init] → [station_optical] → [station_rf ] → [station_audio ] → [final_check]
JIG4: [init] → wait… → [station_audio ] → [station_optical] → [final_check]
Validation rules
| Rule | Error |
|---|---|
| Fewer than 2 steps in a group | Pool group requires at least 2 steps |
| Steps are not contiguous | All steps in a pool group must be adjacent |
Background step declares pool_group | Background steps cannot participate in a pool group |
Sequence Editor
Steps in a pool group show a purple label Pool: <group-name> above the first step and a purple left border on each step. A warning panel appears above the step list if a pool group consistency issue is detected — it updates in real time.
Resource locks
Resource locks let parallel jobs safely share hardware instruments. Declaring a lock on a step tells the engine: acquire exclusive access to this resource before executing; hold it until the step completes.
When to use
- A single bench DMM shared between two fixture slots.
- A power supply channel used by multiple jobs at different points in their sequences.
- Any hardware resource that cannot safely handle concurrent access.
Lock modes
| Mode | Behavior |
|---|---|
step (default) | Acquire before the step, release automatically when it exits. |
create | Acquire and hold across subsequent steps until explicitly released. |
release | Drop session-held locks before this step runs. |
JSON format
Step mode (default):
{
"id": "measure_dc_voltage",
"locks": ["dmm_bench"],
"lock_timeout_ms": 8000
}
Multiple resources at once: "locks": ["dmm_bench", "psu_ch1"]
Create / release (hold across steps):
{ "id": "init_dmm", "locks": ["dmm_bench"], "lock_mode": "create", "lock_timeout_ms": 5000 }
// ... intermediate steps need no lock declaration ...
{ "id": "release_dmm", "locks": ["dmm_bench"], "lock_mode": "release" }
Field reference
| Field | Default | Description |
|---|---|---|
locks | [] | Resource names to acquire or release |
lock_timeout_ms | 5000 | Maximum wait time in ms. Ignored in release mode. |
lock_mode | "step" | "step", "create", or "release" |
Deadlock prevention
Corvus acquires all locks in alphabetical order regardless of the declaration order in the sequence. This prevents deadlocks when multiple jobs try to acquire the same set of resources simultaneously — both jobs will always attempt locks in the same order.
Behavior on timeout
If a lock cannot be acquired within lock_timeout_ms, the step produces an error outcome. The sequence's normal error handling applies. All session locks are cleared automatically at the end of every test cycle — no permit can leak across cycles.
Validation rules
| Rule | Error |
|---|---|
| Lock name is blank | Lock name must not be empty |
lock_timeout_ms == 0 in non-release mode | Must be greater than 0 |
release step with no lock names | Must declare at least one lock name |
create on an already-held name | Double-create — add a release step first |
release of a name never created | Orphan release — check for typos |
create with no matching release | Unclosed create — add a release step |
Sequence Editor
Each step has a Resource Locks section. Check Manage resource locks to enable it, select a lock mode, type a name and press Enter. Autocomplete suggests names already used elsewhere in the sequence. An amber warning panel appears whenever a lock consistency issue is detected.
Step UIDs
Every step in a sequence carries a UID — a UUID4 generated at authoring time. The UID is the canonical internal reference for jump targets and background handle cross-references.
Why UIDs
The human-readable id field is convenient for authoring but fragile as a routing key:
- Renaming a step
idsilently breaks allon_pass.jump_to/on_fail.jump_toreferences pointing at the old name. - Two steps can share the same
idstring by accident.
UIDs are stable and never change regardless of how the step's label evolves.
Jump resolution
The engine tries jump targets against the UID index first, then falls back to the id index. Existing sequences using "jump_to": "teardown" continue to work unchanged. New sequences authored in the editor store the UID in jump_to and are immune to renames.
Legacy migration
When the Sequence Editor opens a file without UID fields, it automatically assigns a fresh UUID4 to every step that lacks one and marks the sequence dirty. No manual migration is required — just save.