UniteLabs onboarding
Welcome, let's get you set up.
Use this guide to set up your UniteLabs account, development machine, instruments, and run your first workflow. Come back any time; your progress is saved on this device.
What is UniteLabs
UniteLabs is an API-first platform that connects instruments once and makes them available, through code, to everything else: your workflows, your data, your own tools.
Connectors
Every instrument (liquid handler, reader, incubator) connects to the platform through a connector. A connector is a driver that communicates directly with the device and runs on the GroundControl desktop app. From there it's available to the SDK, the REST API and the platform UI. What is a connector? →
SDK
The UniteLabs SDK lets you control connected instruments and call platform services from Python. It also includes the liquid-handling and labware libraries. Control with code →
Orchestration
Workflows combine multiple steps and instruments into one run using Workflow → Phase → Step hierarchy. The engine schedules the work, tracks execution and pauses for operator input when needed. What is a workflow? →
Data layer
Raw instrument output flows through File System Connectors into object storage, then into a queryable data warehouse via ETL workflows, with no manual storage management. Data overview →
In short: your lab methods become code: you can develop in Python, use Git for version control, test against simulated devices, and deploy them as workflows. Learn how it works.
Getting set up
Five steps, roughly in order. Expand any step for the key info without leaving this page, or open the full guide for everything else.
What each credential is for and where it goes. Open full guide →
The edge app that connects instruments to your tenant. Open full guide →
Editor, Git, uv and Python. Open full guide →
Install the UniteLabs SDK and send a first API request. Open full guide →
Turn on the server-initiated connection. Open full guide →
Connect your instruments
Select the instruments you use: configure each as a connector in GroundControl, then select it below. See Connector configuration for how to add and set one up. Selecting any one instrument completes this section.
Select an instrument above to see its setup guide and any setup tips.
Build your first workflow
UniteLabs workflows are built from a Workflow → Phase → Step hierarchy (the full breakdown is in What is a workflow?). We'll use two real examples from the workflow-template repo. Both run with no hardware and no credentials. Clone it to your development machine before you start.
Workflow
The top-level process, written as an @workflow function. It coordinates phases and passes data between them, but does not control hardware directly.Learn more →
Phase
A group of steps that ends at a state the run can resume from, written as a @phase-decorated function. A phase can also pause workflow execution and wait for operator confirmation or input.Learn more →
Step
One action on one device, written as a @step function - this is meant to be a reusable, granular function.Learn more →
Exercise 1: the decorators, explained
This is the real source of w01-hello-world, the simplest workflow in the template. It just logs a greeting and the SDK version. Pick either decorator below to see why it's there.
from unitelabs.sdk import __version__ as sdk_version
from unitelabs.sdk import get_logger, workflow
@workflow(name="Hello World")
async def hello_world(recipient_name: str = "world") -> None:
logger = get_logger()
logger.info(f"Hello, {recipient_name}!")
logger.info(f"UniteLabs SDK: {sdk_version}")Exercise 2: where each piece lives
Back in What is UniteLabs, the platform split into four pieces: Connectors, SDK, Orchestration, and the Data layer. Click each fact below to see where it actually shows up in w02-liquid-handling.
MicrolabSTARMock() stands in for the real Hamilton STAR whenever simulate=True, so the workflow runs with no hardware attached.
Every step and helper reaches the platform through calls like AsyncApiClient() and client.get_service_by_name(...).
liquid_handling() calls setup(), then transfer(), in order, and the engine tracks each as its own checkpointed run.
Writing the finished run's results into the Data Warehouse for later querying.
Exercise 3: walk through the real order
w02-liquid-handling transfers liquid from one plate to another on a (simulated) Hamilton STAR. Step through what actually happens, in order.
Step 1 of 5
Create & initialize the liquid handler (mock or real)
The handler must be available before the workflow can set up the deck or transfer liquid.
Exercise 4: classify the code
Every piece of workflow code belongs to one layer, including a fourth one we haven't named yet: a Helper, a plain function with no decorator, not tracked by the workflow engine. These five snippets come from w02-liquid-handling and its shared/ library. Click each one to see which layer it belongs to and why.
@workflow(name="Liquid Handling Demo")
async def liquid_handling(simulate: bool = True) -> None:
result = await setup(device_name=INSTRUMENT_NAME)
await transfer(
lh=result["lh"],
tip_rack=result["tip_rack"],
source_plate=result["source_plate"],
dest_plate=result["dest_plate"],
)based on workflow.py in w02-liquid-handling
@phase(name="Phase 01: Setup")
async def setup(device_name: str) -> dict:
lh = await create_liquid_handler_step(
simulate=simulate, device_name=device_name
)
lh, tip_rack, source_plate, dest_plate = (
await setup_deck_step(lh=lh)
)
if not simulate:
await operator_confirm(
"Deck setup complete. Confirm layout, then resume."
)
return {
"lh": lh,
"tip_rack": tip_rack,
"source_plate": source_plate,
"dest_plate": dest_plate,
}based on phase_01_setup.py in w02-liquid-handling
@step(name="Step: Transfer Column")
async def transfer_column_step(
lh, tip_rack, source_plate, dest_plate,
column, mix_after=False,
) -> None:
tip_spots = tip_rack.next_tips(count=8)
await lh.pipettes.pick_up_tips(
channels=CHANNELS, spots=tip_spots
)
await lh.pipettes.aspirate(
source=source_wells, parameters=aspirate_params
)
await lh.pipettes.dispense(
target=dest_wells, parameters=dispense_params
)
await lh.pipettes.discard_tips(channels=CHANNELS)based on shared/steps/liquid_handler/_steps.py
async def create_liquid_handler(simulate: bool, device_name: str):
lh = MicrolabSTARMock() if simulate else MicrolabSTAR(name=device_name)
await lh.configure()
await lh.initialize()
return lhbased on shared/steps/liquid_handler/_helpers.py
async def get_plateloc_service(device_name: str):
client = Client()
service = await client.get_service_by_name(device_name)
return service, clientthe connector-lookup pattern used throughout shared/
Exercise 5: try it (simulated)
This mirrors exactly what w01-hello-world logs when it runs.
This page doesn't actually execute Python or install the real SDK. When you're ready, run it on your local machine:
git clone https://gitlab.com/unitelabs/workflows/workflow-template.git
cd workflow-template
uv sync --directory w01-hello-world
uv run --directory w01-hello-world workflow