UniteLabs

Deploy a workflow

Bundle a workflow from a workflow-template repo and ship it to the UniteLabs platform with scripts/deploy.py.

In this guide you will use the scripts/deploy.py CLI shipped with the workflow template to bundle one (or every) workflow in your repo and register it with the UniteLabs platform.

Prerequisites

  • A repo cloned (or generated) from the workflow template. Each top-level workflow directory (w<NN>-<name>/, for example w02-liquid-handling/) is a standalone workflow package with its own pyproject.toml declaring [project].name, [project].version, and [tool.unitelabs.workflow] metadata.
  • uv (latest) and Python 3.12+.
  • UniteLabs API credentials: BASE_URL, AUTH_URL, CLIENT_ID, CLIENT_SECRET.

Store your credentials

Copy .env.example to .env at the repo root and fill in the four values:

.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>

scripts/deploy.py loads .env automatically when run locally. CI/CD pipelines set the same four variables as platform secrets — see CI/CD for workflows.

.env is in the template's .gitignore. Never commit credentials.

What the deploy script does

scripts/deploy.py is a single, self-contained file. It declares its own dependencies (requests, python-dotenv) inline, so uv run scripts/deploy.py works from a fresh clone without an install step. There is no pyproject.toml at the repo root.

For each workflow you deploy, the script:

  1. Finds the workflow. It scans the repo for directories that match w<NN>-<name>/ and contain a pyproject.toml, then picks the one you asked for by its slug. The slug is the workflow's unique short name: the [project].name from its pyproject.toml, identical to the directory name (for example w02-liquid-handling).
  2. Builds a bundle. It creates a zip containing the unchanged workflow directory, plus a copy of shared/ at the top level of the zip. shared/ is the repo's single library package: it sits next to the workflows and holds the code they all import. Because it travels inside the bundle, import shared.steps... works on the platform exactly as it does locally.
  3. Collects the dependencies. The workflow and shared/ each declare their dependencies in their own pyproject.toml. The script merges the two lists and drops duplicates. If both declare the same package, the workflow's version wins. The platform installs the runtime environment from this merged list.
  4. Authenticates against the platform with the OAuth2 client credentials from your .env: a machine-to-machine login with client ID and secret, no browser involved.
  5. Creates or updates the platform record. It looks up the workflow by its display_name. If a record exists, the script updates it; if not, it creates a new one. If several records share the same display name, the script stops instead of guessing, so it never overwrites the wrong workflow.
  6. Restores soft-deleted records. Deleting a workflow in the UI only disables it. On redeploy the script re-enables the record and refreshes its description and tags, so you don't end up with a hidden duplicate.

Commands

All five forms are mutually exclusive; pick whichever fits your need.

Deploy a single workflow

uv run scripts/deploy.py w02-liquid-handling

The positional argument is the workflow's slug[project].name in its pyproject.toml.

Deploy every workflow

uv run scripts/deploy.py --all

Deploy only what changed

uv run scripts/deploy.py --changed-from origin/main

Runs git diff --name-only <ref>..HEAD and deploys only the workflows whose directory was touched. Use it locally to preview what a merge would redeploy, or in CI on push-to-main (see CI/CD for workflows).

When everything counts as changed. A change under shared/ or to scripts/deploy.py itself marks every workflow as changed, because shared/ is copied into every bundle and the script builds every bundle. This rule is implemented in one place, the helper function affected_workflows() inside the script.

Deploy a tagged release

uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0

The tag combines the workflow's slug and its version, separated by /v. The script splits the tag, checks that the workflow's [project].version matches 1.2.0, and adds v1.2.0 to the platform tags. If the pyproject version and the tag disagree, the script refuses to deploy. The version in pyproject.toml is the one that counts.

uv run scripts/deploy.py --list
uv run scripts/deploy.py --list --changed-from origin/main

Prints the selected slugs (one per line) and exits before loading .env or authenticating. CI pipelines (or you, locally) can use this to sanity-check the selection without needing credentials.

Channels: DEV / STG / PRD

uv run scripts/deploy.py w02-liquid-handling --channel dev
uv run scripts/deploy.py --all --channel stg
uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0 --channel prd

--channel layers on top of any of the forms above. It prepends [DEV] /[STG] /[PRD] to the platform display_name and adds the channel name as a platform tag. The result: a single tenant can host parallel DEV, STG, and PRD records of the same workflow as distinct platform entries. Running a workflow against real instruments on DEV therefore cannot disturb the record production uses.

The channel-to-trigger mapping is established by CI; locally you'll usually omit --channel (deploys land as untagged "main" records) or pass --channel dev when iterating against a shared DEV tenant. The full mapping is in CI/CD for workflows.

Extra platform tags

uv run scripts/deploy.py w02-liquid-handling --tag prod --tag stable

--tag/-t is repeatable and stacks with --git-tag's version label and --channel's channel tag. Use it for ad-hoc labels (a feature flag, a customer name) without bumping the workflow's version.

Release a workflow

Each workflow ships independently. To release w02-liquid-handling v1.2.0:

  1. Bump the version. Edit w02-liquid-handling/pyproject.toml and set [project].version = "1.2.0".
  2. Commit:
    git add w02-liquid-handling/pyproject.toml
    git commit -m "release: w02-liquid-handling v1.2.0"
    
  3. Tag and push:
    git tag w02-liquid-handling/v1.2.0
    git push origin main --tags
    

The tag push triggers deploy-prd in CI — see CI/CD for workflows. You can also run the release locally:

uv run scripts/deploy.py --git-tag w02-liquid-handling/v1.2.0 --channel prd
The tag has to name the workflow. CI only reacts to tags of the form <slug>/v<X.Y.Z>, because the slug inside the tag is what tells the pipeline which of the repo's workflows to release:
  • w02-liquid-handling/v1.2.0 deploys w02-liquid-handling at version 1.2.0 and leaves every other workflow in the repo untouched.
  • v1.2.0 matches no rule. Nothing is deployed and no pipeline runs.

Verify

Open the UniteLabs Workflows page and confirm the workflow appears with the expected [CHANNEL] prefix, version, and tags.

The script's terminal output shows what landed on the platform: display name, resolved entrypoint, bundle size, dependency count, and whether the record was created or updated.

Next steps

  • Set up CI/CD — wire scripts/deploy.py into GitHub Actions or GitLab CI with the three-channel (dev/stg/prd) model.
  • Trigger a workflow run — confirm the deployment end-to-end.
  • AGENTS.md — authoring rules for adding new workflows to the template.