Releases & Versioning.
How a version of relay comes to exist, what the alpha/beta/rc/stable ladder means, and how to pick a download channel.
0.1.0-alpha.N → -beta.N → -rc.N → 0.1.0 ladder. The wire protocol between hub and node, the on-disk state layout and the CLI surface can all change without a deprecation period; /v1 tracks the OpenAI shape and is the most stable thing here. Nothing is accredited or covered by a compatibility promise.Releases are a consequence of merging, not a manual step
relay is released as a direct consequence of merging to main — nobody tags, builds, or uploads anything by hand, and pushing a tag yourself does nothing: there is no tag-push trigger. The only input to the release process is commit history on main.
| Step | What happens |
|---|---|
| 1. Merge to main | The usual PR flow. CI must be green. |
| 2. release-please reacts | Scans conventional-commit messages since the last release and opens or updates a standing release PR — just a CHANGELOG.md diff and a version bump, titled like chore(main): release 0.1.0-alpha.2. Every other merge just refreshes this PR; it ships nothing on its own. |
| 3. Merge the release PR | That merge is what release-please treats as “ship it”: it creates the git tag and the GitHub Release, changelog and all, in the same job run. |
| 4. goreleaser runs | Only on the push that just created a release. Builds every archive/package, signs the checksum file with cosign, generates SBOMs, attests build provenance, and uploads the versionless “stable alias” downloads. |
| 5. Watch it | gh run watch --repo opalengine/relay |
| 6. Verify for real | Download one artifact and actually run the verification steps — a green checkmark is not proof the artifacts are what they claim to be. |
The alpha → beta → rc → stable ladder
This project is in alpha. The version string comes entirely from release-please-config.json, not from anything committed in the repo:
"versioning": "prerelease",
"prerelease": true,
"prerelease-type": "alpha.1",
"packages": { ".": { "initial-version": "0.1.0-alpha.1" } }| Key | Job |
|---|---|
versioning: "prerelease" | Selects the version strategy at all — without it, prerelease-type is ignored. |
prerelease: true | Only marks the GitHub release object as a pre-release. |
initial-version | What the very first release is called when no tag exists — omit it and release-please falls back to 1.0.0, which is exactly what happened on the first run. |
prerelease-type is not a fixed label — it is the literal identifier release-please appends the first time it starts a new pre-release series, and from then on only the trailing number increments while conventional-commit types are ignored for version-bump purposes. "alpha.1" rather than a bare "alpha" is deliberate: a bare label would make the first release the numberless 0.1.0-alpha, so the series would run alpha, alpha.1, alpha.2 — one release short of a clean count. So today the series runs 0.1.0-alpha.1, 0.1.0-alpha.2, 0.1.0-alpha.3, ...
Moving to the next stage is a one-time, explicit action, not something more merges do on their own — a commit on main carrying a Release-As: footer:
Release-As: 0.1.0-beta.1release-please takes that as the exact next version, overriding the trailing-number rule; from beta.1 the trailing number increments again until the next Release-As: commit. Cutting the actual 0.1.0 — no suffix — is a different move: flip "prerelease" to false and merge. That is also the point where /releases/latest starts resolving to something.
Picking a download channel
scripts/install.sh — the one installer for every platform, Windows included, where it runs under Git Bash — accepts --channel (rc is accepted as an alias for prerelease) for resolving "latest" when no explicit --version is given, which always wins over --channel when both are set:
| Channel | Resolution |
|---|---|
auto | Default. Tries GET /releases/latest (newest stable) first; if that returns no release at all — true for the whole alpha/beta/rc period — falls back to the newest release of any kind from GET /releases. |
stable | GET /releases/latest only. Errors clearly ("no stable release exists yet") instead of falling back. |
prerelease | (alias rc) — GET /releases, newest first, first entry with "prerelease": true. |
The source and releases open soon. Until then there is nothing to install.
auto is the right default specifically because the project is in alpha today — once a stable 0.1.0 exists, auto starts resolving to it with no installer change needed.
Downloads and the versionless aliases
Every goreleaser run additionally copies each versioned archive/package to a versionless alias on the same release:
| Versioned | Alias |
|---|---|
relay_<version>_linux_amd64.tar.gz | relay_linux_amd64.tar.gz |
relay_<version>_linux_arm64.tar.gz | relay_linux_arm64.tar.gz |
relay_<version>_darwin_amd64.tar.gz | relay_darwin_amd64.tar.gz |
relay_<version>_darwin_arm64.tar.gz | relay_darwin_arm64.tar.gz |
relay_<version>_windows_amd64.zip | relay_windows_amd64.zip |
relay_<version>_windows_arm64.zip | relay_windows_arm64.zip |
relay_<version>_linux_{amd64,arm64}.{deb,rpm,apk} | relay_linux_{amd64,arm64}.{deb,rpm,apk} |
relay_<version>_checksums.txt | relay_checksums.txt |
/releases/latest redirect only ever resolves to a non-prerelease release — every alpha, beta and rc release is marked as a pre-release and excluded from it, regardless of how far along the ladder it is. So for as long as relay is on the alpha/beta/rc ladder, that URL has nothing to resolve to; link a specific pre-release's assets by its exact tag instead, e.g. /releases/download/v0.1.0-alpha.3/relay_linux_amd64.tar.gz. A site building download links during this period has to query GET /repos/opalengine/relay/releases and pick a tag — switching to the versionless /latest/download/... form only once a stable release exists.What a release contains
relay_<version>_{linux,darwin}_{amd64,arm64}.tar.gzandrelay_<version>_windows_{amd64,arm64}.zip— the Windows amd64 archive also containsrelay.exe; the Linux archives also contain a Linux tray build ofrelay-tray.relay_<version>_linux_{amd64,arm64}.{deb,rpm,apk}.relay_<version>_checksums.txtand its.sigstore.jsonbundle — verify withcosign verify-blob --bundle.- The versionless stable aliases of all of the above.
- A build-provenance attestation on every file, verifiable with
gh attestation verify <file> --owner opalengine. - An SPDX SBOM per archive.
- Release notes generated by release-please from conventional-commit prefixes (
feat:,fix:,perf:,docs:,chore(deps):), with goreleaser's own verification header/footer appended.
Both checks are keyless: cosign signs the checksums file under a short-lived certificate tied to this repository's own GitHub Actions OIDC identity, and the build-provenance attestation is issued the same way — there is no long-lived private key to leak or rotate. Neither one is a platform code-signing signature: there is no Windows Authenticode signature and no macOS notarization on any archive, so a fresh download can still trigger a Windows SmartScreen or macOS Gatekeeper warning even when the cosign and attestation checks both pass.
Homebrew and Scoop are not part of this yet.
Every archive bundles a pinned llama.cpp
Each of the six archives carries a pinned, checksum-verified llama.cpp llama-server/ggml-rpc-server build plus the shared libraries they need, laid out beside relay. scripts/llama-bundle-manifest.json names one exact upstream release asset per platform and a hand-computed sha256; fetch_llama_bundle.py re-verifies that hash on every download — cached copies included — and fails the build on any mismatch.
The bundled binary beside relay deliberately wins over a llama-server already on an operator's PATH — that's what makes relay serve/relay up work with zero configuration on a fresh machine, and it's never silent (an slog.Info at each resolver call site, and relay doctor's llama-shadow check, name it on demand). Set RELAY_LLAMA_BIN/RELAY_RPC_SERVER_BIN to make an operator's own build win instead — that always takes precedence.