Skip to content

Desktop: a second install of the same identity cannot address agents at all — empty mention picker, hidden members, and typed mentions silently drop the p tag (mobile unaffected) #5264

Description

@agentbaltic

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:

  1. managedAgentPubkeys — agents registered locally on this machine
  2. 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-450sync_managed_agent_profile builds a kind:0 event (display name and avatar only)
  • crates/buzz-cli/src/commands/channels.rs:1004-1046buzz 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

  1. Set up a community with an agent whose harness runs on machine A, and add it to a stream channel with role=bot
  2. Install Buzz Desktop on machine B and sign in with the same identity
  3. Open the shared channel on machine B — the agent's messages are visible, and it is a member
  4. Type @ in the composer → the agent is absent from autocomplete
  5. 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.

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