Step
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.
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
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:
@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:
@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).
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:
@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.
Related concepts
- Phase: the container that sequences and recovers steps
- Error Handling: retry behavior and error propagation
- Workflow: the top-level process steps belong to