Workflow template
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.
| # | Workflow | Demonstrates |
|---|---|---|
| W01 | hello_world | Minimal @workflow that logs the SDK version. Requires no hardware or platform connection. |
| W02 | liquid_handling | Eight-channel plate-to-plate transfer on a mocked Hamilton Microlab STAR. Demonstrates the workflow, phase, and step layers without hardware. |
| W03 | plateloc_sealer | Operator-guided plate sealing. Shows human in the loop with operator_confirm() inside a phase. |
| W04 | tecan_fluent_control | Minimal 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.
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
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>
.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.
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
.envat the repo root (BASE_URL,AUTH_URL,CLIENT_ID,CLIENT_SECRET). This is the same one you set up in step 2.scripts/deploy.pyloads 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.
.envto authenticate against the platform and reach the connector.
Start the workflow:
uv run --directory w03-plateloc-sealer workflow
The workflow runs two phases:
- Prepare connects to the PlateLoc, moves the stage to the OUT position, and calls
operator_confirm()so the operator can place a plate. - 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.
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):
- Create a directory
w<NN>-<name>/at the repo root, for examplew05-dna-extraction/. Pick the next free two-digit number and a lowercase name with hyphens. - Inside it, create:
pyproject.tomlmirroring an existing workflow (set[project].name, version, description, dependencies,[tool.unitelabs.workflow]).src/w05_dna_extraction/workflow.pywith the@workflowfunction. The package directory uses the same name with underscores instead of hyphens.- One
phase_<NN>_<name>.pyfile per phase, next toworkflow.py.
- Add new shared code to
shared/src/shared/{config,library,steps,utils}/if needed. - Run
uv sync --directory w05-dna-extractionto generate the workflow's lockfile. - Add the workflow's path to the
foldersarray inworkflow-template.code-workspace.
No central registry to edit. scripts/deploy.py discovers workflows automatically.
Next steps
- Author your own workflow: copy W01 and build your first own workflow. The repo's
AGENTS.mdholds the detailed authoring rules (layers, retry policy, naming). - Deploy a workflow: the full
scripts/deploy.pyCLI reference. - Set up CI/CD: wire deploys to GitHub Actions or GitLab CI with the three-channel (dev/stg/prd) model.
- Trigger runs via the API: start a deployed workflow programmatically.
- Add human-in-the-loop steps: use W03 as the reference pattern.
What is a workflow?
Workflows are the core automation building block in UniteLabs: reproducible, versioned processes that coordinate instruments, data, and scientific logic.
Your First Workflow
Clone the workflow template, run the hello-world example locally, and set up your own first workflow in under 15 minutes.