UniteLabs
Concepts

Step

The smallest scientifically atomic unit of intent — a single device action that either happened or did not.

A step is the smallest scientifically atomic unit of intent within a workflow. It executes on exactly one hardware device and produces a result that is either complete or not — there is no partial success at the step level.

Steps are what actually drive instruments. A step might lock a shaker, set its RPM, run it for a duration, and unlock it. From a scientific perspective, the shaking either happened or it did not.

For unitelabs-sdk< 0.10.0, import flow and task from Prefect instead. Use flow in place of both workflow and phase decorators, and task as step decorators.

Example

workflows/steps.py
from unitelabs.sdk.automate import step, phase

@step()
async def shake(shaker: ShakerDevice, rpm: int, duration: int):
    await shaker.elm_controller.lock(retry=3)
    await shaker.shake_controller..set_rpm(rpm)
    await shaker.shake_controller..shake(duration)
    await shaker.elm_controller..unlock()

Steps are called from inside a phase:

workflows/agitation.py
@phase()
async def agitation(target: Plate, shaker: ShakerDevice):
    await mix(target)
    await shake(shaker=shaker, rpm=300, duration=60)
    await shake(shaker=shaker, rpm=150, duration=30)

Key properties

  • Scientifically atomic operation: the step either happened or did not; there is no partial state visible at the scientific level. Error raising and handling should ensure a clear outcome - successful or failure.
  • Single-device responsibility: a step executes on exactly one hardware instance; multi-device coordination belongs in a phase
  • Automatically retryable: the workflow engine retries technical errors (device timeouts, transient communication failures) automatically before propagating the error upward
  • Sequenced within a phase: steps within a phase always run sequentially, in the order they are called

Retries

The workflow engine retries steps on technical failures automatically. You can configure retry behavior per step:

workflows/steps.py
@step(retries=5, retry_delay_seconds=2)
async def aspirate(liquid_handler: LiquidHandler, volume: float):
    await liquid_handler.pipettes.aspirate(volume)

A technical error is a transient device-level failure: a timeout, a lost connection, a device that did not acknowledge a command. These are retried automatically.

A scientific error: a sample gone missing, a volume that cannot be aspirated — is propagated to the phase for user-defined recovery. Steps do not handle scientific errors; phases do (see Error Handling).

Steps are always sequential within a phase. If you need parallelism, split your work into multiple phases — the workflow engine will run independent phases concurrently and evaluate constraints and transitions.

Device typing

Steps declare the device type they require as a function parameter. The workflow engine resolves the actual hardware instance at run time based on availability and capability — the step code never hardcodes a specific instrument:

workflows/steps.py
@step()
async def centrifuge_samples(centrifuge: CentrifugeDevice, rpm: int, duration: int):
    await centrifuge.centrifugation_controller.spin(rpm=rpm, duration=duration)

This means the same step can run on any compatible centrifuge in the lab.

  • Phase: the container that sequences and recovers steps
  • Error Handling: retry behavior and error propagation
  • Workflow: the top-level process steps belong to