Desktop: agents owned by another user are never mentionable — the managed-list guard in useMentions dead-codes shouldHideAgentFromMentions
Summary
On Buzz Desktop, an agent deployed by user A is invisible in user B's @-mention autocomplete — even when the agent is a member of the same channel and B is on the agent's respond_to allowlist. Typing the name by hand doesn't help either: the message is sent without a p tag, so the agent's harness never triggers.
This makes cross-user agent collaboration (the "agents are members, not bots" story from the README) work on mobile but not on desktop.
Root cause
In desktop/src/features/messages/lib/useMentions.ts, addCandidate runs two gates in sequence:
if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) return; // gate 1
if (shouldHideAgentFromMentions({ ..., mentionableAgentPubkeys, ... })) return; // gate 2
- Gate 1 drops every
isAgent candidate whose pubkey is not in the local managed-agent list (managed-agents.json). On any install that doesn't manage agents itself, this is the empty set — every agent candidate dies here, including channel members with role bot.
- Gate 2 (
shouldHideAgentFromMentions) starts with if (mentionableAgentPubkeys.has(normalized)) return false; // Invocable => always show.
getMentionableAgentPubkeys seeds its result with managedAgentPubkeys (agentAutocompleteEligibility.ts), so managedAgentPubkeys ⊆ mentionableAgentPubkeys always holds.
⟹ Every agent candidate that survives gate 1 is already "invocable" to gate 2, which therefore returns false on its first line, always. All remaining branches of shouldHideAgentFromMentions — including the "Option B" member branch (lines 84–97) whose comment block explicitly specifies "Member: … Unknown invocability (not in directory) => show" — are unreachable from their only production call site. The policy documented there was designed for exactly this scenario, and gate 1 starves it.
Evidence this is a regression, not intent
- The mobile client is the witness.
mobile/lib/features/channels/mentions/mention_candidates.dart implements the same pipeline without the pre-gate (member bots are always candidates; agentIsSharedWithUser handles non-member directory agents), and its comments say "Mirrors desktop's shouldHideAgentFromMentions". The port codified desktop's intended semantics; desktop no longer executes them.
- Coverage has a hole exactly here. The e2e suite pins "relay-only (non-member) agents stay hidden" and "own managed agents are visible" — there is no test for channel-member agent owned by another user.
- The typed-mention fallback is also starved.
extractMentionPubkeys has an explicit loop resolving hand-typed @Name against channel members without autocomplete. It works for humans; for foreign agents, gate 1 has already removed them from mentionCandidates.
Net effect: on a desktop with an empty managed list, no agent is mentionable at all — not even one with respond_to: anyone. The client-side allowlist/anyone logic (relayAgentIsSharedWithUser, getMentionableAgentPubkeys) is unreachable ornament.
isAgentIdentityInManagedList has a legitimate job — its unit test says "keeps people and only current managed agent identities", i.e. dropping stale/ghost agent identities surfaced by discovery/search. The bug is only that it also swallows relay-confirmed channel members.
Fix
Exempt channel members from gate 1 so that gate 2 — which already encodes the correct member policy — actually decides:
if (
candidate.isMember !== true &&
!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)
) {
return;
}
Ghost-guard semantics are preserved for non-members (discovery, directory, global search), so the pinned "relay-only agents stay hidden" behavior is unchanged.
PR with the fix and an e2e regression test (channel-member bot + empty managed list ⇒ visible in @, mention resolves to a p tag on the wire): #TBD
Repro
- User A deploys an agent and adds it to a shared channel (member role
bot).
- User B (no locally managed agents) opens the channel on desktop and types
@.
- The agent is missing from the list; typing
@AgentName by hand sends a message without any p tag (verifiable in the event), so the agent never responds.
- Same account, same channel on mobile: the agent appears and the mention works.
Desktop: agents owned by another user are never mentionable — the managed-list guard in
useMentionsdead-codesshouldHideAgentFromMentionsSummary
On Buzz Desktop, an agent deployed by user A is invisible in user B's
@-mention autocomplete — even when the agent is a member of the same channel and B is on the agent'srespond_toallowlist. Typing the name by hand doesn't help either: the message is sent without aptag, so the agent's harness never triggers.This makes cross-user agent collaboration (the "agents are members, not bots" story from the README) work on mobile but not on desktop.
Root cause
In
desktop/src/features/messages/lib/useMentions.ts,addCandidateruns two gates in sequence:isAgentcandidate whose pubkey is not in the local managed-agent list (managed-agents.json). On any install that doesn't manage agents itself, this is the empty set — every agent candidate dies here, including channel members with rolebot.shouldHideAgentFromMentions) starts withif (mentionableAgentPubkeys.has(normalized)) return false; // Invocable => always show.getMentionableAgentPubkeysseeds its result withmanagedAgentPubkeys(agentAutocompleteEligibility.ts), somanagedAgentPubkeys ⊆ mentionableAgentPubkeysalways holds.⟹ Every agent candidate that survives gate 1 is already "invocable" to gate 2, which therefore returns
falseon its first line, always. All remaining branches ofshouldHideAgentFromMentions— including the "Option B" member branch (lines 84–97) whose comment block explicitly specifies "Member: … Unknown invocability (not in directory) => show" — are unreachable from their only production call site. The policy documented there was designed for exactly this scenario, and gate 1 starves it.Evidence this is a regression, not intent
mobile/lib/features/channels/mentions/mention_candidates.dartimplements the same pipeline without the pre-gate (member bots are always candidates;agentIsSharedWithUserhandles non-member directory agents), and its comments say "Mirrors desktop'sshouldHideAgentFromMentions". The port codified desktop's intended semantics; desktop no longer executes them.extractMentionPubkeyshas an explicit loop resolving hand-typed@Nameagainst channel members without autocomplete. It works for humans; for foreign agents, gate 1 has already removed them frommentionCandidates.Net effect: on a desktop with an empty managed list, no agent is mentionable at all — not even one with
respond_to: anyone. The client-side allowlist/anyone logic (relayAgentIsSharedWithUser,getMentionableAgentPubkeys) is unreachable ornament.isAgentIdentityInManagedListhas a legitimate job — its unit test says "keeps people and only current managed agent identities", i.e. dropping stale/ghost agent identities surfaced by discovery/search. The bug is only that it also swallows relay-confirmed channel members.Fix
Exempt channel members from gate 1 so that gate 2 — which already encodes the correct member policy — actually decides:
Ghost-guard semantics are preserved for non-members (discovery, directory, global search), so the pinned "relay-only agents stay hidden" behavior is unchanged.
PR with the fix and an e2e regression test (channel-member bot + empty managed list ⇒ visible in
@, mention resolves to aptag on the wire): #TBDRepro
bot).@.@AgentNameby hand sends a message without anyptag (verifiable in the event), so the agent never responds.