# Switchyard Release Cycle: How Tag-Driven Automation Publishes to PyPI and crates.io

> Discover the Switchyard release cycle. Learn how tag-driven automation publishes to PyPI and crates.io, ensuring efficient and reliable updates for Python and Rust.

- Repository: [NVIDIA-NeMo/Switchyard](https://github.com/NVIDIA-NeMo/Switchyard)
- Tags: internals
- Published: 2026-08-23

---

**TLDR:** Switchyard uses a tag-driven release workflow where pushing a `vMAJOR.MINOR.PATCH` git tag triggers a fully automated pipeline that validates, tests, and publishes both Python wheels and Rust crates, while manual dev builds remain opt-in and never reach production registries.

The open-source project **NVIDIA-NeMo/Switchyard** combines a Python API with a Rust core. Its release cycle is defined in the [`.github/workflows/publish.yml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/.github/workflows/publish.yml) file, which separates official production releases from optional development artifact builds. This guide walks through every phase of the Switchyard release cycle, explains how to trigger a release, and shows you which configuration files control the output.

## Overview of the Switchyard Release Cycle

Switchyard follows an OSS-style, tag-driven release cadence. A maintainer pushes a git tag like `v0.2.0`, and the CI workflow automatically performs validation, builds compiled artifacts for a full OS/CPU matrix, and publishes them to both **PyPI** and **crates.io**. The cycle ensures that the Python package version in [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) and the Rust workspace version in [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml) are always identical — a single source of truth that the CI enforces.

## The Tag-Driven Release Pipeline

When a valid tag is pushed, the workflow in `.github/​workflows/publish.yml` executes the following steps sequentially.

### 1. Release Reference Validation

As implemented in the [`publish.yml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/publish.yml) file (lines 49-78), the workflow first checks that the tag matches the `vX.Y.Z` pattern. It then compares the version string in [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) and [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml) to ensure they are mathematically identical. Any mismatch stops the pipeline immediately, preventing inconsistent distributions.

### 2. Python Checks

The `python-release-checks` job (lines 82-103) runs `ruff` for linting, performs type checking, and executes the full Python test suite across all supported Python interpreters — versions 3.10, 3.11, 3.12, 3.13, and 3.14. This stage verifies that the package behaves correctly before any binary is built.

### 3. Rust Checks

Meanwhile, the `rust-release-checks` job (lines 104-118) formats the Rust code with `cargo fmt`, lints it with `clippy`, and runs the entire Rust workspace test suite. These tests run in parallel with the Python checks, ensuring both language stacks are healthy.

### 4. Source Distribution Build

The `source-dist` job (lines 20-41) uses `maturin` to build a source distribution (sdist). If a development matrix is requested, it will also stamp a `.dev` version into the metadata first. The job then verifies that all required license files are present in the archive.

### 5. Native Wheel Matrix Build

The `wheels` job (lines 65-107) compiles native wheels for the following platforms:

- Linux x86_64 (manylinux)
- Linux aarch64 (manylinux)
- macOS x86_64
- macOS arm64
- Windows x86_64
- Windows arm64

Each produced wheel is immediately smoke-tested on both Python 3.10 and Python 3.14, confirming the wheel is importable and the core ABI works across its targeted Python versions.

### 6. Final Publish to PyPI and crates.io

The `publish` job (lines 28-47) triggers only when the workflow was started by a tag push (not a manual dispatch). It uploads the previously built sdist and wheels to PyPI using `uv publish`. Simultaneously, the `publish-rust-crates` job publishes five Rust crates from the workspace in dependency order: `switchyard-protocol`, `switchyard-translation`, `switchyard-libsy`, `switchyard-llm-client`, and `switchyard-server` to crates.io.

## Dev-Only Artifact Builds

Not every build must be a production release. Switchyard provides two manual triggers for developers who need early testing artifacts. These are initiated via the GitHub Actions UI by setting the `build_dev_artifact` or `build_dev_matrix` input parameters.

- **`build_dev_artifact = true`** — Builds a single manylinux x86_64 wheel, uploads it as a GitHub artifact, and keeps it for one day.
- **`build_dev_matrix = true`** — Builds the full sdist + wheel matrix exactly like a production run, but does **not** publish anything to PyPI.

Both paths use the helper script [`scripts/release/set_dev_wheel_version.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/scripts/release/set_dev_wheel_version.py) to temporarily add a `.dev` suffix to the version number in [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml). This prevents accidental overwrites of an official release.

## Code Examples: Triggering a Release

### Production Release

To trigger a full production release, a maintainer simply creates a tag that matches the version in both [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) and [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml):

```bash
git tag v0.2.0   # Must match version 0.2.0 in both files

git push origin v0.2.0

```

The entire pipeline runs automatically — no manual steps needed.

### Development Build (Preview)

To do a dev wheel or matrix build from the GitHub Actions tab manually, use the workflow dispatch UI. The following local command can preview the temporary version that will be altered:

```bash
python scripts/release/set_dev_wheel_version.py 0.0.1.dev0 --print-version

# Output: 0.0.1.dev0

```

Then set the `build_dev_artifact` or `build_dev_matrix` flag in the workflow UI when you run the workflow.

## Key Files in the Release Cycle Ath

These files are the foundation of Switchyard’s release system:

| File | Role |
|------|------|
| [`docs/internal/release_workflow.md`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/docs/internal/release_workflow.md) | Human-readable documentation of the entire release process. |
| [`.github/workflows/publish.yml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/.github/workflows/publish.yml) | CI definition that drives all validation, builds, and publishing steps. |
| [`scripts/release/set_dev_wheel_version.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/scripts/release/set_dev_wheel_version.py) | Helper script for stamping temporary `.dev` versions into [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml). |
| [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) | Declares the Python package version and build configuration. |
| [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml) | Declares the Rust workspace version and must match [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml). |

These components collectively enforce a consistent, safe release cadence — ensuring every release is reproducible, tested, and publishable.

## Summary

- Switchyard’s release cycle is **driven by git tags** of the format `vX.Y.Z`.
- The workflow **validates the version consistency** across [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) and [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml) to avoid mismatched releases.
- A complete **wheel matrix** is built for Linux, macOS, and Windows, each smoke-tested on Python 3.10 and 3.14.
- Production publishing **only happens on tag pushes** — manual dev builds never reach PyPI or crates.io.
- Development artifacts are **opt-in** and use a `.dev` version stamp via [`scripts/release/set_dev_wheel_version.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/scripts/release/set_dev_wheel_version.py).

## Frequently Asked Questions

### How often does Switchyard release new versions?

The release cycle is **not time-based** but triggered by the maintainers on-demand. Whenever they decide to cut a release, they create a `vMAJOR.MINOR.PATCH` tag and push itto GitHub. There is no fixed commitment to a cadence like monthly or quarterly.

### Can I publish to PyPI without a tag?

No. The `publish` job in [`.github/workflows/publish.yml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/.github/workflows/publish.yml) only runs when the workflow is triggered by a tag push. Manual `workflow_dispatch` runs, even with `build_dev_matrix`, will never upload packages to PyPI or crates.io. That design keeps development builds separate from official releases.

### What happens if [`pyproject.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/pyproject.toml) and [`Cargo.toml`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/Cargo.toml) versions don’t match?

The workflow fails during the validation step (lines 49-78) before any build or test begins. This makes a version mismatch immediately visible and prevents publishing an inconsistent package where the Python and Rust layers are out of sync.

### How can I test a release before pushing a tag?

Maintainers can use the **development matrix** trigger from the GitHub Actions UI. Setting `build_dev_matrix=true` runs the entire build and test pipeline but skips the publishing stage. The generated wheels are available as GitHub artifacts for inspection and smoke testing.