Skip to main content

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 (a PlatePad).
  • 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 the shaker 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:

SurfaceWhen to reach for it
Python SystemRuntime APIIn-process integration. Your own service code, embedded testing, in-process orchestration loops.
Localhost daemon REST + SSEOut-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 CLIOperator 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:

  1. The runtime creates an execution — an instance of the workflow.
  2. The starting thread spawns; secondary threads spawn when their triggers fire (a join opportunity, a contributing thread completing).
  3. 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.
  4. Threads can pause, branch, or park along the way; the runtime waits for events or operator input.
  5. When all methods complete, the transporter moves labware to the thread's end location.
  6. 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.