Sim hierarchy
Every submission picks one of three run modes. Every device can also declare a per-device sim_override in the topology. The framework combines the two with a fixed resolver — a 12-row lookup — to decide how each action dispatches.
The rule in one sentence: the submission mode is the ceiling; per-device overrides can ratchet toward sim but never toward live.
The three modes
from orca.runtime.run_modes import WorkflowRunMode
| Mode | What dispatches |
|---|---|
PURE_SIM | Every device runs against its sim driver. No wire calls. Fast, local. |
DEVICE_SIM | The real driver runs; under it, the gateway-routed sim backend (PLR Chatterbox or similar) replaces the hardware backend. Wire is exercised, devices are not real. |
LIVE | Real drivers on real hardware. |
Mode is required on every submission. There's no deployment-level default — runtime.submit(workflow, mode=...) is the only way to specify it. The CLI mirrors this: orca run <workflow> --run-mode <mode> rejects submissions that don't pass --run-mode.
Per-device sim_override
shaker = Shaker("shaker_01", sim_override=WorkflowRunMode.PURE_SIM)
A sim_override on a device pins that device's dispatch independently of what the submission picks. Common use cases:
- A device is offline for maintenance — pin it to
PURE_SIMso workflows can continue running against the rest of the lab. - A device is awaiting calibration — pin it to
PURE_SIMuntil validated. - You're bringing a new device online — pin it to
DEVICE_SIMto exercise driver code without touching hardware.
The override lives at topology construction time. It's read by SystemTopologyRegistry and surfaced on TopologyCard.topology_sim_override. The runtime threads it onto every dispatch through SimulationManager.
sim_override=None (the default) means "follow the submission mode."
The 12-row resolver
resolve_effective_mode_for_device(submission_mode, device_sim_override) is the entire resolver. It returns a ResolvedDeviceMode(resolved, warning).
| Submission mode | Device sim_override | Effective mode | Warning? |
|---|---|---|---|
PURE_SIM | (any) | PURE_SIM | no |
DEVICE_SIM | None | DEVICE_SIM | no |
DEVICE_SIM | PURE_SIM | PURE_SIM (ratchet down) | no |
DEVICE_SIM | DEVICE_SIM | DEVICE_SIM | no |
DEVICE_SIM | LIVE | DEVICE_SIM (override silently inert) | no |
LIVE | None | LIVE | no |
LIVE | PURE_SIM | PURE_SIM (ratchet down) | YES |
LIVE | DEVICE_SIM | DEVICE_SIM (ratchet down) | YES |
LIVE | LIVE | LIVE | no |
Twelve rows when you fully enumerate device_sim_override ∈ {None, PURE_SIM, DEVICE_SIM, LIVE} against the three submission modes. The collapsed version above is the operative summary.
Two rules govern the table:
- Overrides only ratchet toward sim. A device pinned to
LIVEunder aPURE_SIMsubmission still dispatches PURE_SIM — the submission ceiling wins. - A
LIVEsubmission warns about sim-direction overrides. Any device pinned toPURE_SIMorDEVICE_SIMunder aLIVEsubmission fires a warning so the operator doesn't accidentally LIVE-deploy with half the lab still in sim.
The LIVE confirmation gate
A LIVE submission against any device with a sim-direction sim_override is refused unless the submission carries acknowledge_warnings=True. The in-process call raises LiveSubmissionWithSimOverridesUnacknowledgedError, whose .devices attribute is the full list of offending devices as (device_name, sim_override, resolved_mode) tuples:
# error.devices
[
("shaker_01", WorkflowRunMode.PURE_SIM, WorkflowRunMode.PURE_SIM),
("sealer_01", WorkflowRunMode.DEVICE_SIM, WorkflowRunMode.DEVICE_SIM),
]
Operators see exactly which devices the LIVE submission would skip before deciding whether to proceed.
To proceed:
| Surface | How to acknowledge |
|---|---|
| CLI | orca run <workflow> --run-mode LIVE --confirm |
| Python | runtime.submit(workflow, mode=WorkflowRunMode.LIVE, acknowledge_warnings=True) |
Acknowledging applies to all sim-direction overrides on that submission — it's a single boolean, not a per-device list. If you want LIVE on one of the pinned devices, remove the sim_override on that device first (a topology edit, then a runtime rebuild).
The current_run_mode ContextVar
Internally the resolver result is propagated through current_run_mode: ContextVar[WorkflowRunMode]. It's seeded at each execution entrypoint and re-seeded per-thread at thread start. asyncio.Task inherits a snapshot of the parent context, so child tasks see the seed automatically.
You don't normally read this directly — SimulationManager.driver is the consumer at dispatch time. If you're writing custom dispatch glue (rare) or test code that wants to match the production fallback, the constant UNSEEDED_FALLBACK_MODE (= PURE_SIM) is what reads outside a seeded scope return.
Where it's enforced
| Layer | What it checks |
|---|---|
Submission validation (SystemRuntime.submit_workflow / runtime.submit) | Computes resolved modes for every device and fires LIVE_SUBMISSION_WITH_SIM_OVERRIDES_UNACKNOWLEDGED if any sim-direction override is present without acknowledge_warnings=True. |
Dispatch (SimulationManager.driver) | Per-action lookup of current_run_mode.get(); returns the sim driver or the wire driver based on the resolved mode. |
Pitfalls
- Mode is required on every submission. There is no deployment-level default. A bare
runtime.submit(workflow)raises. The CLIorca runwithout--run-modeexits with EXIT_USAGE. This is deliberate — getting the mode wrong is a class of bug worth refusing rather than defaulting. - A device pinned to
LIVEis NOT a no-op. It tells the resolver "this device will never go to sim regardless of submission mode" — but the submission mode is still the ceiling, so aPURE_SIMsubmission still dispatches PURE_SIM on that device. The override is functionally inert in the toward-live direction; the only thing it changes is the warning ("the warning fires only if you also try LIVE"). --confirmis per-submission. It doesn't change the topology. The next submission against the same workflow without--confirmwill refuse again. To remove the friction permanently, edit the topology to drop thesim_override.DEVICE_SIMstill requires drivers. It's the real driver running with a sim backend — if the driver itself doesn't load (import error, missing config), DEVICE_SIM fails the same way LIVE would.PURE_SIMis the only mode that skips driver loading entirely.- The sim/live decision is per-action, not per-thread. A thread can pick up a plate from a LIVE-resolved device and drop it on a PURE_SIM-resolved device in the same method. The transporter is also resolved per-action and may itself be mixed.
Where to go next
- CLI: run — operator surface for
--run-modeand--confirm. - Submissions — the larger submission shape that carries
mode. - Devices — how
sim_overrideslots into device construction.