From 2bea56f3bd85c730df1f02a6593e8666a0b1505a Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 15:51:41 -0700 Subject: [PATCH 1/6] ci: add tag-triggered container releases Signed-off-by: Sertac Ozercan --- .github/workflows/release.yml | 96 +++++++++++++++++++++++++++++++++++ README.md | 25 +++++++++ 2 files changed, 121 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9a1ff1f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,96 @@ +name: Release + +on: + push: + tags: + - "v[0-9]*" + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + images: + name: Publish ${{ matrix.image }} + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - image: agentkit + dockerfile: Dockerfile + - image: serve-pydantic-ai + dockerfile: runtimes/pydantic-ai/Dockerfile + - image: serve-maf + dockerfile: runtimes/microsoft-agent-framework/Dockerfile + - image: serve-langgraph + dockerfile: runtimes/langgraph/Dockerfile + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + with: + platforms: arm64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Login to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }}/${{ matrix.image }} + # Semver metadata adds latest for stable versions, never prereleases. + tags: type=semver,pattern={{raw}} + + - name: Build and push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + file: ${{ matrix.dockerfile }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: LDFLAGS=-X github.com/sozercan/agentkit/pkg/version.Version=${{ github.ref_name }} + cache-from: type=gha,scope=release-${{ matrix.image }} + cache-to: type=gha,scope=release-${{ matrix.image }},mode=max + sbom: true + provenance: true + + release: + name: Create GitHub Release + needs: images + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - name: Create release with generated notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + args=(--latest) + if [[ "$TAG" == *-* ]]; then + args=(--prerelease --latest=false) + fi + gh release create "$TAG" --repo "$GH_REPO" --verify-tag \ + --title "$TAG" --generate-notes "${args[@]}" diff --git a/README.md b/README.md index fa73b7b..7bae75c 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,31 @@ make build-test-agent RUNTIME=langgraph See [`docs/development.md`](docs/development.md) for the full local test and CI workflow. +## Release AgentKit + +After CI passes for the commit you want to release, push a version tag: + +```sh +git tag v0.1.0 +git push origin v0.1.0 +``` + +The release workflow builds and publishes these images for `linux/amd64` and +`linux/arm64` under `ghcr.io/sozercan/agentkit`: + +- `agentkit`, the BuildKit frontend. +- `serve-pydantic-ai`, the Pydantic AI runtime. +- `serve-maf`, the Microsoft Agent Framework runtime. +- `serve-langgraph`, the LangGraph runtime. + +Each image gets the version tag, such as `v0.1.0`. Stable releases also update +`latest`. Prerelease tags such as `v0.2.0-rc.1` publish versioned images without +changing `latest` and create a GitHub prerelease. + +A GitHub Release with generated notes is created only after all four images +publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry +secret is needed. Builds include SBOM and provenance attestations. + ## More docs - [`docs/agentkitfile.md`](docs/agentkitfile.md) — Agentkitfile schema and build From f7a4c869302daed208fe278d7e3070161083d9a5 Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 16:04:05 -0700 Subject: [PATCH 2/6] fix(ci): ignore build metadata in prerelease detection Signed-off-by: Sertac Ozercan --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a1ff1f..3ec40b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,7 +89,8 @@ jobs: TAG: ${{ github.ref_name }} run: | args=(--latest) - if [[ "$TAG" == *-* ]]; then + # A hyphen in build metadata does not make a version a prerelease. + if [[ "${TAG%%+*}" == *-* ]]; then args=(--prerelease --latest=false) fi gh release create "$TAG" --repo "$GH_REPO" --verify-tag \ From 584aba86158032987fe18702a376d9b4e4d22f2d Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 16:53:54 -0700 Subject: [PATCH 3/6] fix(ci): reject colliding release build metadata Signed-off-by: Sertac Ozercan --- .github/workflows/release.yml | 12 ++++++++++-- README.md | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ec40b7..bd52aa3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,15 @@ jobs: - image: serve-langgraph dockerfile: runtimes/langgraph/Dockerfile steps: + - name: Validate release tag + env: + TAG: ${{ github.ref_name }} + run: | + if [[ "$TAG" == *+* ]]; then + echo "::error::Release tags must not contain build metadata (+...), which can collide with Docker tags." + exit 1 + fi + - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -89,8 +98,7 @@ jobs: TAG: ${{ github.ref_name }} run: | args=(--latest) - # A hyphen in build metadata does not make a version a prerelease. - if [[ "${TAG%%+*}" == *-* ]]; then + if [[ "$TAG" == *-* ]]; then args=(--prerelease --latest=false) fi gh release create "$TAG" --repo "$GH_REPO" --verify-tag \ diff --git a/README.md b/README.md index 7bae75c..8c40bc7 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,8 @@ The release workflow builds and publishes these images for `linux/amd64` and Each image gets the version tag, such as `v0.1.0`. Stable releases also update `latest`. Prerelease tags such as `v0.2.0-rc.1` publish versioned images without changing `latest` and create a GitHub prerelease. +Build metadata such as `+build.1` is rejected before publishing to prevent +collisions between versioned Docker tags. A GitHub Release with generated notes is created only after all four images publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry From 9c58519c1b0873c3d206b6ddc98e68efeabf1217 Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 17:12:35 -0700 Subject: [PATCH 4/6] fix(ci): promote latest only after all images publish Signed-off-by: Sertac Ozercan --- .github/workflows/release.yml | 37 +++++++++++++++++++++++++++++------ README.md | 14 +++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd52aa3..15d30b3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: contents: read concurrency: - group: release-${{ github.ref }} + group: release cancel-in-progress: false jobs: @@ -65,8 +65,9 @@ jobs: uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: ghcr.io/${{ github.repository }}/${{ matrix.image }} - # Semver metadata adds latest for stable versions, never prereleases. tags: type=semver,pattern={{raw}} + # Promote latest only after every image has published successfully. + flavor: latest=false - name: Build and push uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 @@ -84,22 +85,46 @@ jobs: provenance: true release: - name: Create GitHub Release + name: Promote images and create GitHub Release needs: images runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: write + packages: write steps: - - name: Create release with generated notes + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Login to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Promote latest and create release with generated notes env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} TAG: ${{ github.ref_name }} run: | - args=(--latest) + args=(--latest=false) if [[ "$TAG" == *-* ]]; then - args=(--prerelease --latest=false) + args+=(--prerelease) + else + # shellcheck disable=SC2016 + latest=$(gh api graphql -F owner="${GH_REPO%/*}" -F name="${GH_REPO#*/}" \ + -f query='query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { latestRelease { tagName } } }' \ + --jq '.data.repository.latestRelease.tagName // ""') + # Older stable releases may publish, but must not roll latest back. + if [[ -z "$latest" || "$(printf '%s\n' "$latest" "$TAG" | sort -V | tail -n 1)" == "$TAG" ]]; then + for image in agentkit serve-pydantic-ai serve-maf serve-langgraph; do + docker buildx imagetools create \ + --tag "ghcr.io/$GH_REPO/$image:latest" "ghcr.io/$GH_REPO/$image:$TAG" + done + args=(--latest) + fi fi gh release create "$TAG" --repo "$GH_REPO" --verify-tag \ --title "$TAG" --generate-notes "${args[@]}" diff --git a/README.md b/README.md index 8c40bc7..85a0594 100644 --- a/README.md +++ b/README.md @@ -378,8 +378,9 @@ The release workflow builds and publishes these images for `linux/amd64` and - `serve-maf`, the Microsoft Agent Framework runtime. - `serve-langgraph`, the LangGraph runtime. -Each image gets the version tag, such as `v0.1.0`. Stable releases also update -`latest`. Prerelease tags such as `v0.2.0-rc.1` publish versioned images without +Each image gets the version tag, such as `v0.1.0`. After all images publish, +a stable release updates `latest` unless a newer stable release already exists. +Prerelease tags such as `v0.2.0-rc.1` publish versioned images without changing `latest` and create a GitHub prerelease. Build metadata such as `+build.1` is rejected before publishing to prevent collisions between versioned Docker tags. @@ -388,6 +389,15 @@ A GitHub Release with generated notes is created only after all four images publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry secret is needed. Builds include SBOM and provenance attestations. +Release runs are serialized. Updates across the four `latest` tags are not +atomic; use version tags when you need a fixed release, and rerun a failed +workflow to finish an interrupted publication. + +On first publication, GHCR packages default to private. After the first release, +open each of the four packages' settings and change its visibility to **Public** +before announcing it. For existing packages published manually, grant this +repository Actions access if needed. The workflow does not change package access. + ## More docs - [`docs/agentkitfile.md`](docs/agentkitfile.md) — Agentkitfile schema and build From 3e1bafa4eb2e80ffc0fbe86d44c946a86deb834a Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 19:38:42 -0700 Subject: [PATCH 5/6] docs: move release instructions into a dedicated guide Signed-off-by: Sertac Ozercan --- README.md | 38 +------------------------------------- docs/release.md | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 docs/release.md diff --git a/README.md b/README.md index 85a0594..8102b33 100644 --- a/README.md +++ b/README.md @@ -361,43 +361,6 @@ make build-test-agent RUNTIME=langgraph See [`docs/development.md`](docs/development.md) for the full local test and CI workflow. -## Release AgentKit - -After CI passes for the commit you want to release, push a version tag: - -```sh -git tag v0.1.0 -git push origin v0.1.0 -``` - -The release workflow builds and publishes these images for `linux/amd64` and -`linux/arm64` under `ghcr.io/sozercan/agentkit`: - -- `agentkit`, the BuildKit frontend. -- `serve-pydantic-ai`, the Pydantic AI runtime. -- `serve-maf`, the Microsoft Agent Framework runtime. -- `serve-langgraph`, the LangGraph runtime. - -Each image gets the version tag, such as `v0.1.0`. After all images publish, -a stable release updates `latest` unless a newer stable release already exists. -Prerelease tags such as `v0.2.0-rc.1` publish versioned images without -changing `latest` and create a GitHub prerelease. -Build metadata such as `+build.1` is rejected before publishing to prevent -collisions between versioned Docker tags. - -A GitHub Release with generated notes is created only after all four images -publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry -secret is needed. Builds include SBOM and provenance attestations. - -Release runs are serialized. Updates across the four `latest` tags are not -atomic; use version tags when you need a fixed release, and rerun a failed -workflow to finish an interrupted publication. - -On first publication, GHCR packages default to private. After the first release, -open each of the four packages' settings and change its visibility to **Public** -before announcing it. For existing packages published manually, grant this -repository Actions access if needed. The workflow does not change package access. - ## More docs - [`docs/agentkitfile.md`](docs/agentkitfile.md) — Agentkitfile schema and build @@ -408,6 +371,7 @@ repository Actions access if needed. The workflow does not change package access adapters, auth, request handling, and tool lifecycle. - [`docs/agent-abi.md`](docs/agent-abi.md) — built `/agent/agent.yaml` contract. - [`docs/development.md`](docs/development.md) — local development and CI. +- [Release guide](docs/release.md), publishing images and package setup. - [`docs/orka.md`](docs/orka.md) — Orka harness mode and AgentRuntime rendering. - [`docs/architecture.md`](docs/architecture.md) — codebase architecture map for contributors. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..78e033d --- /dev/null +++ b/docs/release.md @@ -0,0 +1,36 @@ +# Release AgentKit + +After CI passes for the commit you want to release, push a version tag: + +```sh +git tag v0.1.0 +git push origin v0.1.0 +``` + +The release workflow builds and publishes these images for `linux/amd64` and +`linux/arm64` under `ghcr.io/sozercan/agentkit`: + +- `agentkit`, the BuildKit frontend. +- `serve-pydantic-ai`, the Pydantic AI runtime. +- `serve-maf`, the Microsoft Agent Framework runtime. +- `serve-langgraph`, the LangGraph runtime. + +Each image gets the version tag, such as `v0.1.0`. After all images publish, +a stable release updates `latest` unless a newer stable release already exists. +Prerelease tags such as `v0.2.0-rc.1` publish versioned images without +changing `latest` and create a GitHub prerelease. +Build metadata such as `+build.1` is rejected before publishing to prevent +collisions between versioned Docker tags. + +A GitHub Release with generated notes is created only after all four images +publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry +secret is needed. Builds include SBOM and provenance attestations. + +Release runs are serialized. Updates across the four `latest` tags are not +atomic; use version tags when you need a fixed release, and rerun a failed +workflow to finish an interrupted publication. + +On first publication, GHCR packages default to private. After the first release, +open each of the four packages' settings and change its visibility to **Public** +before announcing it. For existing packages published manually, grant this +repository Actions access if needed. The workflow does not change package access. From 42f05d97f8400d5dbf6528c614d856eb8e7ed50a Mon Sep 17 00:00:00 2001 From: Sertac Ozercan Date: Fri, 25 Sep 2026 19:43:17 -0700 Subject: [PATCH 6/6] docs: align release link with README format Signed-off-by: Sertac Ozercan --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8102b33..c75dcab 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ workflow. adapters, auth, request handling, and tool lifecycle. - [`docs/agent-abi.md`](docs/agent-abi.md) — built `/agent/agent.yaml` contract. - [`docs/development.md`](docs/development.md) — local development and CI. -- [Release guide](docs/release.md), publishing images and package setup. +- [`docs/release.md`](docs/release.md) — publishing images and package setup. - [`docs/orka.md`](docs/orka.md) — Orka harness mode and AgentRuntime rendering. - [`docs/architecture.md`](docs/architecture.md) — codebase architecture map for contributors.