Summary
Buzz Desktop decides which agents you can interact with from what is registered on that machine, not from what the relay says. A second desktop signed in as the same identity, in the same community, sees zero agents — no @ autocomplete, no member-list entries, no ability to start a DM — even though those agents are role=bot members of the channels it is displaying and are actively posting in them.
This is not just a missing autocomplete. The same filtered candidate list is what resolves mentions at send time, so typing an agent's name by hand does not work either: the message sends, renders with what looks like a mention, and carries no p tag, so the agent is never notified and never replies. There is no workaround inside a channel — the affected install simply cannot address an agent.
The mobile client does not have this limitation. It lists channel members directly, so the same account on a phone can mention the same agents without issue. The two clients implement the same feature with different rules, and mobile's implementation carries a comment claiming it mirrors desktop's.
Impact
A desktop install that does not itself host the agent processes is effectively read-only with respect to agents, and fails silently — the message posts normally and looks correct. Nothing in the UI indicates that no one was addressed.
This bites in the ordinary two-machine case (a laptop and a desktop on one identity) and there is no setting that fixes it. The only in-app workaround is a DM conversation that already exists, because open conversation lists are not filtered. New DMs cannot be created, since the recipient search applies the same gate.
Registering the agents locally on the second machine is not a workaround: creating an agent record mints a new keypair, producing a duplicate identity that is a different agent as far as the relay is concerned.
Root cause
Every agent candidate on desktop must pass isAgentIdentityInAllowedList against mentionableAgentPubkeys. Critically this gate is applied inside addCandidate, so it filters the candidate list itself — not merely the dropdown:
desktop/src/features/messages/lib/useMentions.ts:250-271
const mentionCandidates = React.useMemo<MentionCandidate[]>(() => {
const candidatesByPubkey = new Map<string, MentionCandidate>();
const addCandidate = (candidate: MentionCandidate & { pubkey: string }) => {
const pubkey = normalizePubkey(candidate.pubkey);
if (isArchivedDiscovery(pubkey)) return;
if (!isAgentIdentityInAllowedList(candidate, mentionableAgentPubkeys)) return;
...
That set is built from exactly two sources — desktop/src/features/agents/lib/agentAutocompleteEligibility.ts:47-82:
managedAgentPubkeys — agents registered locally on this machine
- relay agents holding a kind:10100 record whose
channel_ids includes the channel and whose respond_to permits the current user
Source 2 is empty in practice, because nothing publishes a kind:10100 agent directory record. Grepping every KIND_AGENT_PROFILE write site:
desktop/src-tauri/src/relay.rs:444-450 — sync_managed_agent_profile builds a kind:0 event (display name and avatar only)
crates/buzz-cli/src/commands/channels.rs:1004-1046 — buzz channels set-add-policy, the only kind:10100 writer, publishes {"channel_add_policy": "<policy>"} with no name, respond_to, or channel_ids
Since kind:10100 is replaceable, set-add-policy would in fact overwrite a richer record with a minimal one.
With source 2 empty, the rule reduces to: you can only address agents that run on the machine you are typing on.
Why typing the name by hand also fails
extractMentionPubkeys is what populates the outgoing event's p tags, and it resolves names against mentionCandidates — the list the gate above already emptied:
desktop/src/features/messages/lib/useMentions.ts:794-829
for (const candidate of mentionCandidates) {
if (!candidate.pubkey) continue;
if (!candidate.isMember) continue;
...
if (name && hasMention(text, name)) {
pubkeys.push(candidate.pubkey);
}
}
The other source it consults, mentionMapRef, is populated only when a picker item is selected — which is impossible when the picker is empty. So on the affected machine both resolution paths are dead and the p tag is never emitted.
Surfaces affected
| Surface |
File |
Line |
@ mention autocomplete |
desktop/src/features/messages/lib/useMentions.ts |
258 |
Mention resolution at send time (p tags) |
desktop/src/features/messages/lib/useMentions.ts |
794-829 |
| Channel members sidebar |
desktop/src/features/channels/ui/MembersSidebar.tsx |
297 |
| New-DM recipient search |
desktop/src/features/messages/ui/useNewMessageRecipients.ts |
131-132 |
DMs use a different scope
useMentions.ts:197-206 uses channel scope only when the channel type is stream or forum; DMs get { type: "managed-only" }, which rejects relay agents unconditionally. So even if kind:10100 records were published, they could never fix mentions inside a DM.
Mobile does it differently
mobile/lib/features/channels/mentions/mention_candidates.dart:59-79 adds every channel member unconditionally — no allowlist gate, no kind:10100 requirement:
for (final member in members) {
final pk = member.pubkey.toLowerCase();
if (!seen.add(pk)) continue;
final profile = userCache[pk];
final ownerPubkey = ownerByAgentPubkey[pk] ?? profile?.ownerPubkey;
final isAgent = member.isBot || ownerPubkey != null;
candidates.add(MentionCandidate(... isMember: true, role: member.role));
}
Mobile applies an invocability check only to non-member agents, and its comment at mention_candidates.dart:118-121 states it "Mirrors desktop's shouldHideAgentFromMentions for non-member agents." Desktop applies its gate to members too, so the behaviours diverge for exactly the common case.
mobile/lib/features/pulse/pulse_provider.dart:110-128 additionally falls back to the relay member list (kind:13534, role=bot) with the comment "This catches managed agents that may not have published a kind:10100 profile event yet" — acknowledging in-tree that 10100 records are frequently absent. Desktop has no equivalent fallback.
Probable dead code
Because the isAgentIdentityInAllowedList gate at useMentions.ts:258 already guarantees membership in mentionableAgentPubkeys, the subsequent shouldHideAgentFromMentions call can never return true for an agent — its first branch is if (mentionableAgentPubkeys.has(normalized)) return false. Its documented "member with unknown invocability => show" behaviour (agentAutocompleteEligibility.ts:95-125) is therefore unreachable. That comment suggests the intended behaviour is closer to mobile's, and the earlier hard gate defeats it.
Steps to reproduce
- Set up a community with an agent whose harness runs on machine A, and add it to a
stream channel with role=bot
- Install Buzz Desktop on machine B and sign in with the same identity
- Open the shared channel on machine B — the agent's messages are visible, and it is a member
- Type
@ in the composer → the agent is absent from autocomplete
- Type the agent's display name manually and send
Expected: the agent appears in autocomplete, as it does on machine A and on mobile; and a typed mention resolves to a p tag.
Actual: the agent is absent from autocomplete, absent from the members sidebar, and cannot be selected as a DM recipient. The message in step 5 sends successfully with no p tag, so the agent is never triggered and never responds. No error or warning is shown.
Verified against the relay: the agent is still role=bot in the channel throughout, and the sent event carries only [["h","<channel-uuid>"]].
Suggested fix
Preferred — align desktop with mobile: treat channel membership as sufficient for an agent that is a member of the current channel, and reserve the invocability gate for non-member agents. This appears to be what shouldHideAgentFromMentions already intends; removing or narrowing the isAgentIdentityInAllowedList call at useMentions.ts:258 would let that logic run. The same change is needed at MembersSidebar.tsx:297 and useNewMessageRecipients.ts:131-132.
Alternative — actually publish kind:10100 records. If the directory is meant to be the source of truth, something has to write it. Today no code path does, so the field is load-bearing but never populated.
Independently of which is chosen: a mention that fails to resolve should not send silently. Warning the user that no recipient was addressed would have made this diagnosable in seconds rather than hours.
Environment
- macOS 15.4 (Darwin 25.4.0), Apple silicon
- Buzz Desktop 0.5.5 (source read at
main / 2b873cf, version 0.5.6)
- Hosted community on
communities.buzz.xyz
- Two desktops and one phone on a single identity; agents hosted on one desktop only
Filed by an agent on behalf of the affected user, who owns the two-desktop environment described above. All code references were read at 2b873cf and the relay-side behaviour was verified live.
Summary
Buzz Desktop decides which agents you can interact with from what is registered on that machine, not from what the relay says. A second desktop signed in as the same identity, in the same community, sees zero agents — no
@autocomplete, no member-list entries, no ability to start a DM — even though those agents arerole=botmembers of the channels it is displaying and are actively posting in them.This is not just a missing autocomplete. The same filtered candidate list is what resolves mentions at send time, so typing an agent's name by hand does not work either: the message sends, renders with what looks like a mention, and carries no
ptag, so the agent is never notified and never replies. There is no workaround inside a channel — the affected install simply cannot address an agent.The mobile client does not have this limitation. It lists channel members directly, so the same account on a phone can mention the same agents without issue. The two clients implement the same feature with different rules, and mobile's implementation carries a comment claiming it mirrors desktop's.
Impact
A desktop install that does not itself host the agent processes is effectively read-only with respect to agents, and fails silently — the message posts normally and looks correct. Nothing in the UI indicates that no one was addressed.
This bites in the ordinary two-machine case (a laptop and a desktop on one identity) and there is no setting that fixes it. The only in-app workaround is a DM conversation that already exists, because open conversation lists are not filtered. New DMs cannot be created, since the recipient search applies the same gate.
Registering the agents locally on the second machine is not a workaround: creating an agent record mints a new keypair, producing a duplicate identity that is a different agent as far as the relay is concerned.
Root cause
Every agent candidate on desktop must pass
isAgentIdentityInAllowedListagainstmentionableAgentPubkeys. Critically this gate is applied insideaddCandidate, so it filters the candidate list itself — not merely the dropdown:desktop/src/features/messages/lib/useMentions.ts:250-271That set is built from exactly two sources —
desktop/src/features/agents/lib/agentAutocompleteEligibility.ts:47-82:managedAgentPubkeys— agents registered locally on this machinechannel_idsincludes the channel and whoserespond_topermits the current userSource 2 is empty in practice, because nothing publishes a kind:10100 agent directory record. Grepping every
KIND_AGENT_PROFILEwrite site:desktop/src-tauri/src/relay.rs:444-450—sync_managed_agent_profilebuilds a kind:0 event (display name and avatar only)crates/buzz-cli/src/commands/channels.rs:1004-1046—buzz channels set-add-policy, the only kind:10100 writer, publishes{"channel_add_policy": "<policy>"}with noname,respond_to, orchannel_idsSince kind:10100 is replaceable,
set-add-policywould in fact overwrite a richer record with a minimal one.With source 2 empty, the rule reduces to: you can only address agents that run on the machine you are typing on.
Why typing the name by hand also fails
extractMentionPubkeysis what populates the outgoing event'sptags, and it resolves names againstmentionCandidates— the list the gate above already emptied:desktop/src/features/messages/lib/useMentions.ts:794-829The other source it consults,
mentionMapRef, is populated only when a picker item is selected — which is impossible when the picker is empty. So on the affected machine both resolution paths are dead and theptag is never emitted.Surfaces affected
@mention autocompletedesktop/src/features/messages/lib/useMentions.tsptags)desktop/src/features/messages/lib/useMentions.tsdesktop/src/features/channels/ui/MembersSidebar.tsxdesktop/src/features/messages/ui/useNewMessageRecipients.tsDMs use a different scope
useMentions.ts:197-206uses channel scope only when the channel type isstreamorforum; DMs get{ type: "managed-only" }, which rejects relay agents unconditionally. So even if kind:10100 records were published, they could never fix mentions inside a DM.Mobile does it differently
mobile/lib/features/channels/mentions/mention_candidates.dart:59-79adds every channel member unconditionally — no allowlist gate, no kind:10100 requirement:Mobile applies an invocability check only to non-member agents, and its comment at
mention_candidates.dart:118-121states it "Mirrors desktop'sshouldHideAgentFromMentionsfor non-member agents." Desktop applies its gate to members too, so the behaviours diverge for exactly the common case.mobile/lib/features/pulse/pulse_provider.dart:110-128additionally falls back to the relay member list (kind:13534,role=bot) with the comment "This catches managed agents that may not have published a kind:10100 profile event yet" — acknowledging in-tree that 10100 records are frequently absent. Desktop has no equivalent fallback.Probable dead code
Because the
isAgentIdentityInAllowedListgate atuseMentions.ts:258already guarantees membership inmentionableAgentPubkeys, the subsequentshouldHideAgentFromMentionscall can never returntruefor an agent — its first branch isif (mentionableAgentPubkeys.has(normalized)) return false. Its documented "member with unknown invocability => show" behaviour (agentAutocompleteEligibility.ts:95-125) is therefore unreachable. That comment suggests the intended behaviour is closer to mobile's, and the earlier hard gate defeats it.Steps to reproduce
streamchannel withrole=bot@in the composer → the agent is absent from autocompleteExpected: the agent appears in autocomplete, as it does on machine A and on mobile; and a typed mention resolves to a
ptag.Actual: the agent is absent from autocomplete, absent from the members sidebar, and cannot be selected as a DM recipient. The message in step 5 sends successfully with no
ptag, so the agent is never triggered and never responds. No error or warning is shown.Verified against the relay: the agent is still
role=botin the channel throughout, and the sent event carries only[["h","<channel-uuid>"]].Suggested fix
Preferred — align desktop with mobile: treat channel membership as sufficient for an agent that is a member of the current channel, and reserve the invocability gate for non-member agents. This appears to be what
shouldHideAgentFromMentionsalready intends; removing or narrowing theisAgentIdentityInAllowedListcall atuseMentions.ts:258would let that logic run. The same change is needed atMembersSidebar.tsx:297anduseNewMessageRecipients.ts:131-132.Alternative — actually publish kind:10100 records. If the directory is meant to be the source of truth, something has to write it. Today no code path does, so the field is load-bearing but never populated.
Independently of which is chosen: a mention that fails to resolve should not send silently. Warning the user that no recipient was addressed would have made this diagnosable in seconds rather than hours.
Environment
main/2b873cf, version 0.5.6)communities.buzz.xyzFiled by an agent on behalf of the affected user, who owns the two-desktop environment described above. All code references were read at
2b873cfand the relay-side behaviour was verified live.