Workflow template
The workflow template is a reference implementation of the UniteLabs three-layer taxonomy: Workflow → Phase → Step. Clone it to learn the structure, or fork it as the starting point for your own project. You can run the first workflow in this template without any access credentials or special setup. Try it out!
Three reference workflows
The template ships three progressive examples, each as an independent standalone package under its own top-level directory. Each has its own pyproject.toml, uv.lock, version, and [tool.unitelabs.workflow] metadata, so bumping one doesn't affect the others. This enables you to specify dependencies on a per workflow basis, while being able to share code easily between them. This makes maintainability and deployment easier, but more on that later.
| # | Workflow | Demonstrates |
|---|---|---|
| W01 | hello_world | Minimal @workflow that logs the SDK version. This is a sanity check that your environment is wired correctly. No hardware, no connection to the platform is required. |
| W02 | liquid_handling | 8-channel plate-to-plate transfer on a Hamilton Microlab STAR. Runs with our mocked Microlab STAR to simulate the workflow run by default. Exercises the full Workflow → Phase -> Step layer without hardware. |
| W03 | plateloc_sealer | Operator-guided plate sealing. Shows human in the loop with pause_flow_run() inside a phase. |
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 under steps/{component}/; full protocols live under workflows/w{NN}_{name}/.
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
│ └── ...
│
├── 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, API_URL
├── 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-* versions without dragging the others along.Clone and deploy
End-to-end: clone the repo, run workflows W01-W03 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 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
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.
scripts/deploy.py discovers workflows by globbing w*-*/pyproject.toml, builds a bundle (workflow dir verbatim + shared/src/shared/ hoisted flat at the bundle root), and POSTs/PATCHes it to the platform.
# 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 flow runs two phases:
- Prepare connects to the PlateLoc, moves the stage to the OUT position, and calls
pause_flow_run()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 ships independently via a git tag of the form <slug>/v<X.Y.Z> (slug = [project].name, kebab-case).
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, since pyproject is the source of truth), 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
w{**}-{kebab-name}/at the repo root. - Inside it, create:
pyproject.tomlmirroring an existing workflow (set[project].name, version, description, dependencies,[tool.unitelabs.workflow]).src/w{**}_{snake_name}/workflow.py(the@flow(retries=0)function).src/w{**}_{snake_name}/phase_{**}_{name}.pyfiles for each phase.
- Add new shared code to
shared/src/shared/{config,library,steps,utils}/if needed. uv sync --directory w{**}-{kebab-name}to generate the workflow's lockfile.- Add the workflow's path to
.vscode/workflow-template.code-workspacefoldersarray.
No central registry to edit. scripts/deploy.py discovers workflows automatically.
Next steps
- Author your own workflow: follow the rules in
AGENTS.md(layer rules, retry policy, naming conventions). - 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 W02 as the reference pattern.
What is a workflow?
Workflows are the core automation primitive 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 scaffold your own first workflow in under 15 minutes.