Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 221 additions & 0 deletions .github/workflows/sprout-agent-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
name: Sprout Agent Bundle

# Builds and publishes the Sprout Agent Bundle — a Linux tarball containing
# the three binaries an external service (e.g. sprout-backend-blox) needs to
# run a Sprout agent end-to-end:
#
# sprout-acp ACP harness that bridges Sprout events to the LLM agent
# sprout-agent ACP-compliant agent (spawns MCP, calls LLMs)
# sprout-dev-mcp Developer MCP server (multicall: rg, tree, sprout,
# git-credential-nostr, git-sign-nostr)
#
# Targets: x86_64-unknown-linux-musl and aarch64-unknown-linux-musl (static
# musl so the tarball runs on any modern Linux without libc surprises).
#
# Triggers:
# - push to main → updates rolling `sprout-agent-bundle-latest` release
# - tag `sprout-agent-bundle-v*` → versioned release
# - workflow_dispatch → manual canary build (no release publish)

on:
push:
branches: [main]
tags: ["sprout-agent-bundle-v*"]
workflow_dispatch:
inputs:
publish:
description: "Publish to the rolling release (otherwise artifacts only)"
type: boolean
default: false

permissions:
contents: read

jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
strategy:
fail-fast: false
matrix:
target:
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-musl
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1

- name: Install cross
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18
with:
tool: cross@0.2.5

- name: Resolve version
id: ver
run: |
set -euo pipefail
WORKSPACE_VERSION=$(cargo metadata --no-deps --format-version=1 \
| jq -r '.workspace_default_members[0] as $m | .packages[] | select(.id==$m) | .version')
if [[ -z "$WORKSPACE_VERSION" || "$WORKSPACE_VERSION" == "null" ]]; then
# Fallback: read sprout-acp's version directly.
WORKSPACE_VERSION=$(cargo metadata --no-deps --format-version=1 \
| jq -r '.packages[] | select(.name=="sprout-acp") | .version')
fi

REF="${GITHUB_REF#refs/tags/}"
if [[ "$GITHUB_REF" == refs/tags/sprout-agent-bundle-v* ]]; then
VERSION="${REF#sprout-agent-bundle-v}"
CHANNEL="tag"
else
SHORT_SHA="${GITHUB_SHA::7}"
VERSION="${WORKSPACE_VERSION}+git.${SHORT_SHA}"
CHANNEL="rolling"
fi
{
echo "workspace_version=$WORKSPACE_VERSION"
echo "version=$VERSION"
echo "channel=$CHANNEL"
} >> "$GITHUB_OUTPUT"
echo "Resolved version=$VERSION channel=$CHANNEL"

- name: Build & package bundle
id: pkg
env:
TARGET: ${{ matrix.target }}
VERSION: ${{ steps.ver.outputs.version }}
CHANNEL: ${{ steps.ver.outputs.channel }}
GIT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
# Rolling releases use stable, version-less filenames so the
# asset overwrites cleanly on every push to main. Tagged
# releases keep the version in the filename for traceability.
# The git SHA + version always live inside bundle.json.
if [[ "$CHANNEL" == "tag" ]]; then
ARCHIVE_BASENAME="sprout-agent-bundle-${VERSION}-${TARGET}"
else
ARCHIVE_BASENAME="sprout-agent-bundle-${TARGET}"
fi
ARCHIVE_BASENAME="$ARCHIVE_BASENAME" \
./scripts/build-agent-bundle.sh "$VERSION" "$TARGET"
ARCHIVE="dist/${ARCHIVE_BASENAME}.tar.gz"
test -f "$ARCHIVE"
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
echo "archive_name=${ARCHIVE_BASENAME}.tar.gz" >> "$GITHUB_OUTPUT"

- name: Upload workflow artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sprout-agent-bundle-${{ matrix.target }}
path: |
${{ steps.pkg.outputs.archive }}
${{ steps.pkg.outputs.archive }}.sha256
if-no-files-found: error
retention-days: 30

publish:
name: Publish rolling release
needs: build
if: |
github.event_name == 'push' && github.ref == 'refs/heads/main'
|| (github.event_name == 'workflow_dispatch' && inputs.publish)
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Download all bundle artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist
pattern: sprout-agent-bundle-*
merge-multiple: true

- name: List assets
run: ls -lh dist/

- name: Update rolling release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
TAG="sprout-agent-bundle-latest"
TITLE="Sprout Agent Bundle (rolling)"
NOTES="Rolling Linux build of the Sprout Agent Bundle (sprout-acp + sprout-agent + sprout-dev-mcp), tracking \`main\` (\`${SHA}\`)."

# Force-move the underlying git tag to the current SHA before
# we touch the release. `gh release edit` updates release
# metadata but does *not* move the tag, so without this the
# tag would stick to whatever SHA first created the release.
if gh api "repos/${REPO}/git/refs/tags/${TAG}" >/dev/null 2>&1; then
gh api -X PATCH "repos/${REPO}/git/refs/tags/${TAG}" \
-f sha="${SHA}" -F force=true >/dev/null
else
gh api -X POST "repos/${REPO}/git/refs" \
-f ref="refs/tags/${TAG}" -f sha="${SHA}" >/dev/null
fi

# Create the release if it doesn't exist; otherwise reuse it.
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--prerelease \
--target "${SHA}" \
--title "$TITLE" \
--notes "$NOTES"
else
gh release edit "$TAG" \
--prerelease \
--target "${SHA}" \
--title "$TITLE" \
--notes "$NOTES"
fi
# Asset filenames are stable for rolling builds (no version
# in filename — see the package step), so --clobber overwrites
# them in place. No stale-asset accumulation.
gh release upload "$TAG" dist/* --clobber

publish-tag:
name: Publish tagged release
needs: build
if: startsWith(github.ref, 'refs/tags/sprout-agent-bundle-v')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Download all bundle artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist
pattern: sprout-agent-bundle-*
merge-multiple: true

- name: Resolve tag version
id: ver
run: |
REF="${GITHUB_REF#refs/tags/}"
VERSION="${REF#sprout-agent-bundle-v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$REF" >> "$GITHUB_OUTPUT"

- name: Create tagged release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.ver.outputs.tag }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
set -euo pipefail
gh release create "$TAG" \
--title "Sprout Agent Bundle v${VERSION}" \
--notes "Sprout Agent Bundle v${VERSION} — Linux builds of sprout-acp + sprout-agent + sprout-dev-mcp." \
dist/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Build artifacts
/target/
/dist/

# Environment files (may contain secrets)
.env
Expand Down
Loading
Loading