Skip to content

fix(buzz): require an @ in the mention gate, keep DM classification loose (#78798) - #78869

Closed
thelonewander3r wants to merge 1 commit into
NousResearch:mainfrom
thelonewander3r:fix/buzz-mention-gate-requires-at-sign
Closed

thelonewander3r wants to merge 1 commit into
NousResearch:mainfrom
thelonewander3r:fix/buzz-mention-gate-requires-at-sign

Conversation

@thelonewander3r

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #78798 — with require_mention enabled, the Buzz adapter wakes the agent for a full turn whenever its display name appears as a bare word. Talking about an agent in a shared channel is enough to summon it.

Root cause

BuzzAdapter._is_mentioned() built the display-name pattern with an optional @:

pattern = rf"(?<!\w)@?{re.escape(self._display_name.lower())}(?!\w)"
#                     ^^

Driving the real predicate, 6 of 7 unaddressed messages wake the agent:

bare name              waiting on Chip                      WAKES  <-- bug
third person           the Chip migration is done           WAKES  <-- bug
possessive             Chip's config looks off              WAKES  <-- bug
lowercase bare         did chip finish that?                WAKES  <-- bug
status post            Today: shipped it, Chip handled …    WAKES  <-- bug
email-ish              ping bob@Chip for access             WAKES  <-- bug
trailing word char     @Chipmunk is a different bot         quiet

In an active shared channel, agents get discussed in the third person constantly — status updates, handoffs, retrospectives. Every one of those is an uninvited full turn: model calls, tool calls, context. The reporter measured a single passing mention consuming 21+ API calls and ~148k tokens before it was interrupted.

It is also the opposite of what the setting promises. require_mention: true reads as "only respond when addressed"; the implementation was closer to "respond when named".

Why this isn't the one-character fix the issue suggests

The issue proposes making @ required — a one-character change. That is not safe on its own, because _is_mentioned() has two callers that want opposite things:

caller question it asks wants
the wake gate (_poll_channel) "was I addressed?" strict
_is_direct_message_event() "is this p tag explained by something visible in the text?" loose

The DM discriminator returns p_tagged_to_self and not self._is_mentioned(content). Tighten the shared predicate and a p-tagged bare-name channel post starts reading as structural DM addressing — _maybe_latch_dm() then latches the whole conversation to chat_type="dm" permanently, and from that point every message in it dispatches with no mention gate at all.

That is strictly worse than the reported bug: wakes-on-name becomes wakes-on-everything. Measured on a metadata-less conversation, with the naive shared-predicate change applied:

variant          content                     wakes?    latches channel as DM?
-----------------------------------------------------------------------------
current          waiting on Chip             True      no
naive fix        waiting on Chip             False     YES  <-- gate disabled
naive fix        @Chip please look           True      no

In fairness to the reported scope: _may_reclassify_as_dm() already protects conversations whose channels list metadata looks like a real channel, and #77987 reports that Buzz Desktop only p-tags typed mentions (which carry a literal @Name). So the window is metadata-less conversations reached by a client that p-tags on a bare-name mention. #77987 is itself an open report that the p-tag assumption doesn't hold uniformly across clients, which is exactly when this would bite — cheap to close off rather than rely on.

The fix

Split the predicate. _matches_self(content, require_at=...) holds the shared body; the two callers each get the rule they need:

  • _is_mentioned()require_at=True — the wake gate.
  • _content_references_self()require_at=False — DM classification, behaviour unchanged.

npub and hex branches stay loose in both: those are explicit identity strings that never appear in incidental prose.

_strip_mention() is deliberately untouched — it still strips a leading bare name, so a DM that opens with "Chip /whoami" keeps working.

How to test

scripts/run_tests.sh tests/gateway/test_buzz_adapter.py -q

14 new tests pin the behaviour, and they distinguish all three states:

tree result
unmodified main 7 failed, 30 passed
naive shared-predicate fix 1 failed, 36 passed — test_ptagged_bare_name_post_does_not_latch_channel_as_dm
this PR 37 passed

The middle row is the point: the suite catches not just the original bug but the regression the obvious fix would introduce.

Coverage:

  • 7 parametrized cases that must not wake (bare name, third person, possessive, lowercase, status post, bob@Chip, @Chipmunk).
  • 6 that must still wake (@Chip, @chip, mid-sentence, markdown-wrapped, raw hex pubkey, npub).
  • 1 DM-classification guard: a p-tagged bare-name channel post must not latch the conversation as a DM, and the gate stays armed for the next message.

Test results

  • tests/gateway/test_buzz_adapter.py + test_buzz_websocket.py41 passed.
  • Wider tests/gateway/ sweep (-k "buzz or mention or dm") — 466 passed, 2 failed: test_feishu.py::TestGroupMentionAtAll::test_at_all_still_requires_policy_gate and test_slash_access_dispatch.py::test_admin_runs_quick_command_when_gating_enabled. Both fail identically on unmodified main — pre-existing, unrelated.
  • No callers of _is_mentioned exist outside this adapter.

Platform tested: Windows 11, Python 3.11.15, pytest 9.1.1 / pytest-asyncio 1.3.0. The change is pure string matching with no platform-specific behaviour.

Related

Same class as #70748 (Slack mention gating fails open), which is still open — a fix there would want the same "does this predicate have more than one caller?" check.

🤖 Generated with Claude Code

…oose

Fixes NousResearch#78798. Rebased onto current main; applies unchanged.

`require_mention` matched the bare display name, so any use of the
agent's name in prose woke it — "the Chip migration is done" dispatched
a turn. Buzz was the outlier among the connectors: Discord matches
`<@id>`, Slack `<@uid>`, Chatto `@login`/`@displayName`.

The single `_is_mentioned` helper was doing two jobs with one predicate.
They need opposite strictness:

- The mention gate must require a literal `@`. Picker-inserted mentions
  carry `@Name`, so UI-driven channel mentions still match.
- DM classification must stay loose. A p-tagged message whose text
  visibly addresses us — bare name or `@name` — is a channel reply, not
  structural DM addressing, so only a p-tag with no visible address
  latches.

`_matches_self(content, *, require_at)` carries both, with
`_is_mentioned` (strict) and `_content_references_self` (loose) reading
off it. `_has_dm_shaped_meta` additionally keeps relay-materialized DMs
(name "DM", empty description, NousResearch#68871) on the mention-free path.

Verified against current main: the 8 new assertions fail on unpatched
`origin/main` (cf64ca2) and pass with this diff; the diff is the only
variable. tests/gateway/test_buzz_adapter.py 38/38.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thelonewander3r
thelonewander3r force-pushed the fix/buzz-mention-gate-requires-at-sign branch from 543de2c to 2d8735c Compare August 17, 2026 13:19
@teknium1

Copy link
Copy Markdown
Collaborator

Closing with credit — the bare-name wake bug (#78798) was fixed via #99431 using #92781's composed @-required gate (which also handles the p-tag addressing half). Your independent diagnosis of the display-name matching problem was correct; thanks for the report-quality PR.

@teknium1 teknium1 closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Buzz mention gate matches the bare display name — require_mention wakes on any use of the agent's name

3 participants