Skip to main content

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:

ArgumentHolds
labwareThe labware template this thread carries.
startLocation name where the labware originates.
endLocation 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.

SentinelUse forBehavior
MANUAL_PLACEOperator-handled labwareDefault for bare-string start. In LIVE mode the thread parks at AWAITING_MANUAL_PLACE until the operator places the plate and confirms.
MANUAL_REMOVEOperator-handled dischargeDefault for bare-string end. Thread parks at AWAITING_MANUAL_REMOVE when finished.
DISPENSEPlate stackers / hotelsThe stacker dispenses the next plate fresh — no operator intervention required. Use this for any location connected to an IPlateSource device.
REUSE_EXISTINGPersistent reagentsThe thread binds to a labware instance already at the location (a long-lived trough that survives executions).
LEAVE_IN_PLACEPersistent labware endPair 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

ParameterDefaultEffect
labwarerequiredLabware template this thread carries.
startrequiredOrigin location: a Location, a location-name string, or a (location, sentinel) tuple.
endrequiredDestination location: a Location, a location-name string, or a (location, sentinel) tuple.
contributes_toNoneLabware-template names of the receivers this thread feeds (triggers their auto-spawn).
requiredTrueWhether every submitted group must include this thread. Set False for optional spawn-on-request threads.
immovableFalseWhen 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.
  • Submissionscontributes_to, required, and group-based spawn.