Skip to content

Commit e07429c

Browse files
committed
deploy(arch-box): CI-built buzz-acp binary + systemd user unit
Upstream ships desktop installers but no standalone buzz-acp binary, and buzz-acp is the only shipped path to an agent that is not tied to a desktop app staying open (managed agents are child processes of the Tauri app; remote agents are still design-stage, no provider crate exists). Building in CI instead of on the agent host keeps that box free of a Rust toolchain, makes the binary traceable to a commit, and lets anyone on the team cut a new build instead of it depending on one laptop. - workflow: builds x86_64-linux, smoke-checks it, publishes a release asset - install script: pulls newest (or a pinned) build, verifies sha256 - systemd user unit + env template, matching the hermes-gateway-* pattern
1 parent 2476a8a commit e07429c

4 files changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Build the buzz-acp harness for x86_64 Linux and publish it as a release asset.
2+
#
3+
# Why this exists: upstream publishes desktop installers but no standalone
4+
# buzz-acp binary, and buzz-acp is what runs a 24/7 agent that is NOT tied to a
5+
# desktop app being open (see VISION_REMOTE_AGENTS.md — remote agents are still
6+
# design-stage, so the harness is the only shipped path to an always-on agent).
7+
#
8+
# Building here rather than on the target host keeps the agent box free of a
9+
# Rust toolchain and makes the update path reproducible and reviewable by anyone
10+
# on the team, instead of depending on one person's laptop.
11+
#
12+
# Update path: rebase this branch onto a newer upstream ref, run this workflow,
13+
# then re-run deploy/arch-box/install-buzz-acp.sh on the host.
14+
name: buzz-acp (linux x86_64)
15+
16+
on:
17+
workflow_dispatch:
18+
push:
19+
branches: [railway-deploy]
20+
paths:
21+
- "crates/buzz-acp/**"
22+
- "crates/buzz-sdk/**"
23+
- "crates/buzz-ws-client/**"
24+
- "rust-toolchain.toml"
25+
- ".github/workflows/buzz-acp-linux.yml"
26+
27+
permissions:
28+
contents: write
29+
30+
jobs:
31+
build:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
# rust-toolchain.toml pins the version, so `rustup show` installs exactly
37+
# what the workspace expects instead of whatever the runner ships.
38+
- name: Install pinned Rust toolchain
39+
run: rustup show
40+
41+
- name: Cache cargo
42+
uses: actions/cache@v4
43+
with:
44+
path: |
45+
~/.cargo/registry
46+
~/.cargo/git
47+
target
48+
key: buzz-acp-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
49+
restore-keys: buzz-acp-${{ runner.os }}-
50+
51+
- name: Build
52+
run: cargo build --release -p buzz-acp
53+
54+
# Fail loudly here rather than shipping a binary that cannot start.
55+
- name: Smoke check
56+
run: |
57+
./target/release/buzz-acp --help > /dev/null
58+
echo "binary ok: $(./target/release/buzz-acp --help | head -1)"
59+
60+
- name: Publish release asset
61+
env:
62+
GH_TOKEN: ${{ github.token }}
63+
run: |
64+
set -euo pipefail
65+
SHORT_SHA="$(git rev-parse --short HEAD)"
66+
TAG="buzz-acp-linux-${SHORT_SHA}"
67+
cp target/release/buzz-acp "buzz-acp-x86_64-linux"
68+
sha256sum "buzz-acp-x86_64-linux" > "buzz-acp-x86_64-linux.sha256"
69+
# Idempotent: re-running the workflow on the same commit replaces the
70+
# assets instead of erroring on an existing tag.
71+
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
72+
gh release upload "$TAG" \
73+
"buzz-acp-x86_64-linux" "buzz-acp-x86_64-linux.sha256" \
74+
--repo "$GITHUB_REPOSITORY" --clobber
75+
else
76+
gh release create "$TAG" \
77+
"buzz-acp-x86_64-linux" "buzz-acp-x86_64-linux.sha256" \
78+
--repo "$GITHUB_REPOSITORY" \
79+
--title "buzz-acp linux x86_64 @ ${SHORT_SHA}" \
80+
--notes "Built from ${GITHUB_SHA} by ${GITHUB_WORKFLOW}. Consumed by deploy/arch-box/install-buzz-acp.sh."
81+
fi
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copy to ~/.config/buzz-acp/buzz-acp.env and chmod 600. Never commit the real file.
2+
#
3+
# The agent's Nostr identity. Its pubkey must already be a relay member
4+
# (buzz-admin add-member) or the harness authenticates and then sees nothing.
5+
# Stored in Vaultwarden: Founders Keys / buzz-relay-cofoundy
6+
BUZZ_PRIVATE_KEY=
7+
8+
BUZZ_RELAY_URL=wss://buzz.cofoundy.dev
9+
10+
# Claude Code over ACP. Requires `npm install -g @agentclientprotocol/claude-agent-acp`
11+
# and an authenticated `claude` session on this host (~/.claude/.credentials.json) —
12+
# that session is what lets the agent bill against the Max subscription instead of
13+
# a per-token ANTHROPIC_API_KEY.
14+
BUZZ_ACP_AGENT_COMMAND=claude-agent-acp
15+
16+
# Author gate. `owner-only` is the safe default but drops every event until an
17+
# owner is resolved, so set the owner explicitly rather than widening the gate.
18+
# Andre's pubkey:
19+
BUZZ_ACP_AGENT_OWNER=4cb243bbc2efb7f0871b84e8ff641d7d8329951d900d19ee98018a01f18fba40
20+
BUZZ_ACP_RESPOND_TO=owner-only
21+
22+
# Parallel agent subprocesses (1-32). Each spawns its own MCP server, so memory
23+
# scales roughly linearly. Start at 1 and raise only if queue depth grows.
24+
BUZZ_ACP_AGENTS=1
25+
26+
# Self-prompting on idle. 0 = disabled; enable (>=10s) only once you actually
27+
# want an autonomous agent, since it burns tokens with nobody asking.
28+
BUZZ_ACP_HEARTBEAT_INTERVAL=0

deploy/arch-box/buzz-acp.service

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# systemd USER unit for the buzz-acp harness on the Cofoundy agent box.
2+
#
3+
# Install:
4+
# mkdir -p ~/.config/systemd/user ~/.config/buzz-acp
5+
# cp buzz-acp.service ~/.config/systemd/user/
6+
# cp buzz-acp.env.example ~/.config/buzz-acp/buzz-acp.env
7+
# chmod 600 ~/.config/buzz-acp/buzz-acp.env # then fill it in
8+
# systemctl --user daemon-reload
9+
# systemctl --user enable --now buzz-acp
10+
# loginctl enable-linger "$USER" # survive logout / reboot
11+
#
12+
# Deliberately a *user* unit, matching the hermes-gateway-* agents already on
13+
# this box: same lifecycle, same journal, no root.
14+
[Unit]
15+
Description=Buzz ACP harness (Cofoundy agent on buzz.cofoundy.dev)
16+
Documentation=https://github.com/block/buzz/tree/main/crates/buzz-acp
17+
After=network-online.target
18+
Wants=network-online.target
19+
20+
[Service]
21+
Type=simple
22+
# Secrets live in a chmod-600 env file, never in the unit — the unit is
23+
# committed to git, the env file never is.
24+
EnvironmentFile=%h/.config/buzz-acp/buzz-acp.env
25+
ExecStart=%h/.local/bin/buzz-acp
26+
Restart=on-failure
27+
RestartSec=10s
28+
# The relay is the source of truth for agent state, so a restart is cheap; cap
29+
# the churn so a misconfigured harness cannot hot-loop against the relay.
30+
StartLimitIntervalSec=300
31+
StartLimitBurst=5
32+
33+
# The agent shells out through its MCP tools, so this is not a sandbox — it
34+
# runs at the operator's trust level by design (VISION_AGENT.md). These are
35+
# blast-radius reducers, not a containment boundary.
36+
NoNewPrivileges=true
37+
PrivateTmp=true
38+
39+
StandardOutput=journal
40+
StandardError=journal
41+
SyslogIdentifier=buzz-acp
42+
43+
[Install]
44+
WantedBy=default.target
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Install/update the buzz-acp harness on the Cofoundy agent box.
4+
#
5+
# Pulls the newest binary published by .github/workflows/buzz-acp-linux.yml
6+
# rather than building locally, so the host needs no Rust toolchain and the
7+
# running binary is always traceable to a commit.
8+
#
9+
# Usage:
10+
# ./install-buzz-acp.sh # newest published build
11+
# ./install-buzz-acp.sh <short-sha> # pin a specific build
12+
#
13+
set -euo pipefail
14+
15+
REPO="${BUZZ_ACP_REPO:-cofoundy/buzz}"
16+
DEST="${BUZZ_ACP_DEST:-$HOME/.local/bin}"
17+
ASSET="buzz-acp-x86_64-linux"
18+
19+
pinned="${1:-}"
20+
if [ -n "$pinned" ]; then
21+
tag="buzz-acp-linux-${pinned#buzz-acp-linux-}"
22+
else
23+
# Releases are listed newest-first; filter to this workflow's tag namespace
24+
# so unrelated upstream releases (desktop builds, etc.) never match.
25+
tag="$(gh release list --repo "$REPO" --limit 50 \
26+
| awk '{print $1}' | grep '^buzz-acp-linux-' | head -1)"
27+
[ -n "$tag" ] || { echo "no buzz-acp release found in $REPO — run the workflow first" >&2; exit 1; }
28+
fi
29+
30+
echo "installing $tag from $REPO"
31+
tmp="$(mktemp -d)"
32+
trap 'rm -rf "$tmp"' EXIT
33+
34+
gh release download "$tag" --repo "$REPO" --pattern "$ASSET*" --dir "$tmp" --clobber
35+
36+
# Verify before installing: a truncated download would otherwise land as a
37+
# broken binary that only fails at agent start time.
38+
( cd "$tmp" && sha256sum -c "${ASSET}.sha256" )
39+
40+
mkdir -p "$DEST"
41+
install -m 0755 "$tmp/$ASSET" "$DEST/buzz-acp"
42+
43+
echo "installed: $DEST/buzz-acp ($tag)"
44+
"$DEST/buzz-acp" --help | head -1
45+
46+
cat <<EOF
47+
48+
Next:
49+
systemctl --user restart buzz-acp # if the unit is already installed
50+
systemctl --user status buzz-acp
51+
EOF

0 commit comments

Comments
 (0)