Skip to content

fix(buzz): preserve configured agent handoffs - #77647

Open
cmyk wants to merge 1 commit into
NousResearch:mainfrom
Peakhunter:fix/buzz-unresolved-mention-fallback
Open

cmyk wants to merge 1 commit into
NousResearch:mainfrom
Peakhunter:fix/buzz-unresolved-mention-fallback

Conversation

@cmyk

@cmyk cmyk commented Aug 3, 2026

Copy link
Copy Markdown

What does this PR do?

Buzz supports structured identity mentions, but deliberate agent handoffs need a stable display-name-to-pubkey mapping. Dynamic channel-member lookup alone can resolve a configured name to the wrong identity, add duplicate notifications, or silently downgrade the handoff when Buzz rejects the intended identity.

This adds an optional outbound_mention_pubkeys map. When outbound text contains a complete configured @display-name marker, Hermes sends only the exact mapped npub or hex pubkey for that marker. Configured handoffs fail visibly if Buzz rejects the identity; they are never escaped, dynamically reassigned, or silently published as unstructured edits.

The guarantee applies to direct sends, local-image and attachment captions, standalone/cron sends, and streamed final replies. Matching uses Unicode NFKC plus casefold normalization, gives the longest configured name ownership of overlapping markers, and prevents dynamic lookup from reclaiming any configured span.

Related Issue

No linked issue. This preserves the configured-handoff portion requested in the maintainer review while dropping the unresolved-mention fallback superseded by current main.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Add optional platforms.buzz.extra.outbound_mention_pubkeys configuration for exact display-name-to-npub/hex identity mappings.
  • Validate pubkeys and reject invalid mappings or Unicode-normalized display-name collisions.
  • Normalize configured names and message content with NFKC plus casefold before complete-marker matching.
  • Give the longest configured name ownership of overlapping markers while protecting every overlapping configured name from dynamic member resolution.
  • Preserve exact configured identities across direct text sends, local-image and attachment captions, standalone/cron sends, and streamed final replies.
  • Defer stream previews containing configured handoffs and route final content through a fresh structured send; reject configured markers on unstructured edit paths.
  • Keep current main's bounded recovery ladder for unconfigured presentation mentions and remove this PR's superseded broad unresolved-mention fallback.
  • Document the configuration and add regressions for exact routing, invalid config, Unicode compatibility forms, overlapping names, dynamic collisions, retries, attachments, standalone delivery, and streaming.

How to Test

  1. Run HERMES_PYTHON=<dev-python> scripts/run_tests.sh tests/gateway/test_buzz_adapter.py tests/gateway/test_stream_consumer.py -q and verify all 270 tests pass.
  2. Run HERMES_PYTHON=<dev-python> scripts/run_tests.sh tests/gateway -q; in the recorded environment this produced 7,299 passed, 2 failed, 36 skipped. The two WeCom callback failures reproduce on exact upstream/main because defusedxml is unavailable.
  3. Run Ruff, Python compilation, and git diff --check on the changed Python files.
  4. Configure a display name to a known pubkey, then verify direct, attachment, standalone, and streamed-final messages emit only that exact --mention identity.
  5. Configure overlapping names such as Ann and Ann Lee, then verify @Ann Lee notifies only the longer exact identity while separate @Ann and @Ann Lee markers notify both intended identities.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu 26.04 with hosted Buzz

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A; no contributor workflow changed
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — adapter logic is platform-independent
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A; no model tool changed

Screenshots / Logs

Exact candidate SHA: d4d76f7430192e1b7431a4fc89b33a0b8bbdb216.

Focused Buzz adapter and stream-consumer verification passed 270 tests. The full Gateway suite produced 7,299 passed / 2 failed / 36 skipped; both failures are the pre-existing WeCom callback environment failures described above. Ruff, Python compilation, git diff --check, and the added-line security scan passed.

Independent exact-head review exercised configured and dynamic identity collisions, NFKC-equivalent and overlapping names, distinct markers, stream success and failure paths, attachments, standalone delivery, config validation, retry behavior, and duplicate-notification risk, and returned APPROVE with no findings.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 3, 2026
@XVVH

XVVH commented Aug 4, 2026

Copy link
Copy Markdown

Ran into this as well - cherry picked the fix and validated that it resolved the problem in both group and 1:1 chats on my install.

@cmyk

cmyk commented Aug 8, 2026

Copy link
Copy Markdown
Author

Follow-up: preserve explicit agent handoffs

Commit 8ec0c451d hardens the fallback introduced by this PR after a live regression showed that an intentional agent handoff could be silently downgraded from a visible @Name to plain Name when Buzz name resolution failed.

The original readable-delivery fallback remains for incidental unresolved mentions. The follow-up adds a stricter path for intentional handoffs:

  • optional outbound_mention_pubkeys configuration maps stable display names to exact Nostr identities;
  • matching content keeps the literal visible @Name and passes the exact recipient through buzz messages send --mention;
  • rejection of a configured handoff fails visibly instead of retrying without the marker;
  • mixed messages can still neutralize an unrelated invalid mention without altering the configured handoff;
  • gateway and standalone/cron paths use the same behavior;
  • normalized-name collisions, malformed mappings, Unicode casefold ambiguity, and mention-prefix collisions fail closed or remain token-bounded.

This updates one statement in the original description: the PR now does add an optional config key, documented in cli-config.yaml.example and the Buzz guide.

Verification of the final implementation:

  • current integration tree: 93 Buzz adapter tests and 21 Buzz WebSocket tests passed;
  • broader Buzz-focused review set: 141 tests passed;
  • this PR's older base after cherry-pick: complete adapter file, 44 tests passed;
  • Ruff and git diff --check passed;
  • final independent security/logic review passed with no blockers.

@spfcraze

spfcraze commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary:
The configured-handoff protection stops at send_image(): its fallback call omits the protected-mention set, so the guide's "never silently downgraded" guarantee does not hold for image captions.

Problems:

  • plugins/platforms/buzz/adapter.py send_image() calls _unresolved_mention_fallback(send_content, err) — the only fallback call without configured_handoffs, which send() and _standalone_send() both pass. On the "does not match a current channel member" error the caption retry strips the marker exactly as it would for an unconfigured name.
  • The Buzz guide (website/docs/user-guide/messaging/buzz.md) states configured handoffs are "never silently downgraded to plain text: if Buzz rejects one, delivery fails visibly", without a caption exception, while send() and _standalone_send() pass --mention for configured names and the caption path does not.

Solution:
Pass configured_handoffs to the caption fallback and add --mention routing for caption content as send() and _standalone_send() do, or narrow the guide's handoff bullet to text and standalone sends.


Checked against 8ec0c45 — the tip of fix/buzz-unresolved-mention-fallback when this was written — and 70d1652, main at the moment this comment was posted.

@alt-glitch alt-glitch removed the comp/gateway Gateway runner, session dispatch, delivery label Aug 9, 2026
@reinhold-ph
reinhold-ph force-pushed the fix/buzz-unresolved-mention-fallback branch from 8ec0c45 to c0b8d94 Compare August 9, 2026 10:52
@cmyk

cmyk commented Aug 9, 2026

Copy link
Copy Markdown
Author

Addressed the configured-handoff image-caption gap reported in #77647 (comment) at exact head c0b8d949ebb6578b387afbdf46bec4c656d2ef5f.

The local-image caption path now matches the existing text and standalone/cron paths:

  • captions containing a configured @Name pass the mapped pubkey through buzz messages send --mention <pubkey> while retaining the visible marker;
  • Buzz rejection of that configured handoff fails visibly instead of retrying after stripping the marker;
  • a mixed caption may still neutralize an unrelated unresolved mention while preserving the configured marker and structural recipient;
  • ordinary unconfigured caption mentions retain the existing readable-text fallback.

Regression coverage proves exact --mention propagation, fail-visible behavior, and mixed configured/incidental mention handling. This follow-up was developed RED → GREEN: the structural-routing test first failed because --mention was absent, and the fail-visible test first observed the existing second call with the configured marker stripped.

Verification on the rebased current-upstream branch:

  • canonical adapter runner: 47 passed, 0 failed;
  • combined Buzz adapter/WebSocket suite: 51 passed, 0 failed;
  • full gateway suite: 5,088 passed and 7 unrelated failures; the identical suite with this follow-up stashed produced the same seven failure identities;
  • Ruff, py_compile, git diff --check, merge-tree validation, and staged secret scan passed;
  • final independent security/logic review passed with no findings or remaining suggestions.

@teknium1

Copy link
Copy Markdown
Collaborator

Status after the Buzz consolidation: the base fix (deliver despite unresolved @mentions) landed via #99431's recovery ladder. Surviving half: configured-handoff protection (extra.outbound_mention_pubkeys mapping names→exact pubkeys so intentional handoffs fail visibly instead of silently downgrading) — not on main. Worth a slimmed rebase to just that half.

@reinhold-ph
reinhold-ph force-pushed the fix/buzz-unresolved-mention-fallback branch from c0b8d94 to d4d76f7 Compare August 31, 2026 21:30
@alt-glitch alt-glitch added area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 31, 2026
@cmyk cmyk changed the title fix(buzz): deliver messages with unresolved mentions fix(buzz): preserve configured agent handoffs Sep 1, 2026
@alt-glitch alt-glitch added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state and removed sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 1, 2026
Squashed and rebased onto current main (PR NousResearch#77647). Configured
outbound_mention_pubkeys map display names to exact identities; a complete
@name marker for a configured identity is always published as a structured
--mention, is never escaped or downgraded by the unresolved-mention recovery
ladder, and forces a fresh final send instead of an edit so the p-tag
survives streaming.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants