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

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 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 and the Rust workspace version in 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 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 and 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 to temporarily add a .dev suffix to the version number in 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 and Cargo.toml:

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:

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 Human-readable documentation of the entire release process.
.github/workflows/publish.yml CI definition that drives all validation, builds, and publishing steps.
scripts/release/set_dev_wheel_version.py Helper script for stamping temporary .dev versions into pyproject.toml.
pyproject.toml Declares the Python package version and build configuration.
Cargo.toml Declares the Rust workspace version and must match 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 and 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.

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →