UniteLabs

Workflow template

Clone a production-ready workflow repository, run a workflow in simulation, and deploy any of the reference workflows to the platform.

The workflow template contains four standalone examples organized as workflows, phases, and steps. W01 runs without platform credentials or hardware. Clone the repository to run the examples or use it as the starting point for a workflow project.

Four reference workflows

The template ships four examples of increasing complexity. Each lives in its own top-level directory as a standalone Python package with its own pyproject.toml, uv.lock, version, and [tool.unitelabs.workflow] metadata. Each workflow declares its own dependencies and releases on its own schedule, while shared code lives in the shared/ package next to them.

#WorkflowDemonstrates
W01hello_worldMinimal @workflow that logs the SDK version. Requires no hardware or platform connection.
W02liquid_handlingEight-channel plate-to-plate transfer on a mocked Hamilton Microlab STAR. Demonstrates the workflow, phase, and step layers without hardware.
W03plateloc_sealerOperator-guided plate sealing. Shows human in the loop with operator_confirm() inside a phase.
W04tecan_fluent_controlMinimal demo for a Tecan Fluent: connects to FluentControl, verifies the channel, and optionally runs a named method. Shows a workflow driving vendor software through a connector.

Each workflow is a self-contained Python package at the repo root. The sibling shared/ library holds the cross-workflow pieces (per-instrument config, custom labware, reusable @step steps, helpers) and is consumed by each workflow via shared = { path = "../shared", editable = true } in its [tool.uv.sources].

Project structure

The directory layout maps directly onto the taxonomy. Reusable @step implementations live in shared/src/shared/steps/, grouped by component. Each workflow lives in its own top-level directory named w<NN>-<name>: a two-digit number followed by a lowercase name with hyphens, for example w02-liquid-handling.

workflow-template/                       # uv workspace root (monorepo)
├── shared/                              # Shared library package. Add as many custom packages.
│   ├── src/shared/
│   │   ├── config/                      # Device configs (plateloc.py, microlab_star.py, …)
│   │   ├── library/                     # Custom labware definitions
│   │   │   └── labware/
│   │   ├── steps/                       # Custom step library: reusable @step functions by component
│   │   │   ├── plateloc/
│   │   │   │   ├── _helpers.py          # Plain async SDK wrappers (not tracked)
│   │   │   │   └── _steps.py            # @step public API
│   │   │   └── liquid_handler/
│   │   │       ├── _helpers.py
│   │   │       └── _steps.py
│   │   └── utils/                       # operator_input.py, etc.
│   ├── tests/
│   ├── pyproject.toml
│   └── uv.lock
│
├── w01-hello-world/                     # Standalone workflow app
│   ├── src/w01_hello_world/
│   │   ├── __main__.py                  # `uv run workflow` entrypoint
│   │   └── workflow.py
│   ├── tests/
│   ├── pyproject.toml                   # workflow specific pyproject.toml
│   └── uv.lock
│
├── w02-liquid-handling/                 # Standalone workflow app
│   ├── src/w02_liquid_handling/
│   │   ├── __main__.py
│   │   ├── workflow.py
│   │   ├── phase_01_setup.py
│   │   └── phase_02_transfer.py
│   └── ...
│
├── w03-plateloc-sealer/                 # Standalone workflow app
│   └── ...
│
├── w04-tecan-fluent-control/            # Standalone workflow app
│   ├── src/w04_tecan_fluent_control/
│   │   ├── __main__.py
│   │   ├── workflow.py
│   │   ├── phase_01_connect.py
│   │   └── phase_02_run_method.py
│   └── ...
│
├── scripts/
│   └── deploy.py                        # See usage in pyproject.toml [tool.unitelabs.workflow])
│
├── .github/                             # CI
├── .gitlab-ci.yml
├── .env / .env.example                  # BASE_URL, AUTH_URL, CLIENT_ID, CLIENT_SECRET
├── AGENTS.md                            # authoring rules: layers, retry policy, naming
├── pytest.ini
├── ruff.toml
├── workflow-template.code-workspace     # VS Code multi-root workspace
└── README.md

The repository's AGENTS.md is the authoritative authoring guide for retry policy, naming conventions, and what belongs where.

Why is every workflow its own package? Workflows often need different unitelabs-* SDK versions, for example when they target different tenants or instrument generations. Because each workflow has its own lockfile (uv.lock, where uv records the exact version of every dependency), you can upgrade one workflow without touching the others.

Clone and deploy

End-to-end: clone the repo, run the reference workflows in your IDE, then deploy a workflow to the platform.

1. Clone the repository

git clone https://gitlab.com/unitelabs/workflows/workflow-template.git
cd workflow-template

In your file explorer, navigate into the cloned workflow-template/ directory and open workflow-template.code-workspace with VS Code. It opens directly into the pre-configured multi-root workspace, with each workflow and shared/ loaded as a side-by-side root. After you sync dependencies in step 3, the per-folder .venv is recognized natively.

2. Configure credentials

Copy .env.example to .env and fill in the four values. These credentials are required for the UniteLabs Python client object to establish a connection to the platform API for e.g. connector communication:

cp .env.example .env
.env
BASE_URL=https://api.<your-tenant>.unitelabs.io/
AUTH_URL=https://auth.<your-tenant>.unitelabs.io/realms/<tenant-id>/protocol/openid-connect/
CLIENT_ID=<your-client-id>
CLIENT_SECRET=<your-client-secret>
Need help getting these values? See SDK installation for how to set the credentials. W02 and W03 will also require access to UniteLabs private packages. If you have not set up a .netrc file yet to get access, follow the instructions in the SDK installation section.

3. Install a workflow's dependencies

Each workflow syncs independently. Sync shared first, then any workflow you want to run locally:

uv sync --directory shared
uv sync --directory w01-hello-world
uv sync --directory w02-liquid-handling
uv sync --directory w03-plateloc-sealer
uv sync --directory w04-tecan-fluent-control

Each command creates the workflow's .venv/ with its declared dependencies and the shared library installed editable. Edits to shared/ are picked up without re-sync.

4. Run W01 Hello World

If you can run this workflow, you have successfully installed the UniteLabs SDK and executed a workflow using the workflow hierarchy. You are one step closer to deploying to the platform!

From the project root, run the following command:

uv run --directory w01-hello-world workflow

You can also navigate into the workflow project and run it directly from there using

uv run workflow

5. Run W02 Liquid Handling locally against the mock

W02 runs end-to-end against the liquid handling mock, with no hardware and no credentials. The mock lives in the LH SDK, is stateless, and only validates that the workflow will run (a compiler-style check that inputs are theoretically valid). It does not simulate a device, so you will not see realistic timing or device state, but it is the fastest way to verify your environment and catch configuration errors.

uv run --directory w02-liquid-handling workflow

The workflow console script is declared in each workflow's [project.scripts]. You should see workflow engine logs for the setup phase followed by eight column-by-column transfers in the transfer phase.

Pass extra args via the same command, e.g. uv run --directory w02-liquid-handling workflow --hardware to run against a real Hamilton Microlab STAR. That path requires .env credentials and a reachable device.

6. Deploy a workflow to the platform

Requirements before the first deploy:

  • A populated .env at the repo root (BASE_URL, AUTH_URL, CLIENT_ID, CLIENT_SECRET). This is the same one you set up in step 2. scripts/deploy.py loads it automatically and uses it to authenticate against the platform.

The deploy script does three things. It finds every workflow directory (any w<NN>-<name>/ folder containing a pyproject.toml), packs the workflow directory together with a copy of the shared/ library into a zip bundle, and uploads that bundle to the platform. If the workflow already exists on the platform, it is updated in place; otherwise a new entry is created.

# Deploy every workflow
uv run scripts/deploy.py --all

# Deploy a single workflow by slug
uv run scripts/deploy.py w02-liquid-handling

# Deploy as DEV (display name prefixed [DEV] on the platform)
uv run scripts/deploy.py w02-liquid-handling --channel dev

See Deploy a workflow for the full CLI reference.

Open the UniteLabs Workflows page to confirm the workflow appears with the correct name and description.

7. Run W03 PlateLoc Sealer against a simulated connector

W03 drives a real connector instead of an in-SDK mock. From the workflow's perspective the simulation behaves 1:1 like the physical device, including state and transitions, because the simulation lives inside the connector itself, not inside the SDK.

Requirements before the first run:

  • A running connector simulating a PlateLoc connected to the platform. Verify it appears on the Devices page. See how to start a connector.
  • .env to authenticate against the platform and reach the connector.

Start the workflow:

uv run --directory w03-plateloc-sealer workflow

The workflow runs two phases:

  1. Prepare connects to the PlateLoc, moves the stage to the OUT position, and calls operator_confirm() so the operator can place a plate.
  2. Seal resumes after operator confirmation and moves the stage to the IN position to seal the plate.

While the workflow is paused, open the run in the UniteLabs Workflows page. An Input required banner appears at the top of the run with the message "This workflow is waiting for user input to continue". Click Provide Input, tick the Confirmed checkbox in the dialog, and click Submit Input to resume the run. This is the canonical human in the loop pattern: a phase parks the run on an explicit operator checkpoint rather than polling or sleeping.

The device name is resolved from shared/src/shared/config/plateloc.py (INSTRUMENT_NAME). If your connector is registered under a different name on the platform, override it: uv run --directory w03-plateloc-sealer workflow --device-name "<your-name>".

Releasing a workflow

Each workflow is released on its own, with a git tag that combines two things: the workflow's slug and its version. The slug is the workflow's unique short name. It is set as [project].name in the workflow's pyproject.toml and matches the directory name, for example w02-liquid-handling. The tag w02-liquid-handling/v1.2.0 therefore releases version 1.2.0 of that workflow. The version in the tag must match [project].version.

To release w02-liquid-handling v1.2.0:

# 1. Bump the workflow's version
$EDITOR w02-liquid-handling/pyproject.toml
# (set [project].version = "1.2.0")

# 2. Commit
git commit -am "release: w02-liquid-handling v1.2.0"

# 3. Tag and push
git tag w02-liquid-handling/v1.2.0
git push origin main --tags

CI matches the per-workflow tag pattern and runs scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0 --channel prd. The script parses the tag, verifies the pyproject version matches 1.2.0 (refuses otherwise; the version in pyproject.toml is the one that counts), ships the bundle, and adds v1.2.0 to the platform tags. See CI/CD for workflows for the three-channel deploy model.

Adding a new workflow

Mechanical checklist (see AGENTS.md for the full authoring rules):

  1. Create a directory w<NN>-<name>/ at the repo root, for example w05-dna-extraction/. Pick the next free two-digit number and a lowercase name with hyphens.
  2. Inside it, create:
    • pyproject.toml mirroring an existing workflow (set [project].name, version, description, dependencies, [tool.unitelabs.workflow]).
    • src/w05_dna_extraction/workflow.py with the @workflow function. The package directory uses the same name with underscores instead of hyphens.
    • One phase_<NN>_<name>.py file per phase, next to workflow.py.
  3. Add new shared code to shared/src/shared/{config,library,steps,utils}/ if needed.
  4. Run uv sync --directory w05-dna-extraction to generate the workflow's lockfile.
  5. Add the workflow's path to the folders array in workflow-template.code-workspace.

No central registry to edit. scripts/deploy.py discovers workflows automatically.

Next steps