UniteLabs

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.

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.

workflow.py
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 lh

based 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, client

the connector-lookup pattern used throughout shared/

Exercise 5: try it (simulated)

This mirrors exactly what w01-hello-world logs when it runs.

This is a simulation, not a live sandbox.

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