feat(plugin): Buzz (Block/Nostr) platform adapter - #71610
Conversation
Plugin-path adapter (zero core changes) connecting Hermes to a Buzz community relay via the buzz CLI binary (JSON in/out, arg-list exec, key passed via env only). Inbound uses a poll loop with per-channel high-water marks seeded from newest (no history replay), event-id de-dupe, self-echo suppression by pubkey, and mention gating in channels (DMs always dispatch). Registers env_enablement, cron home-channel delivery, and an out-of-process standalone sender, mirroring the IRC plugin. Verified against a live relay: connect -> send -> poll -> MessageEvent round-trip, self-echo suppressed, clean disconnect. Known limitation: polled inbound (default 4s); a websocket transport (buzz-ws-client) is a future optimization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Buzz adapter's check_requirements() reads config from env only, so a config.yaml-only setup (relay URL in gateway.platforms.buzz.extra) failed the check_fn gate and was silently skipped at startup. Add an apply_yaml_config_fn hook that bridges buzz.extra -> BUZZ_* env vars, mirroring the Slack/Telegram pattern; BUZZ_PRIVATE_KEY stays a .env secret. Also fix send_reaction() to use buzz-cli's real flags (--event <id> --emoji), replacing the non-existent --channel/--message-id flags that would have failed on every message. Verified live against the hosted relay (accepted:true). Refs NousResearch#68871
Channel messages address the agent with a leading @mention (e.g. '@chip /whoami'). The adapter passed the raw content through, so the gateway's is_command() check (text.lstrip().startswith('/')) never matched and slash commands were routed as plain chat. Strip a leading mention (name, npub, or hex form) before dispatch in channels, mirroring the Discord adapter. Also cleans normal prompts ('@chip what's up?' -> 'what's up?'). DMs are untouched. Verified live: '@chip /whoami' -> '/whoami' after connect populates identity. Refs NousResearch#68871
Channel mention-gating was hardcoded on. Add a configurable require_mention (default True, preserving current behavior). When False, the agent responds to every message in a watched channel, not only when @mentioned; DMs always dispatch. Read from config.yaml gateway.platforms.buzz.extra.require_mention with BUZZ_REQUIRE_MENTION env override, bridged via apply_yaml_config_fn like the other settings. A leading mention is still stripped when present. Refs NousResearch#68871
On hosted relays `buzz dms list` reliably returns [] even when DM conversations exist, so DMs leaked in via `channels list` (as entries named "DM" with an empty description) and were seeded chat_type="group". That put them behind the channel mention gate: "@chip /whoami" worked but an un-mentioned DM was silently dropped. Classify from the Nostr tags of real traffic instead: a message another user sends in a DM carries a structural ["p", <own pubkey>] tag even when the text never mentions the agent, while in a real channel a p-tag-to-self only ever accompanies a visible @mention (typed mention or reply). A group conversation therefore latches to chat_type="dm" on the first kind-9 event that is p-tagged to self WITHOUT a visible mention in the content — guarded by channels-list metadata so a real community channel (real name / non-empty description) is never reclassified by a reply or mention that p-tags the agent. - latch during history seeding too, so a leaked DM bypasses the mention gate from the very first poll after connect - keep `dms list` as a best-effort source, and scan `channels list` as a fallback so DM conversations opened mid-run still get watched - strip a leading @mention in DMs as well, so "@chip /whoami" keeps firing as a slash command after the conversation reclassifies Refs NousResearch#68871 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@alt-glitch would love to get your eyes on this. Buzz is growing pretty rapidly, and is shaping up to be a great tool for inter-agent communications. |
|
Took your adapter for a real-world spin this weekend and wanted to report back — it held up well. I've been building multi-agent support on top of #62944 (one gateway process, N agents), and Buzz is the first platform where that registry can map 1:1 onto workspace members — an identity is just a keypair. So I stacked a branch that runs one instance of your adapter per agent, each with its own Two findings from running it against a live self-hosted relay (closed mode, NIP-42/98) that may be useful for this PR:
One micro-observation, take or leave: Nice work on the adapter — it made the multi-agent demo almost anticlimactic. |
… adapter Expands the 'Recommended display settings' section into a comprehensive 'Recommended default settings' block covering display, access control, polling, and mention behavior. Each setting includes an inline annotation and rationale bullet. Matches Telegram/email default behavior (no intermediate tool output, mention-gated channels, private-by-default access). Refs NousResearch#68871
Yeah, I didn't want to do this because it felt like a hack to cue off of the name of a conversation. I've opened an issue with the block/buzz project to fix the dm channel array bug. |
…tration Follow-up on the salvaged #71610 commits: - acquire/release a scoped lock on relay_url+pubkey in connect/disconnect (IRC pattern) so two profiles can't drive one Buzz identity — duplicate replies and split de-dupe state; +2 tests - negative-cache _resolve_user_name failures so a profile-less pubkey doesn't re-hit 'users get' every poll sweep (flagged by @jethac on the PR) - register user-guide/messaging/buzz in website/sidebars.ts (page was unreachable — the #63359 trap)
…tration Follow-up on the salvaged #71610 commits: - acquire/release a scoped lock on relay_url+pubkey in connect/disconnect (IRC pattern) so two profiles can't drive one Buzz identity — duplicate replies and split de-dupe state; +2 tests - negative-cache _resolve_user_name failures so a profile-less pubkey doesn't re-hit 'users get' every poll sweep (flagged by @jethac on the PR) - register user-guide/messaging/buzz in website/sidebars.ts (page was unreachable — the #63359 trap)
|
Merged via PR #73610 with your authorship preserved on all six commits (adapter, config bridge, mention strip, require_mention, p-tag DM classification, docs defaults). On top we added a scoped identity lock (relay+pubkey — prevents the duplicate-reply collision two profiles could hit), negative caching in Really solid work — the p-tag DM classifier working around block/buzz#2897 was the standout piece, and both your hosted-relay verification and @jethac's independent self-hosted run gave us high confidence. Hermes now ships Buzz as a bundled platform. Thanks! |
…tration Follow-up on the salvaged NousResearch#71610 commits: - acquire/release a scoped lock on relay_url+pubkey in connect/disconnect (IRC pattern) so two profiles can't drive one Buzz identity — duplicate replies and split de-dupe state; +2 tests - negative-cache _resolve_user_name failures so a profile-less pubkey doesn't re-hit 'users get' every poll sweep (flagged by @jethac on the PR) - register user-guide/messaging/buzz in website/sidebars.ts (page was unreachable — the NousResearch#63359 trap)
What
Adds a Buzz (Block/Nostr) platform adapter under
plugins/platforms/buzz/, so the agent can connect to Buzz relays and take part in channels and DMs. Implements #68871.Commits
feat(plugin): add Buzz (Block/Nostr) platform adapterfix(plugin): bridge Buzzconfig.yaml→ env + fix reaction flagsfix(plugin): strip leading @mention from Buzz channel messagesfeat(plugin): addrequire_mentionsetting to Buzz adapterfix(plugin): classify Buzz DMs by p-tag so un-mentioned DMs dispatchEmpty DM list workaround
The adapter works around an upstream Buzz bug I filed against the Buzz project — block/buzz#2897:
buzz dms listreturns[]for a DM recipient, so an agent can't discover DM conversations it belongs to. On hosted relays this means inbound DMs never appear indms list; they only surface viachannels listas an entry named"DM"with an empty description. That caused them to be seeded asgroupchats and fall behind the channel mention gate — so@Chip /whoamiworked, but an un-mentioned DM was silently dropped.Workaround (
fix(plugin): classify Buzz DMs by p-tag …): instead of trustingdms list, classify from the Nostr tags of real traffic. A message another user sends in a DM carries a structural["p", <own pubkey>]tag even when the text never @mentions the agent, whereas in a real channel a p-tag-to-self only ever accompanies a visible @mention. So a conversation latches tochat_type="dm"on the first kind-9 event that is p-tagged to self without a visible mention — guarded bychannels listmetadata so a real community channel (real name / non-empty description) is never reclassified. Latching also runs during history seeding (so a leaked DM bypasses the mention gate from the first poll after connect),dms listis kept as a best-effort source with achannels listfallback, and a leading @mention is stripped in DMs too so@Chip /whoamistill fires as a slash command after reclassification.Once block/buzz#2897 is fixed upstream, the
dms listpath works on its own and the tag-based classification becomes belt-and-suspenders.Tests
tests/gateway/test_buzz_adapter.pycovers the adapter, including the DM-classification behavior described above.