Cross-owner agent mentions are impossible: candidates filtered by local ownership, and eligibility reads a kind (10100) nothing publishes
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Describe the bug**
An agent owned by one person can never appear in another person's `@` mention autocomplete, in any channel, under any configuration. `respond_to: anyone`, `respond_to: allowlist`, channel membership, and shared-channel overlap all have no effect.
There appear to be two independent causes, and the second makes the first unreachable.
**Cause 1 — the eligibility gate is unreachable for agents you don't own**
`useMentions.ts` applies three gates in order (v0.5.1 `desktop/src/features/agents/hooks/useMentions.ts:245-262`):
```ts
if (isArchivedDiscovery(pubkey)) return;
if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) return; // :249
if (shouldHideAgentFromMentions({…})) return; // :252
```
`isAgentIdentityInManagedList` (`lib/agentAutocompleteEligibility.ts:57-65`) keeps a candidate only if `isAgent !== true` **or** its pubkey is in `managedAgentPubkeys`. That set is built from `useManagedAgentsQuery` → the local `list_managed_agents` Tauri command, i.e. strictly the viewer's *own* agents — and `apply_inbound_managed_agent` deliberately refuses to mint one from a relay event (`commands/personas/inbound.rs:376-404`, *"managed agents carry device-local secrets and are never minted from a relay event"*).
Relay-agent candidates are added with `isAgent: true` unconditionally (`useMentions.ts:334-346`), and member candidates get `isAgent: true` whenever `profile?.isAgent === true` (`:318-323`), which for a kind:0 profile is derived as `owner_pubkey.is_some()` (`nostr_convert.rs:345`) — always true for an agent, since `sync_managed_agent_profile` always attaches a NIP-OA owner attestation (`relay.rs:401-455`).
So for any viewer who does not own the agent, the candidate is dropped at `:249` **before** `shouldHideAgentFromMentions` / `relayAgentIsSharedWithUser` ever run.
Typing the mention manually doesn't help either: `extractMentionPubkeys` (`:794-831`) resolves `p` tags from `mentionMapRef` plus the *post-filter* `mentionCandidates`, so no `p` tag is emitted for a filtered-out agent.
**Cause 2 — `relayAgentIsSharedWithUser` reads an event kind nothing publishes**
Even if Cause 1 is fixed, the eligibility logic is inert. `relayAgentIsSharedWithUser` (`lib/agentAutocompleteEligibility.ts:12-31`) reads `channelIds` / `respondTo` / `respondToAllowlist` from a `RelayAgent`, which comes from `list_relay_agents` (`desktop/src-tauri/src/commands/agent_discovery.rs:1057-1074`) — a query for exactly `[{"kinds":[10100]}]`.
Nothing in the repo publishes a kind:10100 *agent profile*. The only 10100 writer is `buzz channels set-add-policy` (`crates/buzz-cli/src/commands/channels.rs:1004-1038`), whose content is `{"channel_add_policy": …}`. Agent identity is published as **kind:0** plus **kind:30177** (`managed_agents/agent_events.rs`), and the persona catalog as **kind:30175**. The eligibility path never consults 30177.
Consequently `agents_from_events` (`nostr_convert.rs:445-496`) defaults `channel_ids: []` (`:469-480`) and leaves `respond_to` absent → `respondTo: null` (`shared/api/tauri.ts:686-695`), so `relayAgentIsSharedWithUser` returns `false` for every agent and every viewer.
**Secondary effect: publishing a 10100 makes things worse.** `directoryAgentPubkeys` is *every* pubkey in `relayAgentsQuery.data`, with no eligibility filter (`useMentions.ts:182-190`), and for a channel member `shouldHideAgentFromMentions` ends in `return directoryAgentPubkeys.has(normalized)` (`agentAutocompleteEligibility.ts:85-97`). So a 10100 that doesn't satisfy `relayAgentIsSharedWithUser` converts "unknown invocability ⇒ show" into "explicit exclusion ⇒ hide", removing an agent that channel members could previously see. Note also that 10100 is replaceable-by-pubkey, so publishing one destroys that agent's existing `channel_add_policy` event.
**Steps to reproduce**
1. User A creates an agent, sets **Responds to: Anyone**, and adds it as a member of a channel shared with user B.
2. Confirm on the relay that the agent's kind:30177 content has `"respond_to":"anyone"` (visible in `agents/retention/<64hex>.db`, table `persona_events`).
3. As user B, in that shared channel, type `@` followed by the agent's exact name.
4. The agent never appears in the autocomplete — no entry, and no "not in channel" label.
Same result with **Responds to: Allowlist** containing user B's 64-char hex pubkey.
**Expected behavior**
An agent whose owner has set `respond_to: anyone` (or an allowlist including the viewer) and which shares a channel with the viewer should be selectable in that viewer's mention autocomplete — which is what `relayAgentIsSharedWithUser` and the `respond_to` setting both clearly intend.
**Version and platform**
- Buzz version: 0.5.1 (verified unchanged in 0.5.2 — `agentAutocompleteEligibility.ts`, `agent_discovery.rs`, `agent_events.rs` are all identical between the two tags)
- OS: macOS 15 (Darwin 25.4.0), Apple Silicon
**Logs / additional context**
The agent-side gate is configured correctly and is not the problem: `record.respond_to` reaches the harness as `--respond-to` at spawn (`managed_agents/runtime.rs:380-392`), and `author_allowed` admits any author under `RespondTo::Anyone` for non-DM channels (`crates/buzz-acp/src/lib.rs:250`). The agent would answer; it simply cannot be addressed, because no `p` tag can be produced for it.
Possible fix directions (happy to open a PR if you can indicate a preference):
1. **Cause 1** — allow a candidate through `:249` when its pubkey is in `relayAgentsQuery` and it passes `relayAgentIsSharedWithUser`, rather than requiring local ownership; and relax the corresponding early-return in `extractMentionPubkeys` so the `p` tag is emitted.
2. **Cause 2**, option (a) — add a kind:10100 agent-profile publisher on the owner's desktop. Needs a new projection plus a source for `channel_ids`, which doesn't exist today.
3. **Cause 2**, option (b) — repoint `list_relay_agents` at kind:30177 (which already carries `respond_to` / `respond_to_allowlist`) and resolve channel membership with a kind:39002 `#p` query, the same one the harness already uses at `relay.rs:651-668`. This needs no relay change and seems the cleaner shape.
Workaround for anyone hitting this: skip mentions entirely by setting per-agent env `BUZZ_ACP_SUBSCRIBE=all` (plus `BUZZ_ACP_KINDS=9` and `BUZZ_ACP_CHANNELS=` to avoid wildcard kinds and unintended channels), which makes the agent answer messages in a scoped channel without requiring a `p` tag.
Contributor guide
Assessment
This issue has not been assessed yet.