Architecture
This page is a one-stop tour of how Orca Framework thinks about your lab. The first half is for anyone evaluating the system; the second half adds the technical details for engineers who'll be writing workflows.
Overview (for everyone)
Orca Framework separates two things that lab automation tools usually mix together:
- Topology — your physical lab. Which devices exist, where they sit, which arm moves things. Defined once.
- Workflow — what should happen to a piece of labware. Defined separately, and the same workflow can run on different topologies (a sim lab, a dev lab, a production lab) without changes.
This separation is what makes Orca portable. You can develop and test a workflow against a simulated topology on your laptop, then run the same workflow against a topology that points at real hardware — no workflow changes.
A workflow is composed of one or more threads running in parallel. A thread is the journey of one piece of labware. Each thread is composed of methods, which are groups of actions. Actions are the leaves: a single device operation.
Workflow
└── Thread (one piece of labware's journey)
└── Method (grouped actions at a single device)
└── Action (one device operation)
Above all of this sits the runtime — a long-running service that accepts workflow submissions, schedules them, moves labware, fires events, and recovers from incidents. Every operation the runtime supports is exposed through a stable API so external systems can drive the lab programmatically.
The component model (for engineers)
Topology
Defined as a Topology(locations, transporters, pools):
- Locations — named positions that can hold labware. Each location holds either a device (a
Sealer,Shaker,LiquidHandler,Storage, etc.) or a passive resource (aPlatePad). - Transporters — robotic arms that move labware between locations. Each transporter has a teachpoint store mapping location names to physical coordinates.
- Resource pools — groups of identical devices treated as one addressable pool. The runtime picks the next available device when a method asks for
theshaker pool.
A topology is just data. It's defined separately from any workflow so the same physical lab can run many workflows.
Actions
The smallest unit of work. Defined as:
@orca.action(device=shaker, inputs=[plate])
async def shake(ctx: ActionContext):
await ctx.device().shake(duration=30, speed=500)
An action declares which device it runs on and which labware it consumes / produces. The body talks to the device through ctx.device() — a typed driver handle — and can call any method the device's driver exposes.
Actions can also emit events (ctx.emit(...)), wait for events (ctx.wait_for(...)), and access workflow variables (ctx.param(...)).
Methods
A named, reusable sequence of actions:
@orca.method
async def shake_step(ctx: MethodContext):
yield shake
yield read_pressure
Each action inside the method is bound to its own device (or resource pool); the method itself just composes them. When a method yields an action, the transporter brings labware to that action's device — possibly a different device than the previous action. Methods can have failure policies (e.g., FailurePolicy.PAUSE on failure) that control runtime behavior.
Threads
A thread is the journey of one piece of labware:
@orca.thread(labware=sample_plate, start="stacker", end="waste")
async def sample_journey(ctx: ThreadContext):
yield shake_step
yield seal_step
The decorator declares the labware template, the start location, and the end location. The body yields methods in order. Threads also support more advanced flow primitives — orca.join() for synchronizing with other threads, orca.branch() for conditional paths based on events, orca.park() for parking labware between assignments.
Multiple threads run in parallel — the runtime schedules them concurrently. A workflow with three threads will have three pieces of labware moving through the system at once.
Workflows
A workflow ties threads together:
@orca.workflow(name="my_assay")
def my_assay(wf: WorkflowContext):
wf.start(sample_journey) # primary thread, kicks off the workflow
wf.thread(tips_journey) # secondary thread, spawned when needed
wf.on("ACTION.COMPLETED", MyHandler()) # event handler
wf.variable("dilution_factor", VariableDefinition(...))
Workflows are the unit you submit to the runtime. A single workflow can have many threads, branches, joins, and event handlers.
The runtime
The SystemRuntime is a long-lived service. You stand it up once at process start; it stays up accepting submissions until the process exits.
The runtime owns:
- Workflow templates — the registered workflows that can be submitted.
- Stores: pluggable interfaces for labware, incidents, and the other persistence concerns (
ILabwareStore,IIncidentStore, plus the service-backed access-config, teachpoint, deck-layout, execution-record, and ops-history stores). - The event bus — fires on every status change. Sinks subscribe for monitoring, persistence, or external integrations.
- Active executions — workflow runs in flight at any moment.
Every operation the runtime supports — submit_workflow, pause, resume, recover, abort, get_status — is exposed across three control surfaces, all shipping in the box:
| Surface | When to reach for it |
|---|---|
Python SystemRuntime API | In-process integration. Your own service code, embedded testing, in-process orchestration loops. |
| Localhost daemon REST + SSE | Out-of-process control on the same host. The orca.daemon FastAPI service on 127.0.0.1 exposes the full surface with a live SSE event stream. |
orca CLI | Operator workflow. Talks to the daemon over its local REST API. |
For remote/network access — including authenticated REST + MCP for external clients — wrap the daemon in your own service layer or use Swarm, which is purpose-built for that role.
See Runtime for instantiating SystemRuntime and submitting workflows to it.
Execution flow
When you submit a workflow:
- The runtime creates an execution — an instance of the workflow.
- The starting thread spawns; secondary threads spawn when their triggers fire (a join opportunity, a contributing thread completing).
- For each thread, methods run in sequence. Before each method:
- The runtime reserves the method's device (waits if it's busy).
- The transporter moves the labware to the device.
- All actions in the method run.
- Threads can pause, branch, or park along the way; the runtime waits for events or operator input.
- When all methods complete, the transporter moves labware to the thread's end location.
- When all threads finish, the execution completes.
If something goes wrong (a device error, a labware mismatch), the runtime fires an incident and pauses the affected thread. An operator can inspect the incident, choose a recovery action (retry, skip, abort), and resume.
See Recovery for the incident model.
Where to go next
- Runtime — the persistent runtime, in detail.
- Workflows — multi-thread workflows, joins, branches, parks.
- Devices — what's wired up out of the box.
- Submissions — groups, batching, and joining in-flight executions.