Quick Start
Orca Framework has not been released yet. This walkthrough previews how it works; running it requires preview access. Contact us to get set up.
This guide walks you through writing and running a complete Orca Framework workflow — in simulation, with no hardware required. By the end you'll have a single Python file that defines a lab, a workflow, and runs it end to end.
The workflow we'll build: a sample plate is taken from a stacker, shaken on a shaker, sealed on a sealer, and discarded to waste.
Before you start
You should have:
- Orca Framework installed (see Installation)
- A Python file open in your editor — call it
my_first_workflow.py
Example file
Copy this into my_first_workflow.py first, then we'll walk through what each section does.
import asyncio
import orca.orca as orca
from cheshire_drivers import CartesianCoordinates, Teachpoint
from orca.devices.devices import Storage, Waste
from orca.devices.sealer import Sealer
from orca.devices.shaker import Shaker
from orca.resource_models.transporter import Transporter
from orca.runtime.run_modes import WorkflowRunMode
from orca.runtime.store_factory import InMemoryRuntimeStoreFactory
from orca.runtime.system_runtime import SystemRuntime
from orca.sdk.build import Topology
from orca.sdk.labware import PlateTemplate
from orca.workflow_models.action_context import ActionContext
from orca.workflow_models.method_context import MethodContext
from orca.workflow_models.thread_context import ThreadContext
from orca.workflow_models.workflow_context import WorkflowContext
# 1. Labware template
sample_plate = PlateTemplate("sample_plate", "Cor_Falcon_96_wellplate_340ul_Fb_Black")
def build_topology(stores):
"""Define the physical lab: devices, transporter, locations."""
shaker = Shaker("shaker")
sealer = Sealer("sealer")
stacker = Storage("stacker")
waste = Waste("waste")
c = CartesianCoordinates
teachpoints = [
Teachpoint("stacker", c(0, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("shaker", c(400, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("sealer", c(800, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("waste", c(1200, 200, 200, 0, 90, 180), orientation="right"),
]
arm = Transporter(
"robotic_arm",
teachpoint_store=stores.teachpoints("robotic_arm", seed=teachpoints),
)
return Topology(
locations={
"stacker": stacker,
"shaker": shaker,
"sealer": sealer,
"waste": waste,
},
transporters=[arm],
)
def build_workflow(topology):
"""Define what happens to each sample plate."""
shaker = topology.device("shaker", Shaker)
sealer = topology.device("sealer", Sealer)
# 2. Actions — single device operations
@orca.action(device=shaker, inputs=[sample_plate])
async def shake(ctx: ActionContext):
await ctx.device().shake(duration=30, speed=500)
@orca.action(device=sealer, inputs=[sample_plate])
async def seal(ctx: ActionContext):
await ctx.device().seal(temperature=100, duration=60)
# 3. Methods — sequences of actions on a device
@orca.method
async def shake_step(ctx: MethodContext):
yield shake
@orca.method
async def seal_step(ctx: MethodContext):
yield seal
# 4. Thread — one labware's journey through the lab
@orca.thread(labware=sample_plate, start="stacker", end="waste")
async def sample_journey(ctx: ThreadContext):
yield shake_step
yield seal_step
# 5. Workflow — orchestration of one or more threads
@orca.workflow(name="my_first_workflow")
def my_workflow(wf: WorkflowContext):
wf.start(sample_journey)
return my_workflow
async def main():
# stores hold calibration data like teachpoints and deck layouts; the in-memory factory is fine for sim
stores = InMemoryRuntimeStoreFactory()
topology = build_topology(stores)
workflow = build_workflow(topology)
build = await orca.build_system(
name="My First Workflow",
workflow=workflow,
topology=topology,
stores=stores,
)
runtime = SystemRuntime(build.system, event_bus=build.event_bus)
await runtime.start()
submission = await runtime.submit(workflow, mode=WorkflowRunMode.PURE_SIM)
await runtime.wait_for_execution(submission)
await runtime.shutdown()
if __name__ == "__main__":
asyncio.run(main())
Run it:
python my_first_workflow.py
You should see Orca log the plate being moved, shaken, sealed, and discarded.
Walking through the file
Each subsection below quotes the exact lines from the example above and explains what they do.
Labware template
sample_plate = PlateTemplate("sample_plate", "Cor_Falcon_96_wellplate_340ul_Fb_Black")
A PlateTemplate describes a type of plate, not a specific physical plate. The first argument is a name you'll reference in actions; the second is a labware-catalog slug string (the PyLabRobot plate definition name) that the runtime resolves to the geometry (well count, dimensions, etc.) at build time.
Devices in the topology
shaker = Shaker("shaker")
sealer = Sealer("sealer")
stacker = Storage("stacker")
waste = Waste("waste")
These are the four physical instruments in this lab. Each is constructed with a name — that name is how the topology and workflow refer to it.
Teachpoints
c = CartesianCoordinates
teachpoints = [
Teachpoint("stacker", c(0, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("shaker", c(400, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("sealer", c(800, 200, 300, 0, 90, 180), orientation="right"),
Teachpoint("waste", c(1200, 200, 200, 0, 90, 180), orientation="right"),
]
A teachpoint is the physical coordinate the transporter (the robotic arm) needs to know in order to pick or place labware at a given location. In a real lab you capture these by physically jogging your arm to each position and recording its (x, y, z, yaw, pitch, roll). The numbers above are placeholders — fine for simulation, but you'd swap them for real values before running against hardware.
Transporter
arm = Transporter(
"robotic_arm",
teachpoint_store=stores.teachpoints("robotic_arm", seed=teachpoints),
)
The Transporter is the robotic arm that moves labware around. It needs a name and a teachpoint store seeded with the list above. The store gives the runtime a structured, editable record of teachpoints — useful later when you want to retrain a position without editing source.
Topology
return Topology(
locations={
"stacker": stacker,
"shaker": shaker,
"sealer": sealer,
"waste": waste,
},
transporters=[arm],
)
The Topology is the lab's complete physical layout. It maps location names to devices — the string "stacker" in the locations dict is the same name a thread will use in its start=... / end=... arguments. The transporters list tells the runtime which arm can move labware between those locations.
The topology is defined separately from any workflow, so the same lab can serve many different workflows.
Actions
@orca.action(device=shaker, inputs=[sample_plate])
async def shake(ctx: ActionContext):
await ctx.device().shake(duration=30, speed=500)
An @orca.action is one operation on one device, with one or more pieces of labware as input. ctx.device() gives you a typed handle to the device so you can call its driver methods directly.
Methods
@orca.method
async def shake_step(ctx: MethodContext):
yield shake
A @orca.method groups actions into a named, reusable sequence. Methods make it easy to refer to a group of actions and can also be run on their own as standalone executions.
Thread
@orca.thread(labware=sample_plate, start="stacker", end="waste")
async def sample_journey(ctx: ThreadContext):
yield shake_step
yield seal_step
A @orca.thread is the path one piece of labware takes through the system. It declares which labware template it's tracking, where the labware starts, where it ends, and the methods to execute along the way. The start and end strings reference names in the topology's locations dict.
Multiple threads can run in parallel — for example, one for sample plates and one for tip racks moving through the same lab.
Workflow
@orca.workflow(name="my_first_workflow")
def my_workflow(wf: WorkflowContext):
wf.start(sample_journey)
A @orca.workflow ties threads together. wf.start(...) declares which thread starts when the workflow runs. For multi-thread workflows you'd add wf.thread(other_journey) for the secondary threads.
Build, start, submit
build = await orca.build_system(
name="My First Workflow",
workflow=workflow,
topology=topology,
stores=stores,
)
runtime = SystemRuntime(build.system, event_bus=build.event_bus)
await runtime.start()
submission = await runtime.submit(workflow, mode=WorkflowRunMode.PURE_SIM)
await runtime.wait_for_execution(submission)
await runtime.shutdown()
Five moves:
build_systemis async (await orca.build_system(...)) and produces aSystemBuild: your topology + stores + event bus wired together.SystemRuntime(...)wraps the built system in the long-running service that schedules and runs submissions. See Runtime.runtime.start()brings the runtime to a state where it can accept submissions.runtime.submit(workflow, mode=...)hands the workflow off to the runtime. It returns immediately with asubmissionhandle while the runtime executes the workflow in the background.modeis required and must be one ofPURE_SIM,DEVICE_SIM, orLIVE.runtime.wait_for_execution(submission)blocks your script until that submission's execution actually finishes. Without it, the script would exit before the workflow completed.runtime.shutdown()then tears the runtime down cleanly.
What just happened during execution
- The workflow started the sample plate thread.
- For the
shake_stepmethod, the transporter moved the plate from the stacker to the shaker, then the shake action ran. - For the
seal_stepmethod, the transporter moved the plate from the shaker to the sealer, then the seal action ran. - After all methods completed, the transporter moved the plate to its end location (waste).
Next steps
- Architecture — go deeper on how Orca thinks about your lab.
- Runtime — accept submissions over time with a long-lived
SystemRuntime. - Devices — wire up real hardware.
- Workflows — multi-thread workflows, joins, and branches.