Threads
A thread describes the path one piece of labware takes through the system. Each thread tracks one labware template; multiple threads run in parallel.
Defining a thread
@orca.thread(labware=sample_plate, start="stacker", end="waste")
async def sample_journey(ctx: ThreadContext):
yield shake_step
yield seal_step
yield read_step
The decorator declares:
| Argument | Holds |
|---|---|
labware | The labware template this thread carries. |
start | Location name where the labware originates. |
end | Location name where the labware ends. |
The body yields methods in order. Methods can be plain method templates, or flow primitives (orca.join, orca.branch, orca.park).
Start and end sentinels
start and end can be a bare location name (as above) or a (location, sentinel) tuple that tells the runtime how the labware appears or departs. A bare string is shorthand for MANUAL_PLACE (at start) and MANUAL_REMOVE (at end) — the runtime parks the thread waiting for an operator to physically place / remove the plate.
| Sentinel | Use for | Behavior |
|---|---|---|
MANUAL_PLACE | Operator-handled labware | Default for bare-string start. In LIVE mode the thread parks at AWAITING_MANUAL_PLACE until the operator places the plate and confirms. |
MANUAL_REMOVE | Operator-handled discharge | Default for bare-string end. Thread parks at AWAITING_MANUAL_REMOVE when finished. |
DISPENSE | Plate stackers / hotels | The stacker dispenses the next plate fresh — no operator intervention required. Use this for any location connected to an IPlateSource device. |
REUSE_EXISTING | Persistent reagents | The thread binds to a labware instance already at the location (a long-lived trough that survives executions). |
LEAVE_IN_PLACE | Persistent labware end | Pair with REUSE_EXISTING. The thread completes without moving the labware off its location, so the next submission can re-bind. |
Example shapes:
# Operator-handled sample plate (default semantics, just the string)
@orca.thread(labware=sample_plate, start="pad_1", end="pad_1")
async def sample_journey(ctx: ThreadContext):
...
# Plate dispensed from a stacker
@orca.thread(labware=sample_plate, start=("stacker", DISPENSE), end="waste")
async def stacker_journey(ctx: ThreadContext):
...
# Persistent reagent trough — lives on the deck across executions
@orca.thread(
labware=reagent_trough,
start=("deck_trough", REUSE_EXISTING),
end=("deck_trough", LEAVE_IN_PLACE),
)
async def trough_journey(ctx: ThreadContext):
yield orca.join(allows=[transfer_step])
Import sentinels from orca.spawn:
from orca.spawn import DISPENSE, MANUAL_PLACE, MANUAL_REMOVE, REUSE_EXISTING, LEAVE_IN_PLACE
Flow primitives
orca.join
Used by a contributor thread to indicate it joins another thread's method rather than running its own. Common for shared resources (tips, troughs) that participate in another thread's work:
@orca.thread(labware=tips, start="stacker", end="waste")
async def tips_journey(ctx: ThreadContext):
yield orca.join(allows=[cherry_pick_step, dilute_step])
allows=[...] whitelists which methods this thread is allowed to join. Without allows, the thread joins whatever method spawns it.
orca.branch
Branches to one of several method sequences based on an event value:
yield orca.branch("qc_result", {
"pass": [seal_step],
"fail": [retry_step],
"else": [abort_step],
})
orca.park
Suspends the thread at a parking location until the runtime wakes it (typically after a co-thread arrives or an operator triggers continuation):
yield orca.park("stacker_7")
Thread parameters
| Parameter | Default | Effect |
|---|---|---|
labware | required | Labware template this thread carries. |
start | required | Origin location: a Location, a location-name string, or a (location, sentinel) tuple. |
end | required | Destination location: a Location, a location-name string, or a (location, sentinel) tuple. |
contributes_to | None | Labware-template names of the receivers this thread feeds (triggers their auto-spawn). |
required | True | Whether every submitted group must include this thread. Set False for optional spawn-on-request threads. |
immovable | False | When True, the engine never moves this labware off its start; the deadlock detector treats it as a terminal blocker (other threads requesting its location fail fast with UnresolvableDeadlockError). Use for deck-resident reagents. Cannot be a wf.start() entry. |
Where to go next
- Workflows — composing threads into a workflow.
- Methods — what threads yield.
- Submissions —
contributes_to,required, and group-based spawn.