Skip to content

8.2. Releases

What is a release?

A release is a deliberately tagged snapshot plus evidence about its code, course, agent package, images, data, infrastructure, and known limitations. A version identifies those artifacts; it cannot guarantee that an external hosted model behaves identically forever.

How is the project versioned?

Use Semantic Versioning:

  1. MAJOR for incompatible public course/application/deployment contract changes.
  2. MINOR for backward-compatible capabilities or chapters.
  3. PATCH for backward-compatible fixes and clarifications.

The repository is 0.1.0, which signals that contracts may still change before 1.0. Root pyproject.toml, agents/python/pyproject.toml, and CITATION.cff must agree for a release.

mise run check:release-metadata enforces that agreement, requires the newest dated changelog heading and citation release date to match, and can validate an expected v-prefixed tag. The tag workflow runs that check before building or pushing either image, so a mistyped tag cannot publish artifacts with a different package/A2A version.

How is the changelog maintained?

CHANGELOG.md follows Keep a Changelog. Add user-visible work under Unreleased; move it into a dated version only during an intentional release. Do not generate claims from commit prefixes without reviewing the actual diff.

The repository does not currently ship a git-cliff configuration/task. If automation is added later, it must preserve curated entries and pass the same docs checks rather than replacing review.

What must pass before tagging?

mise run format
mise run check
mise run test
mise run scan
./scripts/check-release-metadata.sh "v$(awk -F '\"' '/^version = / { print $2; exit }' pyproject.toml)"
test -z "$(git status --porcelain)"

Also render both Kubernetes overlays, inspect dependency/image locks, run live evaluations on explicitly recorded models when behavior changed, and review release notes for OSS/cloud/cost/security overclaims. A publication release additionally requires the anonymous clone/site/source-link gate from 8.4. Documentation.

How would a maintainer publish a release?

  1. Choose the SemVer change from the public contract diff.
  2. Update all version metadata and locks.
  3. Move reviewed changelog entries out of Unreleased with the release date.
  4. Run the complete release gate and verify the documentation site locally.
  5. Commit the release metadata, create an annotated v-prefixed tag, and push it.
  6. Publish a GitHub release from that tag with the relevant changelog section and artifact digests.

No release is created merely by merging to main; the docs site deployment and a versioned release are separate events.

What does the release workflow publish?

Pushing a v-prefixed tag runs release.yml, which turns the tag into verifiable artifacts:

  1. Builds the agent (agents/python/Dockerfile) and MLflow (infra/mlflow/Dockerfile) images with buildx.
  2. Scans each image with Trivy before any push: vulnerabilities/secrets fail at HIGH/CRITICAL, and licenses fail at UNKNOWN/HIGH/CRITICAL outside the reviewed OSS policy in trivy.yaml.
  3. Pushes to ghcr.io/mlops-courses/agentops-open-course/{agent,mlflow} tagged with the version.
  4. Generates an SPDX SBOM with syft, signs the image keyless with cosign (GitHub OIDC — no stored key, no secret beyond the built-in token), and attaches the SBOM as an attestation.
  5. Verifies the signature and attestation from a separate job against the expected workflow identity, so the run proves its own supply-chain claim.
  6. Uploads SBOMs and image digests to a draft GitHub release for the tag.

The workflow does not replace the manual gate above: a maintainer still curates the changelog notes and publishes the draft deliberately. Consumers verify images as shown in 6.1. Containers.

How does Renovate propose dependency updates?

renovate.yml runs self-hosted Renovate (OSS, via renovatebot/github-action) every Monday and on manual dispatch. renovate.json5 covers GitHub Actions, both Dockerfiles, compose images, every pyproject.toml/uv.lock, mise.toml tool pins, helmfile chart versions, and the pinned Kubernetes image digests.

Routine minor/patch/digest bumps arrive as one grouped weekly PR with chore(deps): commits; majors and the coordinated pins below arrive as single-concern PRs. Nothing auto-merges — the offline gate cannot see model or protocol drift, so every PR goes through the local validation below.

Renovate needs a RENOVATE_TOKEN repository secret: a fine-grained PAT scoped to this repository with read-write Contents, Pull requests, Workflows, and Issues. The built-in GITHUB_TOKEN is not sufficient — pull requests it creates do not trigger CI, and the action rejects it.

How does a maintainer validate a dependency update?

  1. Check out the PR branch and run mise install; a mise.toml tool bump also needs the refreshed mise.lock committed, which Renovate does not maintain.
  2. Run the full offline gate: mise run format, mise run check (includes rendering both Kubernetes overlays), mise run test, and mise run scan.
  3. For behavior-affecting bumps — ADK, model wiring, agentgateway — also run mise run eval and mise run eval:mlflow from agents/python/ against an explicitly recorded model, and compare scores with the previous baseline.
  4. For platform pins (agentgateway, kagent, collector), smoke-test on k3d: mise run cluster:start, mise run platform:install, mise run platform:dev, then exercise the gateway endpoints.
  5. To roll back, revert the merge commit and re-run the gate; published images are immutable per digest, so deployments roll back by pinning the previous verified digest.

Which pinned components must move together?

The coordinated pins from AGENTS.md cannot be bumped file-by-file:

  1. Google ADK: the compatible range in agents/python/pyproject.toml and the exact pin in its uv.lock.
  2. agentgateway: the mise-managed binary in the root mise.toml and the image digest in infra/k8s/base/agentgateway.yaml, across all three data-plane profiles.
  3. kagent: the kagent-crds and kagent charts in infra/helmfile.yaml stay on the same version, and API resources remain v1alpha2.
  4. MLflow: the agents/python dependency and the infra/mlflow lock, plus the local build tag agentops-mlflow:<version> in infra/observability/compose.yaml and the root build:mlflow-image task — the tag is not a registry image, so update it by hand.
  5. OpenTelemetry Collector: the same contrib digest in infra/observability/compose.yaml and infra/k8s/base/otel-collector.yaml.
  6. Python 3.13: requires-python in the pyprojects and both image base pins.

Renovate groups these into dedicated PRs where it can see them; the hand-maintained halves above are the reviewer's checklist. After merging an upgrade that changes an image, cut the next release tag so the published GHCR artifacts, signatures, and SBOMs match the repository again (see the release workflow above).

Why use v-prefixed tags?

v0.1.0 clearly distinguishes a version tag from other Git refs and is widely understood by GitHub and release tooling. Choose one convention and keep package metadata, changelog headings, tag, and release title consistent.

What is the release checkpoint?

Verify that the latest dated entry in CHANGELOG.md matches the newest v-prefixed tag (v0.1.0 for the initial release) and that pyproject.toml, agents/python/pyproject.toml, and CITATION.cff all carry that version. Any new release requires explicit maintainer intent and the full gate above.