Submissions
A submission is a request to the runtime to run a workflow. The simplest submission runs one workflow with default settings; more advanced shapes let you submit multiple labware groups or join an in-flight execution that's still accepting work.
The simple case
from orca.runtime.run_modes import WorkflowRunMode
submission = await runtime.submit_workflow(
"my_assay",
variables={"dilution_factor": 4.0},
mode=WorkflowRunMode.PURE_SIM,
)
This creates a fresh execution, with one labware group containing one of each thread the workflow declares.
Run modes
The mode keyword is required on every submission — there is no deployment-level fallback. Three values, chosen per submission:
| Mode | Devices behind the workflow | Use it for |
|---|---|---|
WorkflowRunMode.PURE_SIM | Pure software simulators. No driver code paths exercised; no hardware contact. | Authoring and iterating on workflows on a laptop. The fastest mode. |
WorkflowRunMode.DEVICE_SIM | Real driver code against PyLabRobot Chatterbox simulators. Same code path that runs against real hardware, with the I/O stubbed. | Validating driver behavior, integration testing, dry runs before a hardware deploy. |
WorkflowRunMode.LIVE | Real hardware drivers talking to real instruments. | Production runs. |
Before each execution starts, the runtime validates every device declared in the topology against the requested mode. Submitting LIVE against a topology with disconnected hardware fails fast with DEVICE_OFFLINE instead of part-way through the run.
Labware groups
A LabwareGroup is a logical bundle of labware that move through a workflow together. submit_workflow always runs a single default group. To submit multiple groups, use the lower-level runtime.submit(...), which takes a WorkflowTemplate (not a name) plus groups and batch_mode:
from orca.runtime.submission import BatchMode
template = build.system.get_workflow_template("my_assay")
submission = await runtime.submit(
template,
groups=[
LabwareGroup(id="g1", members=[
LabwareGroupMember(thread_template_name="plate_journey", ...),
]),
LabwareGroup(id="g2", members=[
LabwareGroupMember(thread_template_name="plate_journey", ...),
]),
],
mode=WorkflowRunMode.PURE_SIM,
)
mode is required here too. Each group spawns its own copy of every required thread. Groups can share final receivers if a labware template is declared SHARED_ACROSS_GROUPS, useful for workflows where multiple sample plates converge on one final analysis plate.
TODO: full LabwareGroup / LabwareGroupMember field reference.
Sharing labware across groups
A labware template can be flagged SHARED_ACROSS_GROUPS so all groups in a submission converge on the same physical instance:
from orca.resource_models.sharing import GroupSharing
final_plate = PlateTemplate(
"final_plate",
Cor_Falcon_96_wellplate,
group_sharing=GroupSharing.SHARED_ACROSS_GROUPS,
)
Two orthogonal flags on a labware template control sharing, both defaulting to the isolated value:
| Flag | Values | Effect |
|---|---|---|
group_sharing | GroupSharing.PER_GROUP (default) / SHARED_ACROSS_GROUPS | Within one submission: each group gets its own instance, or all groups share one. |
submission_batching | SubmissionBatching.ISOLATED (default) / BATCHABLE | Across submissions: whether a later JOIN_EXISTING submission may join an in-flight receiver. |
Both are silent no-ops in the default single-group + STANDALONE shape. The behavior only activates when the submission opts in (multiple groups for SHARED_ACROSS_GROUPS, batch_mode=BatchMode.JOIN_EXISTING for BATCHABLE).
Batching: joining an in-flight execution
By default, each submission creates a fresh execution. Pass batch_mode=BatchMode.JOIN_EXISTING (the BatchMode enum, not the string) to merge into a still-accepting execution of the same workflow:
from orca.runtime.submission import BatchMode
submission = await runtime.submit(
template,
groups=[...],
batch_mode=BatchMode.JOIN_EXISTING,
mode=WorkflowRunMode.PURE_SIM,
)
The runtime looks for an active execution of the workflow whose phase is still ACCEPTING and injects your groups into it. Fallback behavior:
- No live execution for this workflow:
JOIN_EXISTINGfalls through to a fresh build, exactly likeSTANDALONE. - The existing execution is
DRAINING(its operator calledclose_execution): the call raises. Resubmit asSTANDALONEto start a new execution.
This is the mechanism for staged submissions: kick off a long-running multi-plate assay and add more plates to it as samples arrive over hours. batch_mode and groups live only on runtime.submit(...); submit_workflow does not take them.
Closing an execution
To stop an execution from accepting new submissions but let in-flight threads finish:
phase = runtime.close_execution(execution_id)
close_execution is synchronous and returns the resulting ExecutionPhase. The execution moves to DRAINING; new JOIN_EXISTING submissions targeting it are rejected, while live threads continue to completion.
To abort an execution outright (cancels all threads):
await runtime.abort_execution(execution_id)
abort_execution is async and is the raw, single-call abort primitive: it cancels every thread immediately. The two-call, confirmation-gated operator stop is a separate first-class runtime method, runtime.stop_execution(execution_id, confirm=False): the first call pauses the execution immediately and arms the abort (recoverable via resume), and a second call with confirm=True performs it. It is exposed on the orca daemon REST (POST /operations/stop-execution) and CLI (orca execution stop --confirm), not only on a hosted deployment.
Terminal phase
When an execution's threads all reach a terminal state, the runtime rolls the execution up to a single terminal phase:
COMPLETEDonly if every thread completed.FAILEDif the execution task raised, or if any thread ended in the crash-onlyFAILEDstatus (a thread whose task died on an unhandled error).FAILEDtakes precedence overABORTEDin the rollup.ABORTEDif any thread was aborted or stopped (a clean task return is not enough;ABORT_THREADand a cooperative stop both return without raising).