fix(desktop): decide mention eligibility for external agents from relay proofs - #3676
Open
Shipitrealgood wants to merge 2 commits into
Open
fix(desktop): decide mention eligibility for external agents from relay proofs#3676Shipitrealgood wants to merge 2 commits into
Shipitrealgood wants to merge 2 commits into
Conversation
Owner-attested external agents (kind:0 with verified NIP-OA attestation + kind:10100 directory entry) could not be @-mentioned: the candidate gate dropped every agent-classified identity not present in the local managed store, before the directory eligibility logic could run — making that shipped logic unreachable. Externally-run agents were mentionable only while UNattested (misclassified as people); registering them correctly made them unreachable. Rewrite the mention gate as one predicate deciding "will this agent receive the message and does its policy admit responding to you", from the same proofs other agent surfaces already trust: - a local managed record (the native path — unchanged, including the non-member auto-add flow), or - channel membership (candidates originate from the member list) plus the agent's own kind:10100 respond-to declaration: `anyone`, an allowlist naming the viewer, or `owner-only` when the viewer IS the agent's NIP-OA-verified owner (the same verified ownerPubkey the "managed by" header renders). Liveness is deliberately not part of the predicate: buzz-acp replays missed mentions on reconnect, and stopped managed agents are offered today. Agent-classified candidates with no local record and no directory declaration stay hidden, preserving the no-void-mentions guarantee the old gate encoded. Offering non-member external agents (auto-add honoring the directory's channel_add_policy) is an explicit follow-up. Replaces the stacked isAgentIdentityInManagedList + directory-set checks in useMentions with the single predicate; isAgentIdentityInManagedList remains for its members-sidebar caller. Unit tests cover the full policy matrix (anyone / allowlist in+out / owner-only as owner, non-owner, unverified / no declaration / non-member / managed member+non-member / unknown viewer / pubkey normalization). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Bryce Del Rio <bryce.delrio@gmail.com>
Shipitrealgood
added a commit
to Shipitrealgood/buzz
that referenced
this pull request
Jul 30, 2026
…licy The add-member search dropped every agent-classified candidate not in the local managed store, and the relay's actual authority for third-party adds — the agent's kind:10100 channel_add_policy, enforced server-side in handlers/side_effects.rs (owner_only checked against the stored attested owner, nobody refused, anyone allowed) — was never consulted client-side: the field was not even carried through the RelayAgentInfo conversion. Carry channel_add_policy through the wire types and gate the picker on a predicate aligned with relay enforcement: managed agents always offered (native parity, unchanged); external agents offered when their declared policy is "anyone", or "owner_only" when the viewer is the NIP-OA-verified owner; "nobody" hidden from everyone including the owner (the relay refuses those adds regardless of actor). The picker never offers an external-agent add the relay will reject. In one direction it is deliberately STRICTER than the relay: agents with no directory entry stay hidden even though the relay — whose stored policy defaults to 'anyone' — would accept the add. That tightening preserves the stale-identity suppression the managed-list gate was introduced for (block#2149): no declaration, no offer. Complements the mention-eligibility fix (block#3676): each surface reads its own declaration — respond_to for mentions, channel_add_policy for adds. Unit tests cover the matrix: people / managed / anyone / owner_only as owner, non-owner, unverified / nobody incl. owner / no entry / null policy / pubkey normalization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Bryce Del Rio <bryce.delrio@gmail.com>
…d owner Community feedback on block#2987 surfaced two subcases the predicate left cold: an attested external agent with no kind:10100 at all (never published, or deleted), and — after the block#2508 thread's cross-machine reports — the agent's own owner on a device that doesn't manage it (block#2349, block#3277). Both reduce to the same case: a member agent with a verified NIP-OA attestation and no directory declaration. Offer that candidate to its verified owner only. This is proof-sound: the harness's inbound author gate admits the owner under every respond-to mode, so an owner's mention is never a void chip — and it needs no directory entry, which matters because nothing publishes entries for native agents yet. Strangers still see nothing: attribution without a declaration is not evidence the agent would answer them. Stale same-name identities an owner might now see fold into the live one via the existing same-label/same-owner coalescing, and NIP-IA archived identities are peeled before candidates are built — the two block#2149-era layers this change leans on, both untouched. Tests: declaration-less agent hidden from strangers and unverified attributions, offered to the verified owner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Bryce Del Rio <bryce.delrio@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Owner-attested external agents cannot be @-mentioned. The mention
candidate gate (added in #2149) drops every agent-classified identity
that is not in the local managed store — before the directory
eligibility logic (
relayAgentIsSharedWithUser/ the directory branch ofshouldHideAgentFromMentions) can run, so that shipped logic isunreachable for them. The sharp edge from #2987, which we reproduced
independently: an external agent is mentionable while unattested
(misclassified as a person), and registering it correctly — kind:0
with verified NIP-OA attestation plus a kind:10100 directory entry — is
what makes it unreachable.
This PR rewrites the gate as one predicate that decides "will this agent
receive the message, and does its policy admit responding to you?" from
the same relay facts other agent surfaces already trust, keeping the
local store as one proof among several rather than the gatekeeper:
Deliberate boundaries:
buzz-acpreconnects with asincefilter and replays missed mentions, and stopped managed agents are
offered today. Offline is a queue, not a void.
A second commit (after community feedback on External (non-desktop-managed) agents: publishing kind:10100 makes them un-mentionable —
shouldHideAgentFromMentions's invocability logic is unreachable #2987 and thecross-machine reports) offers declaration-less attested agents to
their NIP-OA-verified owner only: the harness's inbound author gate
admits the owner under every respond-to mode, so an owner's mention is
never a void chip — and it covers the owner-on-another-device cases
([Bug] Remote managed agents are hidden and unmentionable on secondary devices #2349, Managed agents are only mentionable from the machine that runs them #3277) without requiring a directory entry that nothing
publishes for native agents yet. Strangers still see nothing. fix(desktop): prefer live agent mentions #2149's
stale-identity protection: stale same-name identities an owner might
now see fold into the live one via the untouched same-label/same-owner
coalescing, and NIP-IA-archived identities are peeled before
candidates are built.
anyone: offering them would need the add flow to honor thedirectory's
channel_add_policydeclaration — a follow-up, kept outof this diff.
owner-onlyexternal agents becoming mentionable by their verifiedowner is new capability the pre-aggregated sets could not express — it
composes the ownership proof the client already verifies for the
"managed by" surface with the agent's own policy declaration.
What this deliberately does not fix (same family, follow-ups)
respond-to setting never left its home machine (the third-party half
of Desktop hides allowlisted agents owned by another user from @mention autocomplete #3125 / [Bug] Remote Agent Missing from @ Mention Autocomplete Across Machines #2508 / Mention picker lists locally-managed agents instead of channel members, so an agent running on another machine can never be @-mentioned #3204): the owner's intent lives only in the local
kind:30177 record, so no client can honor what was never published.
The owner-side half of the cross-machine cluster ([Bug] Remote managed agents are hidden and unmentionable on secondary devices #2349, Managed agents are only mentionable from the machine that runs them #3277) IS
fixed by the second commit. Completing the third-party half needs a
directory writer at agent create/attach time.
(
MembersSidebar); its proof-shaped fix is thechannel_add_policydeclaration (also noted in External (non-desktop-managed) agents: publishing kind:10100 makes them un-mentionable —
shouldHideAgentFromMentions's invocability logic is unreachable #2987).set-add-policystill replaces the fullreplaceable kind:10100 record with a single field; under this
predicate the damaged entry fails closed ("no declaration") instead of
reading as an explicit exclusion, but the read-merge-write CLI fix is
its own change.
Related issue
Addresses the @-mention core of #2987 (kept open deliberately: that
report also covers the add-member search — first follow-up below — and
its comment thread documents declaration-less subcases discussed there).
Supplies the mechanism for #3125 (and the cross-machine
flavors #2508 / #2349 / #3277 / #3204) — see the section above for what
remains. Closest existing PRs, oldest first: #3110 (gate-loosening in both
the mention and add-member surfaces), #3448 (same root plus the
add-member search and a fetch-merge-write
set-add-policy; its CLIpiece is exactly right and complementary to this change), and #3609
(minimal same-root fix admitting the precomputed mentionable set at the
gate). Relative to both, this PR keeps agents without any directory
declaration hidden (preserving #2149's stale-identity suppression, which
removing or bypassing the pre-gate alone re-opens through the
member-not-in-directory branch), requires channel membership, and
handles
owner-only-by-owner via the verified attestation. Happy tocoordinate however the maintainers prefer — these three PRs are
convergent, not competing.
Testing
cd desktop && pnpm test— 3,784 passingpnpm exec tsc --noEmit— cleanpnpm exec biome check src/features/agents src/features/messages— cleananyone/ allowlist in + out /owner-onlyas owner, as non-owner, unverified / no declaration /non-member external / managed member + non-member / unknown viewer /
pubkey normalization on every comparison.
buzz-acpagents (systemd on a Linux host, NIP-OA-attested, fullkind:10100 entries — the External (non-desktop-managed) agents: publishing kind:10100 makes them un-mentionable —
shouldHideAgentFromMentions's invocability logic is unreachable #2987 topology): before this change theagents render agent chips and verified "managed by" but never appear
in @-autocomplete; with it, autocomplete offers them, the published
event carries the agent
ptag, and the harness responds. Staleregistration facts were confirmed on-relay via the NIP-98 bridge while
root-causing.