Release Python SDK #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Python SDK | |
| on: | |
| # Triggered by agent-assembly's `notify-downstream` job after the upstream | |
| # GitHub Release is created and aasm-* binaries are uploaded. Payload: | |
| # { "release_tag": "v0.0.1-alpha.4" } | |
| # See ai-agent-assembly/agent-assembly PR #842 for the dispatcher side. | |
| repository_dispatch: | |
| types: [agent-assembly-release-published] | |
| workflow_dispatch: | |
| inputs: | |
| pypi_version: | |
| description: "Version to publish to PyPI (e.g. 0.0.1a8.post1, 0.0.1a9). Required when dry-run is false." | |
| required: false | |
| type: string | |
| binary_source_tag: | |
| description: "agent-assembly tag whose aasm-* binaries to bundle into the wheel (e.g. v0.0.1-alpha.8). Defaults to latest agent-assembly Release." | |
| required: false | |
| type: string | |
| dry-run: | |
| description: "Skip PyPI upload, build wheels only (overrides pypi_version)" | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| id-token: write # PyPI Trusted Publisher OIDC | |
| concurrency: | |
| group: release-python-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| # Source of the prebuilt aasm sidecar binary, fetched per platform | |
| # before each maturin build so it lands at agent_assembly/bin/aasm | |
| # inside the wheel (matches runtime.py's WHEEL_BUNDLED_BIN search path). | |
| AASM_BINARY_RELEASE_REPO: ai-agent-assembly/agent-assembly | |
| PYTHON_VERSION: '3.12' | |
| # protoc binary version + per-arch SHA256 sums (cross-verified against | |
| # the GitHub release API's `digest:` field on the v32.1 release assets). | |
| # Bump in one place when upgrading protoc. | |
| PROTOC_VERSION: '32.1' | |
| PROTOC_SHA256_X86_64: 'e9c129c176bb7df02546c4cd6185126ca53c89e7d2f09511e209319704b5dd7e' | |
| PROTOC_SHA256_AARCH_64: '4a802ed23d70f7bad7eb19e5a3e724b3aa967250d572cadfd537c1ba939aee6a' | |
| jobs: | |
| resolve: | |
| name: Resolve release tag and PyPI version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| binary_source_tag: ${{ steps.r.outputs.binary_source_tag }} | |
| pypi_version: ${{ steps.r.outputs.pypi_version }} | |
| dry_run: ${{ steps.r.outputs.dry_run }} | |
| release_tag: ${{ steps.r.outputs.release_tag }} | |
| steps: | |
| # Need the working tree on disk so the resolve step can source the | |
| # tag → PEP 440 conversion script (.github/scripts/tag-to-pep440.sh) | |
| # and its inverse (.github/scripts/pep440-to-tag.sh). Those scripts | |
| # are the single source of truth for the conversion, shared with the | |
| # AAASM-2863 / AAASM-2956 unit tests. | |
| - uses: actions/checkout@v6 | |
| - id: r | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| DISPATCH_BINARY_TAG: ${{ inputs.binary_source_tag }} | |
| DISPATCH_PYPI_VERSION: ${{ inputs.pypi_version }} | |
| DISPATCH_DRY_RUN: ${{ inputs.dry-run }} | |
| DISPATCH_PAYLOAD_TAG: ${{ github.event.client_payload.release_tag }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| # binary_source_tag is optional: when omitted, default to the | |
| # latest agent-assembly Release. Mirrors today's default-latest | |
| # behaviour of `gh release download` with no tag. | |
| if [[ -n "${DISPATCH_BINARY_TAG:-}" ]]; then | |
| binary_source_tag="$DISPATCH_BINARY_TAG" | |
| else | |
| binary_source_tag=$(gh release view --repo ai-agent-assembly/agent-assembly --json tagName --jq .tagName) | |
| echo "::notice::binary_source_tag omitted; defaulting to latest agent-assembly release: $binary_source_tag" | |
| fi | |
| pypi_version="$DISPATCH_PYPI_VERSION" | |
| dry_run="$DISPATCH_DRY_RUN" | |
| elif [[ "$EVENT_NAME" == "repository_dispatch" ]]; then | |
| binary_source_tag="$DISPATCH_PAYLOAD_TAG" | |
| # Convert agent-assembly tag (e.g. v0.0.1-alpha.8) to PEP 440 form | |
| # (0.0.1a8). Mirrors the sync-version-from-dispatch composite action | |
| # so the repository_dispatch path produces identical wheel + version | |
| # state to today. The conversion lives in a sourceable script so | |
| # the AAASM-2863 unit tests exercise the same code path this | |
| # workflow runs (instead of a copy of the regex). | |
| # shellcheck source=.github/scripts/tag-to-pep440.sh | |
| source "${GITHUB_WORKSPACE}/.github/scripts/tag-to-pep440.sh" | |
| pypi_version="$(tag_to_pep440 "$DISPATCH_PAYLOAD_TAG")" | |
| # repository_dispatch is always a real publish — it is fired by | |
| # agent-assembly after a real upstream release. | |
| dry_run="false" | |
| else | |
| echo "::error::unexpected event_name '$EVENT_NAME'" | |
| exit 1 | |
| fi | |
| # Fail fast: a real publish (dry_run != true) MUST have a non-empty | |
| # pypi_version. Otherwise the wheel would be stamped with whatever | |
| # version is checked in to master (lagging the bumper) and PyPI | |
| # would reject the upload as a duplicate of the master version. | |
| # See AAASM-2459 for the alpha-4 incident this guards against. | |
| if [[ "$dry_run" != "true" && -z "${pypi_version:-}" ]]; then | |
| echo "::error::dry-run is false but pypi_version is empty — supply pypi_version when dispatching for a real publish" | |
| exit 1 | |
| fi | |
| # Validate binary_source_tag against v*.*.* semver (allows -alpha.N, | |
| # -beta.N, -rc.N suffixes). Catches typos like "v0.0.1.alpha.8" | |
| # before we waste a 20-minute wheel-build run on a tag that | |
| # gh release download will reject. | |
| if [[ ! "$binary_source_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then | |
| echo "::error::binary_source_tag '$binary_source_tag' does not match v*.*.* (semver) pattern" | |
| exit 1 | |
| fi | |
| # Validate pypi_version against PEP 440 (basic but tight). Rejects | |
| # hyphenated forms like "0.0.1-alpha.8.1" — PyPI accepts only | |
| # "0.0.1a8.post1" style. Deliberately tight to fail fast on | |
| # operator typos that would otherwise produce a wheel filename PyPI | |
| # would reject at upload time (after the full build pipeline ran). | |
| # https://peps.python.org/pep-0440/ | |
| if [[ -n "${pypi_version:-}" ]] && [[ ! "$pypi_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(a|b|rc)?[0-9]*(\.post[0-9]+)?(\.dev[0-9]+)?$ ]]; then | |
| echo "::error::pypi_version '$pypi_version' is not valid PEP 440 (use 0.0.1a8.post1 not 0.0.1-alpha.8.1)" | |
| exit 1 | |
| fi | |
| # Resolve the canonical SemVer release tag the create-github-release | |
| # job will cut at the published version (AAASM-2956). The | |
| # repository_dispatch event already carries the canonical tag, so use | |
| # it verbatim. The workflow_dispatch path only has the PEP 440 | |
| # pypi_version, so derive the tag from it via the single-source-of- | |
| # truth inverse converter. .post/.dev republish forms have no own | |
| # GitHub Release tag, so leave release_tag empty for those. | |
| release_tag="" | |
| if [[ "$dry_run" != "true" ]]; then | |
| if [[ "$EVENT_NAME" == "repository_dispatch" ]]; then | |
| release_tag="$DISPATCH_PAYLOAD_TAG" | |
| elif [[ "$pypi_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(a|b|rc)?[0-9]*$ ]]; then | |
| # shellcheck source=.github/scripts/pep440-to-tag.sh | |
| source "${GITHUB_WORKSPACE}/.github/scripts/pep440-to-tag.sh" | |
| release_tag="$(pep440_to_tag "$pypi_version")" | |
| else | |
| echo "::notice::pypi_version '$pypi_version' is a post/dev republish — no GitHub Release tag will be cut" | |
| fi | |
| fi | |
| { | |
| echo "binary_source_tag=${binary_source_tag}" | |
| echo "pypi_version=${pypi_version}" | |
| echo "dry_run=${dry_run}" | |
| echo "release_tag=${release_tag}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Resolved binary_source_tag=${binary_source_tag} pypi_version=${pypi_version} dry_run=${dry_run} release_tag=${release_tag}" | |
| build-sdist: | |
| name: Build sdist | |
| needs: resolve | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Pin aa-ffi git deps to released core (binary_source_tag) | |
| # Rewrite native/aa-ffi-python/Cargo.toml so the wheel compiles against | |
| # the SAME core release whose aasm-* binaries it bundles. master's pins | |
| # lag by one cycle (bumped only by the post-publish update PR), so | |
| # without this the wheel would pin the PREVIOUS core. Ephemeral CI edit | |
| # — not committed back. See AAASM-2959. | |
| env: | |
| BINARY_SOURCE_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: .github/scripts/pin-ffi-to-tag.sh "$BINARY_SOURCE_TAG" | |
| - name: Sync version | |
| uses: ./.github/actions/sync-version | |
| with: | |
| pypi_version: ${{ needs.resolve.outputs.pypi_version }} | |
| - name: Build source distribution | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| command: sdist | |
| args: --out dist | |
| - name: Upload sdist artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-sdist | |
| path: dist/*.tar.gz | |
| build-linux-x86_64: | |
| name: Build manylinux_x86_64 wheel | |
| needs: resolve | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Stage aasm sidecar binary | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| AASM_REPO: ${{ env.AASM_BINARY_RELEASE_REPO }} | |
| AASM_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p agent_assembly/bin | |
| # binary_source_tag is now guaranteed non-empty by the resolve job | |
| # (either set explicitly via workflow_dispatch input, defaulted to | |
| # the latest agent-assembly release, or derived from | |
| # client_payload.release_tag for repository_dispatch). | |
| # Hard error on missing binary: the repository_dispatch event | |
| # guarantees the aasm-* assets exist on the upstream release | |
| # at this point (see ai-agent-assembly/agent-assembly#842). | |
| gh release download "$AASM_TAG" --repo "$AASM_REPO" --pattern 'aasm-x86_64-unknown-linux-gnu.tar.gz' --dir agent_assembly/bin/ | |
| # Tarball contains a single `aasm` binary at the root; | |
| # extract in place, then drop the archive. | |
| tar -xzf agent_assembly/bin/aasm-x86_64-unknown-linux-gnu.tar.gz -C agent_assembly/bin/ | |
| rm -f agent_assembly/bin/aasm-x86_64-unknown-linux-gnu.tar.gz | |
| chmod +x agent_assembly/bin/aasm | |
| echo "Bundled aasm binary into wheel" | |
| - name: Pin aa-ffi git deps to released core (binary_source_tag) | |
| # Rewrite native/aa-ffi-python/Cargo.toml so the wheel compiles against | |
| # the SAME core release whose aasm-* binaries it bundles. master's pins | |
| # lag by one cycle (bumped only by the post-publish update PR), so | |
| # without this the wheel would pin the PREVIOUS core. Ephemeral CI edit | |
| # — not committed back. See AAASM-2959. | |
| env: | |
| BINARY_SOURCE_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: .github/scripts/pin-ffi-to-tag.sh "$BINARY_SOURCE_TAG" | |
| - name: Sync version | |
| uses: ./.github/actions/sync-version | |
| with: | |
| pypi_version: ${{ needs.resolve.outputs.pypi_version }} | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: x86_64-unknown-linux-gnu | |
| command: build | |
| args: --release --out dist --interpreter ${{ env.PYTHON_VERSION }} | |
| manylinux: auto | |
| # The manylinux2014 image (CentOS 7-based) lacks protoc; aa-proto's | |
| # build.rs needs it via prost-build for proto3 syntax. The yum/dnf | |
| # `protobuf-compiler` package on CentOS 7 ships protoc 2.5.0 which | |
| # ONLY understands proto2 ("Unrecognized syntax identifier 'proto3'"), | |
| # so we download the official protoc binary release instead. | |
| # | |
| # SECURITY: the zip is downloaded over HTTPS from GitHub's release | |
| # CDN AND verified against a hardcoded SHA256 cross-checked against | |
| # the GitHub release API's `digest` field. Without the SHA check we'd | |
| # be installing an arbitrary binary as root with no integrity gate. | |
| before-script-linux: | | |
| set -euo pipefail | |
| (command -v unzip >/dev/null) || (yum install -y unzip 2>/dev/null || dnf install -y unzip 2>/dev/null || (apt-get update && apt-get install -y unzip)) | |
| ARCH=$(uname -m) | |
| case "$ARCH" in | |
| x86_64) PROTOC_ARCH="x86_64"; EXPECTED_SHA="${{ env.PROTOC_SHA256_X86_64 }}" ;; | |
| aarch64) PROTOC_ARCH="aarch_64"; EXPECTED_SHA="${{ env.PROTOC_SHA256_AARCH_64 }}" ;; | |
| *) echo "::error::unsupported manylinux arch: $ARCH"; exit 1 ;; | |
| esac | |
| curl -sSLf --retry 3 --retry-delay 5 \ | |
| "https://github.com/protocolbuffers/protobuf/releases/download/v${{ env.PROTOC_VERSION }}/protoc-${{ env.PROTOC_VERSION }}-linux-${PROTOC_ARCH}.zip" \ | |
| -o /tmp/protoc.zip | |
| echo "${EXPECTED_SHA} /tmp/protoc.zip" | sha256sum --check --status \ | |
| || { echo "::error::protoc-${{ env.PROTOC_VERSION }}-linux-${PROTOC_ARCH}.zip SHA256 mismatch — refusing to install"; sha256sum /tmp/protoc.zip; exit 1; } | |
| unzip -o /tmp/protoc.zip -d /usr/local >/dev/null | |
| protoc --version | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-linux-x86_64 | |
| path: dist/*.whl | |
| build-linux-aarch64: | |
| name: Build manylinux_aarch64 wheel | |
| needs: resolve | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Stage aasm sidecar binary | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| AASM_REPO: ${{ env.AASM_BINARY_RELEASE_REPO }} | |
| AASM_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p agent_assembly/bin | |
| # See linux-x86_64 above for binary_source_tag resolution rationale. | |
| # Hard error on missing binary: the repository_dispatch event | |
| # guarantees the aasm-* assets exist on the upstream release | |
| # at this point (see ai-agent-assembly/agent-assembly#842). | |
| gh release download "$AASM_TAG" --repo "$AASM_REPO" --pattern 'aasm-aarch64-unknown-linux-gnu.tar.gz' --dir agent_assembly/bin/ | |
| # Tarball contains a single `aasm` binary at the root; | |
| # extract in place, then drop the archive. | |
| tar -xzf agent_assembly/bin/aasm-aarch64-unknown-linux-gnu.tar.gz -C agent_assembly/bin/ | |
| rm -f agent_assembly/bin/aasm-aarch64-unknown-linux-gnu.tar.gz | |
| chmod +x agent_assembly/bin/aasm | |
| echo "Bundled aasm binary into wheel" | |
| - name: Pin aa-ffi git deps to released core (binary_source_tag) | |
| # Rewrite native/aa-ffi-python/Cargo.toml so the wheel compiles against | |
| # the SAME core release whose aasm-* binaries it bundles. master's pins | |
| # lag by one cycle (bumped only by the post-publish update PR), so | |
| # without this the wheel would pin the PREVIOUS core. Ephemeral CI edit | |
| # — not committed back. See AAASM-2959. | |
| env: | |
| BINARY_SOURCE_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: .github/scripts/pin-ffi-to-tag.sh "$BINARY_SOURCE_TAG" | |
| - name: Sync version | |
| uses: ./.github/actions/sync-version | |
| with: | |
| pypi_version: ${{ needs.resolve.outputs.pypi_version }} | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: aarch64-unknown-linux-gnu | |
| command: build | |
| args: --release --out dist --interpreter ${{ env.PYTHON_VERSION }} | |
| manylinux: auto | |
| # See linux-x86_64 above for rationale + security model. Same | |
| # SHA-verified protoc binary download. | |
| before-script-linux: | | |
| set -euo pipefail | |
| (command -v unzip >/dev/null) || (yum install -y unzip 2>/dev/null || dnf install -y unzip 2>/dev/null || (apt-get update && apt-get install -y unzip)) | |
| ARCH=$(uname -m) | |
| case "$ARCH" in | |
| x86_64) PROTOC_ARCH="x86_64"; EXPECTED_SHA="${{ env.PROTOC_SHA256_X86_64 }}" ;; | |
| aarch64) PROTOC_ARCH="aarch_64"; EXPECTED_SHA="${{ env.PROTOC_SHA256_AARCH_64 }}" ;; | |
| *) echo "::error::unsupported manylinux arch: $ARCH"; exit 1 ;; | |
| esac | |
| curl -sSLf --retry 3 --retry-delay 5 \ | |
| "https://github.com/protocolbuffers/protobuf/releases/download/v${{ env.PROTOC_VERSION }}/protoc-${{ env.PROTOC_VERSION }}-linux-${PROTOC_ARCH}.zip" \ | |
| -o /tmp/protoc.zip | |
| echo "${EXPECTED_SHA} /tmp/protoc.zip" | sha256sum --check --status \ | |
| || { echo "::error::protoc-${{ env.PROTOC_VERSION }}-linux-${PROTOC_ARCH}.zip SHA256 mismatch — refusing to install"; sha256sum /tmp/protoc.zip; exit 1; } | |
| unzip -o /tmp/protoc.zip -d /usr/local >/dev/null | |
| protoc --version | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-linux-aarch64 | |
| path: dist/*.whl | |
| build-macos-arm64: | |
| name: Build macosx_arm64 wheel | |
| needs: resolve | |
| runs-on: macos-14 # Apple silicon runner | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Stage aasm sidecar binary | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| AASM_REPO: ${{ env.AASM_BINARY_RELEASE_REPO }} | |
| AASM_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p agent_assembly/bin | |
| # See linux-x86_64 above for binary_source_tag resolution rationale. | |
| # Hard error on missing binary: the repository_dispatch event | |
| # guarantees the aasm-* assets exist on the upstream release | |
| # at this point (see ai-agent-assembly/agent-assembly#842). | |
| gh release download "$AASM_TAG" --repo "$AASM_REPO" --pattern 'aasm-aarch64-apple-darwin.tar.gz' --dir agent_assembly/bin/ | |
| # Tarball contains a single `aasm` binary at the root; | |
| # extract in place, then drop the archive. | |
| tar -xzf agent_assembly/bin/aasm-aarch64-apple-darwin.tar.gz -C agent_assembly/bin/ | |
| rm -f agent_assembly/bin/aasm-aarch64-apple-darwin.tar.gz | |
| chmod +x agent_assembly/bin/aasm | |
| echo "Bundled aasm binary into wheel" | |
| - name: Install protoc (macOS) | |
| run: brew install protobuf | |
| - name: Pin aa-ffi git deps to released core (binary_source_tag) | |
| # Rewrite native/aa-ffi-python/Cargo.toml so the wheel compiles against | |
| # the SAME core release whose aasm-* binaries it bundles. master's pins | |
| # lag by one cycle (bumped only by the post-publish update PR), so | |
| # without this the wheel would pin the PREVIOUS core. Ephemeral CI edit | |
| # — not committed back. See AAASM-2959. | |
| env: | |
| BINARY_SOURCE_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: .github/scripts/pin-ffi-to-tag.sh "$BINARY_SOURCE_TAG" | |
| - name: Sync version | |
| uses: ./.github/actions/sync-version | |
| with: | |
| pypi_version: ${{ needs.resolve.outputs.pypi_version }} | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: aarch64-apple-darwin | |
| command: build | |
| args: --release --out dist --interpreter ${{ env.PYTHON_VERSION }} | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-macos-arm64 | |
| path: dist/*.whl | |
| build-macos-x86_64: | |
| name: Build macosx_x86_64 wheel | |
| needs: resolve | |
| runs-on: macos-15-intel # Intel runner (macos-13 sunset 2025-09-19) | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Stage aasm sidecar binary | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| AASM_REPO: ${{ env.AASM_BINARY_RELEASE_REPO }} | |
| AASM_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p agent_assembly/bin | |
| # See linux-x86_64 above for binary_source_tag resolution rationale. | |
| # Hard error on missing binary: the repository_dispatch event | |
| # guarantees the aasm-* assets exist on the upstream release | |
| # at this point (see ai-agent-assembly/agent-assembly#842). | |
| gh release download "$AASM_TAG" --repo "$AASM_REPO" --pattern 'aasm-x86_64-apple-darwin.tar.gz' --dir agent_assembly/bin/ | |
| # Tarball contains a single `aasm` binary at the root; | |
| # extract in place, then drop the archive. | |
| tar -xzf agent_assembly/bin/aasm-x86_64-apple-darwin.tar.gz -C agent_assembly/bin/ | |
| rm -f agent_assembly/bin/aasm-x86_64-apple-darwin.tar.gz | |
| chmod +x agent_assembly/bin/aasm | |
| echo "Bundled aasm binary into wheel" | |
| - name: Install protoc (macOS) | |
| run: brew install protobuf | |
| - name: Pin aa-ffi git deps to released core (binary_source_tag) | |
| # Rewrite native/aa-ffi-python/Cargo.toml so the wheel compiles against | |
| # the SAME core release whose aasm-* binaries it bundles. master's pins | |
| # lag by one cycle (bumped only by the post-publish update PR), so | |
| # without this the wheel would pin the PREVIOUS core. Ephemeral CI edit | |
| # — not committed back. See AAASM-2959. | |
| env: | |
| BINARY_SOURCE_TAG: ${{ needs.resolve.outputs.binary_source_tag }} | |
| run: .github/scripts/pin-ffi-to-tag.sh "$BINARY_SOURCE_TAG" | |
| - name: Sync version | |
| uses: ./.github/actions/sync-version | |
| with: | |
| pypi_version: ${{ needs.resolve.outputs.pypi_version }} | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: x86_64-apple-darwin | |
| command: build | |
| args: --release --out dist --interpreter ${{ env.PYTHON_VERSION }} | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-macos-x86_64 | |
| path: dist/*.whl | |
| publish: | |
| name: Publish to PyPI (Trusted Publisher) | |
| needs: | |
| - resolve | |
| - build-sdist | |
| - build-linux-x86_64 | |
| - build-linux-aarch64 | |
| - build-macos-arm64 | |
| - build-macos-x86_64 | |
| runs-on: ubuntu-latest | |
| # Publish whenever the resolve job decided this is a real publish (not a | |
| # dry-run). repository_dispatch is hardcoded to dry_run='false' in the | |
| # resolve step; workflow_dispatch passes through its dry-run input (which | |
| # defaults to false after AAASM-2856). | |
| if: needs.resolve.outputs.dry_run != 'true' | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/agent-assembly | |
| permissions: | |
| id-token: write # OIDC token for Trusted Publisher | |
| steps: | |
| - name: Download all build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - name: Publish via PyPI Trusted Publisher | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| # No `with: password:` — Trusted Publisher uses OIDC, no token stored. | |
| # Cut python-sdk's own GitHub Release at the just-published version so the | |
| # repo's release line tracks PyPI (and the README `github/v/release` badge | |
| # resolves) instead of drifting onto a separate source-of-truth tag like | |
| # v0.0.2a1. Runs only on the real-publish path, gated on `publish` so the | |
| # tag is never created for a wheel that failed to upload. See AAASM-2956. | |
| create-github-release: | |
| name: Cut GitHub Release at published version | |
| needs: | |
| - resolve | |
| - publish | |
| runs-on: ubuntu-latest | |
| # Real publish only, and only when resolve produced a SemVer tag (empty | |
| # for .post/.dev republishes, which do not get their own Release). | |
| if: needs.resolve.outputs.dry_run != 'true' && needs.resolve.outputs.release_tag != '' | |
| permissions: | |
| contents: write # create the git tag + GitHub Release | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Create tag and GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # client_payload is attacker-controllable on repository_dispatch; | |
| # RELEASE_TAG was validated as a SemVer tag by the resolve job and | |
| # is only ever passed through env (never inlined into a run: body). | |
| RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }} | |
| PYPI_VERSION: ${{ needs.resolve.outputs.pypi_version }} | |
| run: | | |
| set -euo pipefail | |
| # Defence in depth: re-validate the tag shape here so a future | |
| # change to resolve cannot smuggle an arbitrary string into the | |
| # tag/gh commands below. | |
| if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$ ]]; then | |
| echo "::error::release_tag '$RELEASE_TAG' is not a valid SemVer release tag" | |
| exit 1 | |
| fi | |
| # Idempotent: skip if a Release already exists for this tag (e.g. a | |
| # re-run after a partial failure), so the job never errors on retry. | |
| if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then | |
| echo "::notice::GitHub Release $RELEASE_TAG already exists — nothing to do" | |
| exit 0 | |
| fi | |
| prerelease_flag="" | |
| if [[ "$RELEASE_TAG" =~ -(alpha|beta|rc)\. ]]; then | |
| prerelease_flag="--prerelease" | |
| fi | |
| # `gh release create` creates the annotated tag at the current | |
| # commit (the workflow ref) when the tag does not yet exist. | |
| gh release create "$RELEASE_TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| $prerelease_flag \ | |
| --title "$RELEASE_TAG" \ | |
| --notes "python-sdk agent-assembly==${PYPI_VERSION} — coordinated with agent-assembly ${RELEASE_TAG}." | |
| echo "Created GitHub Release $RELEASE_TAG (agent-assembly==${PYPI_VERSION})" | |
| # Publish the *real* release tag as a tiny artifact so the documentation | |
| # workflow (which is triggered by `workflow_run` after this workflow | |
| # completes) can label the frozen docs snapshot with the human-facing tag | |
| # and pick the right channel. The `workflow_run` event itself only exposes | |
| # the PEP-440 pyproject version, which loses the canonical tag form — so we | |
| # hand the tag across explicitly. See AAASM-2750. | |
| publish-release-tag: | |
| name: Publish release tag for docs | |
| needs: | |
| - publish | |
| runs-on: ubuntu-latest | |
| # Only the real release path (repository_dispatch) carries a tag; the | |
| # workflow_dispatch dry-run has no tag and no docs snapshot to cut. | |
| if: github.event_name == 'repository_dispatch' | |
| steps: | |
| - name: Write release tag to file | |
| env: | |
| RELEASE_TAG: ${{ github.event.client_payload.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${RELEASE_TAG}" ]; then | |
| echo "::error::client_payload.release_tag is empty on a release dispatch" | |
| exit 1 | |
| fi | |
| printf '%s\n' "${RELEASE_TAG}" > release-tag.txt | |
| echo "Recorded release tag: ${RELEASE_TAG}" | |
| - name: Upload release-tag artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-tag | |
| path: release-tag.txt | |
| if-no-files-found: error |