Skip to content

Mention picker lists locally-managed agents instead of channel members, so an agent running on another machine can never be @-mentioned #3204

Closed

Description

@cmcgrabby-hue

Describe the bug

The @ mention picker does not build its candidate list from the channel's members. It appears to be scoped to the agents that that Desktop install manages locally, which produces two symptoms at once in the same channel:

  1. It offers an agent that is not a member of the channel — selecting it sends a p tag to a pubkey the channel has never contained.
  2. It omits an agent that is a member of the channel — there is no way to mention it at all.

The practical consequence: an agent whose runtime lives on a different machine can never be @-mentioned. Its private key lives in install A's Desktop, so install B's picker will never list it, no matter that it is a bot member of the channel and actively posting in it. There is no setting to change this.

This is adjacent to #2423 (which lists "mention autocomplete should resolve only current channel members" under Expected behavior) and #2648 (which explains where the duplicate identity comes from), but neither demonstrates the candidate-source defect directly. The two-channel control below isolates it.

To Reproduce

  1. Install Buzz Desktop on machine A. Create an agent — call it Claude, pubkey bf0e814b… — and let it run there.
  2. Add bf0e814b… as a bot member of two channels, #alpha and #beta.
  3. Install Buzz Desktop on machine B, same community, same user. Per [Bug] Second Desktop install creates duplicate agent identities, inflating channel membership #2648 it mints its own agent identity — here 6285b97a…, whose relay profile name is the persona-template default Fizz. It is a bot member of #alpha only. It has never completed setup and cannot respond.
  4. On machine B, open #beta and type @claude.

Expected behavior

The picker offers Claude (bf0e814b…), the only agent member of #beta. If a locally-unmanaged agent cannot be mentioned for some architectural reason, the picker should say so rather than silently substituting a different pubkey.

Actual behavior

The picker offers Fizz — 6285b97a…, which is not a member of #beta — and does not offer Claude, which is. Accepting the suggestion sends a mention to an agent that is not in the channel and cannot reply.

Supporting material

Relay-side member lists, buzz channels members (channel UUIDs redacted, pubkeys truncated):

#beta   bf0e814b…  role=bot      <- Claude, machine A
        fc4907bc…  role=owner    <- human

#alpha  bf0e814b…  role=bot      <- Claude, machine A
        fc4907bc…  role=owner    <- human
        6285b97a…  role=bot      <- Fizz, machine B

6285b97a… does not appear in #beta's roster, yet it is what #beta's picker offers on machine B. bf0e814b… does appear, and is not offered. Membership cannot be the candidate source.

Profiles, buzz users get:

bf0e814b…  display_name: "Claude"
6285b97a…  display_name: "Fizz"

A secondary confusion, and probably the same root as #2423: machine B's Desktop agent list shows this agent as Claude (the user renamed it locally), while its kind-0 profile still says Fizz. The rename never republished the profile, so the sidebar and the picker disagree about the same keypair — which makes the wrong suggestion look like the right one until you compare pubkeys.

Suggested fix

  • Derive candidates from the channel's current member list, then intersect with (or annotate by) locally-managed agents — not the other way round.
  • Never emit a p tag for a pubkey that is not a member of the channel being posted to.
  • Disambiguate duplicates in the dropdown by relay profile name plus a pubkey prefix, since default persona names collide by construction ([Bug] Second Desktop install creates duplicate agent identities, inflating channel membership #2648).
  • Renaming an agent locally should republish its kind-0 profile, or the picker should render the local label so the two never diverge.

Environment

  • Buzz Desktop 0.4.26 (both machines), macOS (Darwin 25.5.0)
  • Self-hosted community, two Desktop installs, same user
  • Agent runtime: buzz-acp

Activity

  1. whotookmylogin commented on Jul 29, 2026

    @whotookmylogin

    Adjacent data point from Desktop 0.5.0 (macOS aarch64, self-hosted relay), which suggests the picker's candidate list and its membership check draw from different wrong sources:

    Setup: second Desktop install, same community, signed in with the owner key (import). The agent in question runs headless on another machine via the buzz-acp harness (OpenCode ACP adapter) — so it is not locally managed on either Desktop install — and is a bot member of one channel. Relay-side its pubkey is present in that channel's kind-39002 member event (two p tags: owner + agent), and the channel header's member count includes it.

    Observed in that channel's picker:

    1. The remote agent is offered (its profile name renders correctly) — so unlike the OP's repro, a locally-unmanaged agent can appear as a candidate here. Possibly relevant difference: it has a published kind-0 profile and was never created through Desktop's persona flow, so there is no locally-minted duplicate identity to shadow it (cf. [Bug] Second Desktop install creates duplicate agent identities, inflating channel membership #2648).
    2. But it is labeled "not in channel" — despite being in the 39002 list and counted in the header. So whatever the badge consults, it is not the channel's member event.
    3. Selecting the picker entry anyway works end-to-end: correct p tag, agent wakes and replies. The badge is purely cosmetic in this configuration.
    4. Typing @name as plain text without accepting the popup delivers no p tag and the agent never wakes — same footgun as Mobile: DM omits recipient p-tag unless a mention is picked, so agents are never woken #2835, made worse here because the misleading badge discourages users from accepting the popup entry that would work.

    So on 0.5.0 there appear to be two separable defects: the candidate list scoped to locally-managed agents (OP's case), and the membership badge checking the wrong source even when the candidate list is right (this case).

  2. matthewdcage commented on Jul 29, 2026

    @matthewdcage

    Additional data point — duplicates from team deploy worsen picker ambiguity

    Your two-channel control isolates the candidate-source bug clearly (local managed list vs channel members). Adding one related mint path still on upstream main that inflates same-name pubkeys on a single install:

    #3639 — AddTeamToChannelDialog.tsx sets forceNewInstance: true on every Deploy team to channel persona, bypassing findReusablePersonaAgent(). Repeated deploys across channels gave us ~24 in-channel bot pubkeys vs 12 running instances (Fizz ×4, etc.) before cleanup.

    That is the same default persona name collision class as #2648 but without a second machine — it makes the picker/membership confusion in your repro more likely.

    Scope boundary

    A fix for this issue still requires deriving mention candidates from channel members (as you and @whotookmylogin note — including the separate “not in channel” badge defect on 0.5.0). Removing forceNewInstance does not change useMentions.ts; it only stops minting new same-name pubkeys going forward.

    We are not duplicating the #2149 / isAgentIdentityInManagedList analysis already covered in #2508 — linking #3639 ↔ #3204 for triage.

  3. DongwonGim commented on Jul 30, 2026

    @DongwonGim

    Another self-hosted data point, and an interaction with #2857 that makes this a trap rather than an inconvenience

    Setup: self-hosted relay, five headless agents running under buzz-acp on a Linux server, none of them locally managed by any Desktop install. All five are bot members of the channels they serve and have published kind:0 profiles. Desktop on macOS. Same symptom as the OP, and as #2508.

    Line references below are against main 63496cc.

    1. The workaround for the duplicate-start bug is exactly what makes the agent unmentionable

    This is the part I have not seen stated anywhere, and it is what turns two separate defects into a dead end.

    With #2857 unmerged, the practical way to stop Desktop from double-answering an externally hosted identity is to keep that identity out of the local managed-agents.json. But isAgentIdentityInManagedList (useMentions.ts:249) makes presence in that same file the requirement for the identity to be mentionable at all.

    So for a self-hosted agent you can have:

    • local record present: mentionable, and Desktop starts a second copy of it on mention-send, so every mention gets answered twice by the same key, or
    • local record absent: answered exactly once, and no way to mention it

    There is no third option in a shipped build. We hit both sides of this within 48 hours.

    I can confirm the #2857 description directly. When we restored our five records with is_active: false and start_on_app_launch: false, Desktop still spawned one on mention-send. agents/agent-pids/<pubkey>.json was created at the moment of the mention, and the reply came from the Mac rather than the server. Neither auto-start path (restore.rs:168, runtime_commands.rs:471) is involved, since both filter on start_on_app_launch. It is the mention-send preflight, as #2857 says.

    2. Timeline, which may explain why some reporters attribute this to an app update

    Ours looked like a regression introduced by an update, and it was not. From backup file timestamps:

    Time managed-agents.json What happened
    07-27 12:04 10 entries, our 5 agents among them mentions working
    07-27 12:07 3 entries (built-in demo personas only) we cleared it to stop duplicate replies
    07-27 to 07-29 file has 3, running app still behaves as if it has 10 mentions kept working for about 36 hours
    07-29 00:05 app auto-updated, so it restarted re-read the cleared file
    07-29 agents gone from @

    The running app apparently keeps the managed list from process start, so editing the file has no visible effect until the next launch. That delay makes the eventual breakage land on whatever restart happens to come next, which is usually an update. Worth keeping in mind when triaging reports that say "broke after upgrading to X".

    For the record, the gate is not new: it arrived in #2149 (a4dfead2, 2026-07-20) and first shipped in v0.4.20 the same day. I checked each tag from v0.4.10 through v0.5.2, and every release from v0.4.20 onward contains it at useMentions.ts:249. Upgrading does not help.

    3. role != "bot" is not a workaround, because NIP-OA alone trips the gate

    The existing reports focus on the role == "bot" path. There is a second, independent one that matters for anyone following the NIP-OA owner attestation guidance:

    // nostr_convert.rs:345, users_batch_from_events
    is_agent: owner_pubkey.is_some(),   // owner_pubkey = profile_valid_oa_owner_pubkey(kind:0)

    So any identity whose kind:0 carries a valid auth tag is isAgent: true in the picker, regardless of channel role. Our five agents are in this state and the relay has verified the tags (users.agent_owner_pubkey is populated for all five).

    The practical consequence: attaching an owner attestation, which is the documented way to make the app show who owns an agent, is also sufficient on its own to remove that agent from the mention picker. Dropping role=bot does not recover it. You would have to also drop the auth tag, at which point the app shows owner unavailable instead. Owner attribution and mentionability are currently mutually exclusive for an externally hosted agent.

    4. All four candidate sources funnel through the same gate

    addCandidate (useMentions.ts:244) is the only way into the candidate map, and the gate is its first statement. The four producers are:

    Producer how isAgent is set effect for a remote agent
    channel members (:295) role=="bot" || profile.isAgent || ... dropped
    relay directory kind:10100 (:334) hardcoded true dropped
    local managed list (:349) hardcoded true the only path through
    global user search (:365) user.isAgent || ... dropped

    Two things follow. First, typing the full name to force a global search does not surface it either, so there is no user-side escape hatch. Second, the relay-directory branch at :334 is unreachable in practice: it inserts candidates with isAgent: true, which the gate then drops unless the same pubkey is also in the local file. That in turn makes getMentionableAgentPubkeys's relay branch, directoryAgentPubkeys, and the shouldHideAgentFromMentions "Option B" logic dead for exactly the population they were written for. The comment in that function states the intent:

    Member (Option B): hide only when we have an explicit not-invocable signal, a relay directory (kind:10100) entry that excludes us. Unknown invocability (not in directory) => show.

    "Not in directory => show" is the stated intent, and the preceding gate implements "not in local managed list => hide".

    5. Two adjacent observations while in this code

    relayAgentIsSharedWithUser has no owner-only branch. It returns true only for allowlist containing the caller, or anyone plus a shared channel. owner-only is a valid value of the enum (RelayAgentInfo) and is the setting crates/buzz-acp/README.md recommends when first attaching an agent, so those agents can never reach mentionableAgentPubkeys even once the gate above is fixed.

    Nothing in-tree publishes kind:10100 except one CLI subcommand. The only writer I can find is cmd_set_add_policy in crates/buzz-cli/src/commands/channels.rs, which emits {"channel_add_policy": policy} and nothing else. buzz-acp and buzz-desktop contain no reference to channel_add_policy at all. On our relay the directory is empty (0 events of kind 10100), which is consistent with there being no publisher for an agent that was not created through Desktop. If the relay directory is meant to be the discovery source for externally hosted agents, something needs to populate it, and the harness looks like the natural place.

    6. Workaround that does not depend on Desktop, for anyone else stuck here

    Since the blocked step is the client attaching the p tag, one option is to stop requiring the p tag on the agent side. buzz-acp config mode filters can read the message body:

    # rules/Digger.toml, used with: buzz-acp --subscribe config --config rules/Digger.toml
    [[rules]]
    name = "digger-mention"
    channels = "all"
    kinds = [9]
    require_mention = false
    filter = 'str_contains(content, "@Digger") || str_contains(content, "@digger")'

    Verified on our relay. The sent event carried no p tag at all and the agent still woke and replied:

    01:16:23  owner   "@Digger 테스트다 한줄만입력해"   tags = [["h", "<channel>"]]
    01:16:31  Digger  "테스트 확인, 여기 잘 받음"
    

    --respond-to owner-only is a separate author gate and still applies, so this does not open the agent up to everyone.

    Two notes on it. Desktop puts the literal @Name text in the body even when the mention was picked from the popup, so a single rule covers both the picked case and the typed case. And this also sidesteps the footgun in #2835 and in @whotookmylogin's comment above, where typing @name as plain text produces no p tag and the agent never wakes. The cost is that the subscription widens from mentions to all kind:9 in the agent's channels, which is not free on a busy relay.

    This is a harness-side workaround, not a fix. The candidate-source change the OP proposes is still the right one.

    Environment

    • Buzz Desktop 0.5.0 then 0.5.2, macOS (Darwin 24.6.0)
    • Self-hosted single-community relay, ghcr.io/block/buzz:main
    • Five agents under buzz-acp with claude-agent-acp, --respond-to owner-only, on a separate Linux host
    • Relay side confirmed by direct Postgres queries against events, channel_members, and users
  4. motox23 commented on Jul 30, 2026

    @motox23

    Additional data point from Buzz Desktop v0.5.2 on two Macs using the same owner identity:

    On the secondary Mac, managed-agents.json contains seven account-synced definitions whose pubkey value is an empty string:

    Wrench
    Scooter
    Bumble
    Fizz
    Honey
    Enduro
    Revv
    

    The only record on that Mac with a non-empty local pubkey is a separate laptop-local agent named MBA (0dabb74e…52c12).

    In a channel where the relay roster confirms Wrench (686b3e44…967e) and Bumble (7cba9463…0405) are bot members, typing @ on the secondary Mac shows the synced Wrench and Bumble definitions as “agent not in channel.” It does not offer the actual channel-member identities.

    This suggests the picker is admitting the account-synced definition rows even though they carry no usable pubkey, while the real remote member pubkeys are filtered out. An empty string is distinct from a missing/null value, so any eligibility or deduplication check that only tests for nullish pubkey may allow these bodyless rows through.

    We did not select one of those rows because that could enter the duplicate-mint path described in #3414. Merely launching the secondary client did not change relay identity counts.

  5. daguiler76 commented on Jul 31, 2026

    @daguiler76

    Additional reproduction on Buzz Desktop 0.5.2, macOS, with a narrower
    control: this happens on a single Desktop installation even when the omitted
    agents are locally managed, running, and subscribed to the channel.

    Setup

    • One self-hosted community and one macOS Desktop installation.
    • A channel contains the owner plus three bot members: Grok, Claude, and
      Codex.
    • All three agents are managed and running on this same Desktop installation.
    • The relay member list and the channel header both include the agents.

    Actual behavior

    Opening @ in the channel composer:

    • omits Grok, Claude, and Codex, which are the actual channel-member instances;
    • offers account-synced persona rows such as Bumble, Fizz, and Honey;
    • labels those offered rows managed by you · not in channel.

    Typing @codex manually produces only visible text. The published kind:9 has
    the channel h tag but no Codex p tag, so buzz-acp is never triggered.

    The same Codex identity remains reachable through
    Channel members → Codex → DM, and the DM works. Restarting Desktop does not
    change the picker.

    Local-state control

    The installation's managed-agents.json has 16 records:

    • 8 persona-definition records with populated display_name and slug, but
      pubkey: "";
    • 8 running-instance records with populated pubkey and persona_id, but
      display_name: null and slug: null.

    Every running instance's persona_id matches its persona definition's slug.
    All eight running instances are missing display_name.

    For Codex specifically:

    • the instance is active and configured to start with Desktop;
    • there is no recorded startup error;
    • the harness log shows a clean relay connection and successful subscription
      to the affected channel;
    • the relay profile resolves correctly;
    • the identity is not archived.

    No full pubkeys, channel IDs, relay URLs, auth tags, or credentials are included
    here.

    Interpretation

    This looks like the picker is admitting persona-definition rows without first
    joining them to their running instances, while membership is keyed by the
    instance pubkey. The empty-string persona pubkeys then cannot match the channel
    roster, and the valid in-channel instances are omitted.

    The join available in the local state is:

    instance.persona_id == persona.slug
    

    Mention candidates should ultimately be keyed by the channel-member instance
    pubkey; the visible label can come from the joined persona display_name or
    the instance name.

    This is consistent with the secondary-Mac data point already reported here,
    but confirms the defect does not require a remote/headless agent or two
    Desktop installations. It also affects Desktop's own active local instances.

  6. rudymccomb commented on Aug 3, 2026

    @rudymccomb

    Another confirmed reproduction on Buzz Desktop v0.5.3, macOS, self-hosted relay, two Macs sharing the same owner identity (agents' bound records live on Mac A; Mac B is effectively a client).

    Relay-side state is fully correct — verified directly in the relay DB:

    • channel_members lists the agents as members of the channels in question, and the current kind:39002 replaceable events match exactly (agents tagged bot), e.g. one channel with just owner + one bot member.
    • The agent is online at the time of the repro (its buzz-acp runtime is up on Mac A, presence online) and actively posting in the same channel.

    Symptoms on Mac B, both from the same defect:

    1. In a channel where Mac B happens to have unbound local template records (pubkey: "") with the same display names, the picker offers those dead templates labeled "agent not in channel" — while the real, in-channel, online instances are absent (matches Mention picker fails to coalesce local persona definitions with their running channel-member instances #3947's coalescing angle).
    2. In a channel where Mac B has no local record of the member agent at all, typing @ + prefix yields no candidates whatsoever, despite that agent being one of the channel's two members and the newest poster in it.

    Our agents run respond_to=owner-only, so this also lines up with #4535's eligibility analysis (no owner-only branch, and kind:10100 cards never receive respond_to updates) — i.e. no user-facing setting can work around it. Mentioning works only from the machine that holds the bound records; thread replies are the only cross-device path to reach an agent.

    🤖 Generated with Claude Code

  7. Tasuku0127 commented on Aug 4, 2026

    @Tasuku0127

    Adding a concrete cross-client evidence point. In the same channel and thread, sending the identical typed text @general (fable) (an agent running only on a separate always-on host) from different clients produced different tags:

    • Buzz Desktop (machine with no locally-registered agents): the event had p=[] — no p tag, so the mention was inert plain text and the agent was never notified.
    • Mobile (same account, same text): the event had p=["864def9c…0810d63"] (the agent's real pubkey) — a real mention that delivered.

    So mobile/web already resolve the picker against channel membership; only Desktop scopes candidates to locally-managed agents. This makes the "agents on a 24/7 exec host + human opens Desktop on a second machine" setup silently broken, with no error or fallback. +1 to the suggested fix: derive candidates from the channel member list and never emit a p tag for a pubkey that is not a member.

  8. eireal commented on Aug 4, 2026

    @eireal

    Another reproduction on Buzz Desktop 0.5.3, macOS + Windows 11, two machines under the same owner identity, two self-hosted communities (leevia. and knowhow.communities.buzz.xyz). Relay-side state is correct; the defect is entirely local. Two data points I have not seen in this thread yet.

    1. The local agent registry stores synced agents with an empty pubkey

    ~/Library/Application Support/xyz.block.buzz.app/agents/managed-agents.json on the Mac holds 13 records. The 8 created on this machine carry a real pubkey (and, for community-bound ones, a relay_url). The 5 that arrived by sync from the Windows machine carry "pubkey": "" and "relay_url": "":

    'Bumble'      | pubkey=''              | relay_url=''
    'Fizz PC'     | pubkey=''              | relay_url=''
    'Honey PC'    | pubkey=''              | relay_url=''
    'Oracle'      | pubkey=''              | relay_url=''
    'Wally PC'    | pubkey=''              | relay_url=''
    'Bumble Mac'  | pubkey='0bb9f1084e8b…' | relay_url='wss://knowhow.communities.buzz.xyz'
    'Bumble Mac'  | pubkey='f74495db3d4f…' | relay_url='wss://leevia.communities.buzz.xyz'
    'Fizz Mac'    | pubkey='9dd15fbb9855…' | relay_url='wss://knowhow.communities.buzz.xyz'
    'Fizz Mac'    | pubkey='bf667ab3b0cf…' | relay_url='wss://leevia.communities.buzz.xyz'
    'Honey Mac'   | pubkey='56769d9e5f9f…' | relay_url='wss://knowhow.communities.buzz.xyz'
    'Honey Mac'   | pubkey='612eeaae8570…' | relay_url='wss://leevia.communities.buzz.xyz'
    'Nemo Mac'    | pubkey='758f820fc293…' | relay_url=''
    'Oracle'      | pubkey='0b116b16909e…' | relay_url=''
    

    So multi-device sync propagates the agent definition but not its key. If the picker is sourced from this file — as the title of this issue states — then on each machine the remote agents are present by name but unmentionable by construction: there is no pubkey to attach. This is symmetric, and it explains why the failure is total rather than intermittent.

    Relay-side, by contrast, buzz channels members lists every one of those agents with role=bot in the channels concerned. Nothing is missing on the relay.

    2. The nostr:npub… escape hatch does not work either

    The obvious workaround — bypass the picker and paste the bech32 URI into the composer — also produces an inert message. Sent from Buzz Desktop into a channel where the target agent is a bot member:

    content: **nostr:npub1wk8cyr7zj0k397lgk645c43yk0try7d6stk40daryqnlvqkrgjcqn9nzzs te adjunto…
    tags:    [["h","<channel-uuid>"],["e","33a4113b…","","reply"]]
    

    Event c05c168fe4efd22d75cb8dd122daf535b6412dfc40071dcaaf6a53dbae6bcf38, 2026-08-04T14:05:44Z. No p tag. The composer does not resolve a pasted nostr:npub… URI into a mention, so the agent was never woken. (The URI was inside **…** bold markers here; I have not isolated whether unwrapped text behaves differently.)

    The practical consequence for a two-machine owner is that there is no path from the client machine to an agent hosted on the other one: not the picker, not a pasted npub. The only channel that still works is a DM, where the recipient p tag is added automatically regardless of the picker.

  9. zacklavin11 commented on Aug 6, 2026

    @zacklavin11

    Another confirmed reproduction, macOS, hosted community relay.

    Setup: whole agent fleet (6 agents) migrated from Desktop-managed on machine A (MacBook, Desktop-spawned seats) to a headless Mac mini running buzz-acp under a separate user account — same keypairs transplanted, so relay-side nothing changed: every agent still has its kind-0 profile and is a bot member of the channels concerned (buzz channels members confirms), all actively replying.

    Result on the original machine's Desktop: the @ picker now offers no agents at all in any channel — the local managed-agents store no longer holds them, so they are dropped before eligibility runs, exactly as described above. Attempted composer sends reach the relay with p=[].

    What still works, consistent with the other reports here:

    • DMs deliver (recipient p-tag added automatically, picker not involved).
    • buzz-cli messages send --mention <pubkey> delivers with a correct receipt.
    • Channel membership is not the issue — relay state verified correct throughout.

    +1 to deriving picker candidates from the channel's member list and intersecting/annotating with the local store, rather than gating on it. For a multi-machine or headless-fleet setup there is currently no Desktop path to mention a channel-member agent.

    🤖 Generated with Claude Code

  10. added a commit that references this issue on Aug 17, 2026
    f716eef
  11. added a commit that references this issue on Aug 18, 2026
    bcf6982
  12. added a commit that references this issue on Aug 23, 2026
    93986ae
  13. added a commit that references this issue on Oct 7, 2026
    bc20497
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      Sponsor
      SponsoredKunjungi sekarang
      Promo