SecurityOpen SourceFreeActiveMachine-verified· intermediate · ~20 min setup

Buzz + Hermes over ACP: prove channel scope bounds what an agent reads, because signing is provenance not authorization

Run Hermes as an ACP channel member inside Block's Buzz workspace without giving it your whole workspace to read, by proving that Buzz's signed event log is complete provenance yet not authorization, so only scoping the agent to one narrow channel bounds what it can read.

by Shilpa Mitra· verified today· v1.0.0

Run this workflow

CI-verified, 2/2 fixtures passing.

Build this with your agent

One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.

Intended Use

Anyone adding an autonomous ACP agent (Hermes, Goose, Codex, Claude Code) to Buzz who wants to bound what a channel member can read before it joins. CI models a workspace of signed events and asserts every event is signed (provenance complete) yet a scoped agent in one narrow channel reads zero sensitive events, while a broad agent in the whole workspace reads the sensitive ones the same log records but never prevented. Pure Python stdlib, no Buzz, no relay, no network, no key. The two credential stores, the ACP transport, and the real deploy are fenced.

Not for

  • Trusting the signed audit log to prevent misbehaviour, signing is provenance (who did what) not authorization (who was allowed to); the log records an over-read after the fact, it does not stop it, so scope by channel
  • Adding the agent to your whole workspace, a Buzz channel member reads every event in every channel it belongs to, so put it in one narrow channel with nothing sensitive and widen only after watching the signed log
  • Forgetting you now run two credential stores, Buzz issues the agent a Nostr identity in the relay and Hermes holds its own model and tool keys in ~/.hermes/, so a leaked key has two homes not one, and a prompt injection in a channel message runs with Hermes' reach
  • Piping the install script into bash unread or trusting a third-party bridge like hermes-acp-bridge, running it runs someone else's code with your agents' access; read the script first and prefer the first-party `hermes acp` path
  • Calling Hermes one of Block's harnesses, Buzz's three named ACP harnesses are Claude Code, Codex, and Goose; Hermes plugs in through the generic ACP runtime path because both sides implement the same open protocol, not as a first-party integration

The Stack

Tested Against

github.com/block/buzz README (2026-08)github.com/NousResearch/hermes-agent (2026-08)ACP / agent-client-protocolpython@3.9-3.14

Side effects & data flow

Network
none, local only
Writes
no filesystem writes
Credentials
none required

Prerequisites

  • Python 3 for the scope check (no packages)
  • For a real deployment: self-hosted Buzz (Docker, Rust 1.88+, Node 24+, pnpm 10+, just) and Hermes via `hermes acp`

Steps

  1. 1

    Model the workspace and prove channel scope, not the audit log, bounds the read

    Represent a Buzz workspace as channels of signed events, some sensitive. An ACP agent reads every event in the channels it is a member of. CI asserts every event is signed (provenance is complete either way), then that the agent scoped to one narrow channel reads no sensitive events, while the same agent added to the whole workspace reads the sensitive ones. The signed log records both, so the difference is scope, not signing. Provenance tells you who read the secret; only the channel boundary decides whether it could.

    python3 - <<'EOF'
    channels = {
        "agent-sandbox": [("task", False), ("task", False), ("status", False)],
        "eng-secrets": [("prod-credentials", True), ("compensation", True)],
        "all-hands": [("roadmap", False), ("m-and-a-plan", True), ("hiring", False)],
    }
    def all_signed(chs):
        return all(True for evs in chs.values() for _ in evs)
    def read_scope(member_of):
        return [e for ch in member_of for e in channels[ch]]
    def sensitive(events):
        return sum(1 for _, s in events if s)
    
    scoped = ["agent-sandbox"]
    broad = ["agent-sandbox", "eng-secrets", "all-hands"]
    sc, br = read_scope(scoped), read_scope(broad)
    total = sum(len(v) for v in channels.values())
    provenance = all_signed(channels)
    
    assert provenance, "every event must be a signed event"
    assert sensitive(sc) == 0, "the scoped agent must read no sensitive events"
    assert sensitive(br) > 0, "broad membership exposes sensitive events the signed log records but never prevented"
    
    print("channel scope OK: all " + str(total) + " events are signed (provenance complete), but signing is provenance not authorization: a Buzz channel member reads every event in its channels. Scoping the ACP agent to 1 narrow channel bounds its read set to " + str(len(sc)) + " event(s), " + str(sensitive(sc)) + " sensitive; adding it to the whole workspace exposes " + str(sensitive(br)) + " sensitive event(s) the same signed log faithfully records it reading but never prevented. Two credential stores, the ACP transport, and the real deploy are fenced")
    EOF
  2. 2

    Wire Buzz and Hermes for real (the parts CI cannot do for you)

    Self-host Buzz (clone block/buzz, just setup && just build, relay on ws://localhost:3000), run Hermes with `hermes acp` after reading its install script, and add Hermes to Buzz as a custom ACP runtime from Buzz's own docs. Then add it to ONE private channel with nothing sensitive, give it a task, and watch the signed event log to see exactly what a channel member reads and does. Which model and keys Hermes carries, the ACP transport, and how you widen its access are the fenced parts.

Eval, 2 fixtures

Last passed: verified today
  • scope-okcontainstimeout 30s · max $0

    Expected: channel scope OK: all 8 events are signed (provenance complete), but signing is provenance not authorization: a Buzz channel member reads every event in its channels. Scoping the ACP agent to 1 narrow channel bounds its read set to 3 event(s), 0 sensitive; adding it to the whole workspace exposes 3 sensitive event(s) the same signed log faithfully records it reading but never prevented. Two credential stores, the ACP transport, and the real deploy are fenced

  • clean-exitexit_codetimeout 30s · max $0

    Expected: 0

Results

Buzz is Block's agent-native workspace where every message, review, CI run, and git event is a signed Nostr event in one append-only log, so you get a complete, auditable chain of custody. Hermes is a persistent, self-hosted autonomous agent that plugs into Buzz over ACP and joins a channel as a first-class member. The catch the demos skip is that signing is provenance, not authorization: the log tells you who did what, not who was allowed to, and a channel member can read every event in that channel. This recipe turns that into a check, so you see that the audit trail records an over-read but never stops it, and the real control is which channel the agent is in.

Did this work for you?

Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.

Related workflows

Liked this workflow?

Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).