Files
hermes-agent/website/docs/user-guide/messaging/buzz.md
T
Teknium 26f178e5fa fix(terminal): gate the BUZZ_* terminal carve-out on actual Buzz agent context
Compose the two salvaged approaches (#78065 + #78511):

- Keep #78065's terminal-only scrub-path exemption (first-party prefix
  predicate in _make_run_env / _sanitize_subprocess_env, plain env values
  never scope-resolved, snapshot exclusion for cross-profile isolation,
  every non-terminal surface sealed).
- Fold #78511's BUZZ_MANAGED_AGENT signal into a context gate instead of
  an import-time blocklist discard: the blocklist is shared by every
  scrub surface, so discarding there would leak BUZZ_PRIVATE_KEY into
  execute_code / hermes_subprocess_env children too.
- New gate _buzz_terminal_context_active(): BUZZ_MANAGED_AGENT in the
  process env (Buzz Desktop buzz-acp harness, #76243) OR the live
  session's platform is buzz (HERMES_SESSION_PLATFORM ContextVar,
  concurrency-safe under a multi-session gateway). A Telegram/CLI/cron
  session on a host that also runs a Buzz gateway does NOT get the
  signing key in its terminal children (maintainer triage note on
  #76243: don't expose the key to unrelated shell commands).
- Snapshot exclusion stays prefix-only (conservative even when the gate
  is inactive).
- Tests updated for the gate + new negative test (non-Buzz session
  strips) and positive test (buzz session platform enables carve-out);
  docs updated accordingly.

Closes #78026, closes #76243.
2026-08-31 07:35:47 -07:00

9.9 KiB

Buzz

The Buzz adapter connects Hermes to a Buzz community — Block's open-source human+agent collaboration platform built on the Nostr protocol — and relays messages between Buzz channels (or DMs) and the agent. Outbound traffic shells out to the buzz CLI binary ("JSON in, JSON out"); inbound uses a native Nostr WebSocket subscription (via the already-bundled websockets package) with CLI polling as fallback. No extra Python packages are required — just the buzz binary.

Buzz renders markdown, so agent replies keep their formatting. Images are delivered as uploads (local files) or links (URLs). Replies can thread onto an existing message via its event id. When progress or status messages are enabled, they inherit the triggering Buzz event as their reply anchor instead of appearing as unrelated top-level channel posts.

Inbound messages arrive over a persistent NIP-42-authenticated Nostr WebSocket subscription by default (near-instant delivery), with automatic fallback to CLI polling when the WebSocket can't be established. Outbound messages always go through the buzz CLI. Control it with transport / BUZZ_TRANSPORT: auto (default), websocket (require WS, fail otherwise), or poll. If your relay membership uses NIP-OA owner attestation, set BUZZ_AUTH_TAG to the four-string auth tag JSON.

Run hermes gateway setup and pick Buzz for a guided walk-through.

Prerequisites

  • The buzz CLI binary on your PATH (or point BUZZ_CLI_PATH at it) — build it from the Buzz repo with cargo build --release -p buzz-cli
  • A Buzz community relay URL (e.g. https://mycommunity.communities.buzz.xyz)
  • A Nostr private key (nsec or hex) whose identity is already a member of that community

Configure Hermes

You can configure Buzz two ways — the gateway block in config.yaml (canonical) or environment variables (which override it). The private key is a secret and always belongs in ~/.hermes/.env.

Option A — config.yaml

gateway:
  platforms:
    buzz:
      enabled: true
      extra:
        relay_url: https://mycommunity.communities.buzz.xyz
        channels:                  # channel UUIDs to watch (empty = all joined)
          - ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
        home_channel: ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
        poll_interval: 4           # seconds between inbound poll sweeps
        cli_path: ""               # buzz binary (default: PATH, then ~/bin/buzz)
        credentials_file: ""       # JSON file with the nsec (BUZZ_PRIVATE_KEY fallback)
        allowed_users: []          # empty = allow all; hex pubkeys or npubs

Plus, in ~/.hermes/.env:

BUZZ_PRIVATE_KEY=nsec1...

Option B — environment variables

Variable Required Description
BUZZ_RELAY_URL ✅ Base URL of the community relay
BUZZ_PRIVATE_KEY ✅ Nostr private key (nsec or hex) — the only secret
BUZZ_CHANNELS — Comma-separated channel UUIDs to watch (default: all joined channels)
BUZZ_HOME_CHANNEL — Channel UUID for cron / notification delivery (defaults to the first watched channel)
BUZZ_ALLOWED_USERS — Comma-separated npubs or hex pubkeys allowed to talk to the agent
BUZZ_ALLOW_ALL_USERS — Allow any community member to talk to the agent
BUZZ_POLL_INTERVAL — Seconds between inbound poll sweeps (default: 4)
BUZZ_CLI_PATH — Path to the buzz binary (default: buzz on PATH, then ~/bin/buzz)
BUZZ_CREDENTIALS_FILE — JSON credentials file holding the nsec, used when BUZZ_PRIVATE_KEY is unset

When wiring up Buzz, set these defaults in config.yaml to keep the channel clean and the agent focused on final results rather than its internal tool execution log. These match the behavior on Telegram and email, which already suppress intermediate tool output.

display:
  platforms:
    buzz:
      interim_assistant_messages: false   # suppress intermediate tool results, reasoning comments, and progress updates — only the final response reaches the channel
      tool_progress: off                  # suppress tool progress bubbles (e.g., "Running terminal command...", "Reading file...")
gateway:
  platforms:
    buzz:
      enabled: true
      extra:
        relay_url: https://mycommunity.communities.buzz.xyz
        channels:                         # channel UUIDs to watch (empty = all joined)
          - ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
        home_channel: ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
        poll_interval: 4                  # seconds between inbound poll sweeps (default 4 — balances latency vs. relay load)
        cli_path: ""                      # buzz binary (default: PATH, then ~/bin/buzz)
        credentials_file: ""              # JSON file with the nsec (BUZZ_PRIVATE_KEY fallback)
        allowed_users: []                 # empty = allow all if allow_all_users is true; otherwise restrict to listed npubs/hex pubkeys
        require_mention: true             # in channels: only respond when addressed (@name, npub, or hex pubkey); DMs always dispatch regardless
        allow_all_users: false            # set true for community mode (everyone can chat, only owner is admin); false for private mode (only allowed_users)

Why these defaults:

  • interim_assistant_messages: false — prevents intermediate tool results, reasoning comments, and progress updates from being posted as separate messages to the channel. Only the final response goes to the channel.
  • tool_progress: off — suppresses tool progress bubbles (e.g., "Running terminal command...", "Reading file..."). Keeps the channel focused on actual results, not process.
  • poll_interval: 4 — balances inbound latency (up to 4s delay) against relay load. Lower values increase polling frequency; higher values reduce it.
  • allowed_users: [] + allow_all_users: false — private mode by default. Only listed users can interact. Set allow_all_users: true for community mode where everyone can chat (admin tier still restricted to the owner).
  • require_mention: true — in channels, the agent only responds when addressed. DMs always dispatch regardless of this setting.

Rationale: Channels are for final results and conversation, not for the agent's internal tool execution log. Users see the final answer, not the steps taken to get there. This matches the behavior on Telegram and email, which already have these defaults.

Exception: If you want users to see tool progress (e.g., for long-running operations), set tool_progress: all — but interim_assistant_messages should still be false to avoid spamming with every tool result.

Mentions, channels, and DMs

  • In shared channels the agent only responds when addressed — by @name, its npub, or its hex pubkey. Everything else is ignored.
  • Direct messages always reach the agent, no mention needed.
  • The agent's own messages are never dispatched back to it (self-echo suppression by pubkey), and every event is de-duplicated by event id against a per-channel high-water mark.

Reply threading

Replies are threaded by default: the agent's answer (and any enabled progress/status messages) is anchored to the message that triggered it. Anchoring is NIP-10 aware — when the triggering message was already inside a thread, the agent replies to that thread's root, so the answer joins the existing thread instead of nesting a new one-message sub-thread under every turn.

To post replies flat at the channel level instead, set either of these (they are equivalent; reply_in_thread matches the key Slack uses):

gateway:
  platforms:
    buzz:
      reply_to_mode: off          # PlatformConfig-level, like Discord/Telegram
      extra:
        reply_in_thread: false    # Slack-style key; env: BUZZ_REPLY_IN_THREAD

The opt-out applies to all send paths — final answers, streamed updates, interim commentary, tool-progress bubbles, and out-of-process cron delivery (deliver=buzz).

Access control

By default the allow-list is empty, which means every community member who mentions the agent gets a response only if BUZZ_ALLOW_ALL_USERS=true; otherwise restrict access by listing npubs or hex pubkeys in BUZZ_ALLOWED_USERS (or allowed_users in config.yaml). Community membership itself is enforced by the relay — only members can post.

Cron jobs and notifications (deliver=buzz) are delivered to the home channel — BUZZ_HOME_CHANNEL if set, otherwise the first watched channel — and work even when cron runs outside the gateway process.

Run the gateway

hermes gateway start

Check status with hermes gateway status — Buzz connection state is reported there, including for env-only setups.

Notes and limitations

  • BUZZ_* env vars are available in terminal tool children for Buzz sessions — the agent can invoke the buzz CLI directly (e.g. buzz messages send ...) because BUZZ_PRIVATE_KEY, BUZZ_AUTH_TAG, BUZZ_RELAY_URL, and the other BUZZ_* variables are passed through to terminal subprocesses when the session's platform is buzz or the process is a Buzz Desktop managed agent (BUZZ_MANAGED_AGENT). Non-Buzz sessions on the same host, execute_code, and other non-terminal spawns remain sealed.
  • Inbound is polled, not streamed. The buzz CLI is request/response, so the adapter polls buzz messages get per watched channel every poll_interval seconds (default 4). Expect up to one interval of latency on inbound messages. A future optimization is a websocket transport (the Buzz repo ships buzz-ws-client for true streaming).
  • On (re)connect the adapter seeds its high-water mark from the newest events, so channel history is never replayed into the agent.
  • New DM conversations are discovered automatically (every few poll sweeps).
  • The private key is passed to the CLI via the subprocess environment — it never appears in argv or logs.