Skip to content
Open
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
5 changes: 5 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -825,3 +825,8 @@ These are verified gaps in the current implementation — not design aspirations
| 4 | **Huddle recording/tracks not built** | Voice, room lifecycle, and join/leave/end events are wired (see Huddle Audio above). Recording and per-track publishing have reserved kinds but no producer yet. |
| 5 | **Approval gates not wired end-to-end** | The executor returns `StepResult::Suspended` and the relay has grant/deny API endpoints with DB CRUD, but the engine intercepts before creating `WaitingApproval` rows — runs that hit an approval gate are marked as Failed (🚧 WF-08). |
| 6 | **Workflow actions partially stubbed** | The `send_dm` and `set_channel_topic` workflow actions are in the schema but return `NotImplemented` — a run that reaches one fails at execution (🚧 WF-07). |

## Self-host backend framework

For the operator-facing always-on agents + any-client layout, see
[docs/self-host-backend-framework.md](docs/self-host-backend-framework.md).
9 changes: 9 additions & 0 deletions crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,12 @@ See the [root TESTING.md](../../TESTING.md) for the full integration testing gui
## License

Apache-2.0

## Always-on agents (any client)

For **server-side `buzz-acp`** with Desktop, web, mobile, or CLI as thin clients
(agents stay up when devices sleep or disconnect), see:

- **Full framework:** [docs/self-host-backend-framework.md](../../docs/self-host-backend-framework.md)
- **Quick start:** [docs/self-host-always-on-agents.md](../../docs/self-host-always-on-agents.md)
- **systemd:** [deploy/systemd/](../../deploy/systemd/)
6 changes: 6 additions & 0 deletions deploy/compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ $EDITOR .env
curl -fsS "http://127.0.0.1:$(grep -E '^BUZZ_HTTP_PORT=' .env | cut -d= -f2-)/_liveness"
./run.sh status
```

## Always-on agents (any client)

- Framework: [docs/self-host-backend-framework.md](../../docs/self-host-backend-framework.md)
- Guide: [docs/self-host-always-on-agents.md](../../docs/self-host-always-on-agents.md)
- Units: [deploy/systemd/](../systemd/)
33 changes: 33 additions & 0 deletions deploy/systemd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# systemd units for always-on Buzz agents

These units run [`buzz-acp`](../../crates/buzz-acp/README.md) as a supervised
service so agents stay online independent of any Desktop or laptop session.

See the full framework: [docs/self-host-backend-framework.md](../../docs/self-host-backend-framework.md).

## Files

| File | Purpose |
|------|---------|
| `buzz-acp@.service` | Template unit (`buzz-acp@coder`) |
| `buzz-acp.env.example` | Environment skeleton (copy per agent) |

## Install

```bash
sudo cp buzz-acp@.service /etc/systemd/system/
sudo mkdir -p /etc/buzz-acp
sudo cp buzz-acp.env.example /etc/buzz-acp/coder.env
sudo edit /etc/buzz-acp/coder.env # keys + BUZZ_RELAY_URL
sudo systemctl daemon-reload
sudo systemctl enable --now buzz-acp@coder
sudo systemctl status buzz-acp@coder
```

User units: install under `~/.config/systemd/user/` and use `systemctl --user`.

## Requirements

- `buzz-acp` installed at `/usr/local/bin/buzz-acp` (or edit `ExecStart`)
- Relay reachable at the **same host string** clients use
- Agent pubkey is a relay/channel member as required by your join policy
20 changes: 20 additions & 0 deletions deploy/systemd/buzz-acp.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# /etc/buzz-acp/<name>.env
# chmod 600

# Agent Nostr identity (hex 64 or nsec1…)
BUZZ_PRIVATE_KEY=

# MUST match Buzz Desktop relay host string exactly (community selector)
BUZZ_RELAY_URL=ws://buzz.example.com:3000

# ACP agent binary + args (comma-separated args for buzz-acp)
BUZZ_ACP_AGENT_COMMAND=goose
# BUZZ_ACP_AGENT_ARGS=

BUZZ_ACP_SUBSCRIBE=mentions
BUZZ_ACP_RESPOND_TO=anyone
# BUZZ_ACP_DISPLAY_NAME=Coder
# BUZZ_ACP_SYSTEM_PROMPT_FILE=/etc/buzz-acp/coder.prompt.md

# HOME for the agent process (credentials, cwd defaults)
# HOME=/var/lib/buzz-acp/coder
22 changes: 22 additions & 0 deletions deploy/systemd/buzz-acp@.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[Unit]
Description=Buzz ACP agent (%i)
Documentation=https://github.com/block/buzz/blob/main/docs/self-host-always-on-agents.md
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
# Environment file: /etc/buzz-acp/%i.env (or EnvironmentFile= override)
EnvironmentFile=-/etc/buzz-acp/%i.env
EnvironmentFile=-/etc/buzz-acp/%i
# Binary on PATH after `cargo install` / release build install
ExecStart=/usr/local/bin/buzz-acp
Restart=on-failure
RestartSec=3
# Harden lightly — agents need homedir tools; tighten further per site
NoNewPrivileges=true
# Limit runaway forks; raise if your agent spawns heavy tool trees
TasksMax=4096

[Install]
WantedBy=multi-user.target
172 changes: 172 additions & 0 deletions docs/self-host-always-on-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Self-host: always-on agents (any client → server backend)

Run **agents and the relay on a machine that stays on**. Connect from **any**
Buzz client — Desktop (macOS / Windows / Linux), web, mobile, or `buzz` CLI —
without tying agent lifetime to a single person’s laptop.

**Full framework (architecture, security, runbook, topologies):**
[self-host-backend-framework.md](./self-host-backend-framework.md).

This is the thin-client / remote-backend layout (same idea as pointing a desktop
app at a remote server process).

| Role | Runs | Examples |
|------|------|----------|
| **Clients** (many) | Human UI only | Desktop on a laptop or workstation, web UI, mobile, `buzz` CLI, CI bots that are *not* coding agents |
| **Backend** (one or more hosts) | `buzz-relay` + `buzz-acp` (+ Postgres/Redis per deploy) | VPS, bare metal, homelab, k8s node |

Agents stay online when clients disconnect, sleep, or switch devices.

```
Desktop ─┐
Web ─┼── ws(s)://HOST:3000 ──► buzz-relay
Mobile ─┤ ▲
CLI ─┘ buzz-acp @ agent
ACP stdio agent
(goose, claude, codex,
cursor-agent, grok, …)
```

Anyone on the community can @mention server-side agents. You do **not** need a
laptop specifically — only a client that speaks the relay URL.

## Why the URL string matters

Buzz selects the **community** from the connection **Host** header
(`req.community = resolve_host(connection.host)`).

Use the **same host string** on every client **and** every `buzz-acp`:

| Good | Bad |
|------|-----|
| `ws://buzz.example.com:3000` everywhere | One device uses `localhost`, agents use the public hostname |
| `ws://myserver.tailnet.ts.net:3000` consistently | Mixing Tailscale DNS and LAN IP for “the same” deploy |
| `wss://buzz.example.com` behind TLS terminator | Clients on `https://…` / `wss://…` while agents still on plain `ws://127.0.0.1` without a matching host map |

If a client shows empty rooms while the server CLI sees agents, you are almost
always in a **different community** (wrong host) or on a **fresh Nostr identity**
that is not a channel member.

## 1. Backend: relay

Follow the main deploy guide (`deploy/compose` or your preferred install) so
`GET http://HOST:3000/_liveness` returns ok and WebSocket `ws://HOST:3000`
(or `wss://…`) accepts clients.

Ensure the relay is reachable from every client network path you care about
(public DNS, Tailscale, LAN, reverse proxy with WebSocket support). Bind and
firewall so remote clients are intentional, not accidental open internet
without TLS/auth policy.

## 2. Backend: always-on agent with systemd

Install `buzz-acp` and `buzz` (CLI) from this repo
(`cargo build --release -p buzz-acp -p buzz-cli`).

Per agent:

1. Generate a key: `buzz-admin generate-key` (save the secret).
2. Add the agent as a relay member (closed-relay deploys).
3. Install the unit template from `deploy/systemd/`:

```bash
# system-wide example
sudo cp deploy/systemd/buzz-acp@.service /etc/systemd/system/
sudo mkdir -p /etc/buzz-acp
sudo cp deploy/systemd/buzz-acp.env.example /etc/buzz-acp/coder.env
# edit coder.env — BUZZ_PRIVATE_KEY, BUZZ_RELAY_URL, agent command
sudo systemctl daemon-reload
sudo systemctl enable --now buzz-acp@coder
sudo systemctl status buzz-acp@coder
```

User-level units work the same under `~/.config/systemd/user/` with paths adjusted.

### Minimal env

```bash
BUZZ_PRIVATE_KEY=<agent hex or nsec>
BUZZ_RELAY_URL=ws://HOST:3000 # MUST match every client host string
BUZZ_ACP_AGENT_COMMAND=goose # or claude-agent-acp, etc.
# BUZZ_ACP_AGENT_ARGS= # comma-separated if needed
BUZZ_ACP_SUBSCRIBE=mentions
BUZZ_ACP_RESPOND_TO=anyone # or owner-only / allowlist
```

See [crates/buzz-acp/README.md](../crates/buzz-acp/README.md) for goose, Codex,
Claude Code, multi-agent pools, and heartbeats.

## 3. Clients: anyone joins the same community

### Buzz Desktop (any OS)

1. Install [Buzz Desktop](https://github.com/block/buzz/releases) on the machine
people actually sit at (laptop, desktop workstation, etc.).
2. Use a Nostr identity that is a **member** of the channels you care about
(a random new key will not see existing private membership).
3. Connect / join with the **same** relay URL the agents use:

`ws://HOST:3000` or `wss://HOST`

4. Confirm each agent is a **channel member** (same as adding a person).
5. `@mention` the agent.

Optional env (builds that honor it):

```bash
export BUZZ_RELAY_URL=ws://HOST:3000
export BUZZ_AUTO_CONNECT_DEFAULT_RELAY=1
buzz-desktop
```

### Web / mobile

Point the client at the same community URL. Identity and membership rules are
unchanged: same host string, keys that are members of the right channels.

### CLI / automation

```bash
export BUZZ_PRIVATE_KEY=<human or bot>
export BUZZ_RELAY_URL=http://HOST:3000 # HTTP origin for REST; host must map to same community
buzz channels list
buzz messages send --channel <uuid> --content '@AgentName hello'
```

## 4. Smoke test (from any machine that can reach the relay)

```bash
export BUZZ_PRIVATE_KEY=<member key>
export BUZZ_RELAY_URL=http://HOST:3000
buzz messages send --channel <uuid> --content '@AgentName ping — reply PONG'
buzz messages get --channel <uuid> --limit 10
```

## Who this is for

| Audience | Use |
|----------|-----|
| Solo operator | Agents on a VPS; Desktop or CLI at home |
| Small team | Shared community URL; each human brings their own client |
| Homelab | Relay + agents on NAS/mini-PC; phones + laptops join via Tailscale |
| CI / ops | CLI and agents on servers; humans on Desktop/web |

Not laptop-specific. Laptop is only the common *example* of a client that should
not host production agents.

## Troubleshooting

| Symptom | Check |
|---------|--------|
| Empty channels on one device | That device’s host string ≠ agent `BUZZ_RELAY_URL`; or new key |
| Agent never replies | `systemctl status buzz-acp@…`; agent member of channel; subscribe=mentions |
| Works on server CLI only | Other clients connected to different host/community |
| Closed relay rejects a client | Add that client’s pubkey as relay member; improve join/invite UX upstream |
| Voice huddle mic denied | OS microphone permission on the **device running the UI**; remote desktop/noVNC often has no real mic |

## Related

- [buzz-acp README](../crates/buzz-acp/README.md)
- [ARCHITECTURE.md](../ARCHITECTURE.md) — host → community binding
- Closed-relay client join: track upstream issues on membership / owner bootstrap
Loading