fix: publish kind:10100 agent directory records so shared agents are @-mentionable - #5546
Open
jjalora wants to merge 2 commits into
Open
fix: publish kind:10100 agent directory records so shared agents are @-mentionable#5546jjalora wants to merge 2 commits into
jjalora wants to merge 2 commits into
Conversation
The desktop mention-eligibility pipeline admits a foreign agent only when a kind:10100 directory record advertises respond_to (anyone/allowlist) plus the exact channel — but nothing ever published such records, so relayAgents was always empty (or a policy-only stub from `buzz channels set-add-policy`) and non-owners could never @-mention shared agents regardless of the owner's configuration. Publish the record from buzz-acp itself: the harness knows the respond_to policy it actually enforces and the channels it actually listens on, so the advertisement can never drift from enforcement (a deployment that clamps --respond-to also clamps what is advertised). - publish on startup (after channel subscriptions), debounced on membership add/remove, best-effort offline status flip on graceful shutdown - carry channel_add_policy forward from the current head record so a policy set via `buzz channels set-add-policy` survives the republish (and the relay's kind:10100 side effect stays satisfied) - exclude dm/unknown-type channels: the author gate rejects non-owner DMs and fails closed on unresolved types, so advertising them would invite mentions the harness then silently drops - best-effort throughout; a failed publish never takes the harness down The consumer side (agents_from_events → RelayAgent → mention eligibility, pinned by desktop/tests/e2e/mentions.spec.ts) already exists; a new fixture test pins the cross-crate content schema from the consumer side. Fixes block#5363 Fixes block#4548 Signed-off-by: jjalora <jjalora@stanford.edu>
…ntionable
addCandidate applied isAgentIdentityInAllowedList before
shouldHideAgentFromMentions, which made the latter's documented
member-agent rule ("unknown invocability => show" — the Option B
comment and its unit tests) unreachable: any member agent absent from
both the managed list and the kind:10100 directory was dropped by the
first check before the second could run.
Drop the redundant first check at this call site.
shouldHideAgentFromMentions already implements the intended policy:
invocable agents always show, non-member non-invocable agents stay
hidden, member agents with an explicit not-invocable directory record
stay hidden, and member agents with no directory record now show — the
fallback that keeps cross-owner channel agents addressable while their
harness has not (yet) published a directory record.
With the harness now publishing records for every respond_to mode,
owner-only member agents gain an explicit exclusion record and correctly
disappear from other users' autocomplete — the stale-agent concern that
motivated the stricter gate is answered by data instead of by hiding
every foreign member agent. The pinned e2e expectation is updated
accordingly.
Fixes block#4489
Fixes block#3809
Signed-off-by: jjalora <jjalora@stanford.edu>
wolfyy970
suggested changes
Aug 11, 2026
wolfyy970
left a comment
There was a problem hiding this comment.
Publishing from the harness is the better direction. It can describe the channels and response mode the running agent actually enforces, so I would use this instead of the separate CLI writer in #5528. #5530 should reuse the same path rather than construct another profile.
Two boundaries need settling first:
- NIP-AP explicitly treats respond_to_allowlist pubkeys as sensitive. Kind 10100 is queried community-wide, so this publishes the exact access list to every member. The public directory can advertise safe routing state, but allowlist membership needs a private boundary.
- Carrying channel_add_policy forward is still a read-modify-write race. If buzz channels set-add-policy writes after the head fetch but before this submit, the harness restores the old policy. Either the harness must become the sole profile writer or directory and policy state need separate ownership.
I would also split the unknown-member fallback into its own PR. The producer fix is useful without changing whether clients show agents whose invocability is unknown.
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
Non-owners can never
@-mention a shared agent, no matter what the owner configures — the root cause is that the kind:10100 agent directory the mention-eligibility pipeline reads has no producer. #4913 implemented and e2e-pinned the consumer side (respond_to+ exact-channel authorization fromRelayAgentrecords), but nothing anywhere publishes those records: the only kind:10100 writer in the tree isbuzz channels set-add-policy, which writes a policy-only body (and, being replaceable, clobbers anything richer — #3663/#4490). On a real relay,list_relay_agentsreturns nothing usable,getMentionableAgentPubkeyscollapses to the viewer's own managed agents, and the feature only appears to work in e2e because the mock bridge synthesizes directory entries (syncMockRelayAgentsFromManagedAgents) with no production counterpart.Fixes #5363, fixes #4548 (commit 1); fixes #4489, fixes #3809 (commit 2). Also relevant to #2603 (composer half; the MembersSidebar add-gate is untouched), #4128, #4776, #3125, #3739, and the directory-staleness aspect of #4535.
Commit 1 —
feat(acp): the harness publishes its own directory recordbuzz-acpnow publishes and maintains the agent's kind:10100 record. The harness is the right producer because it knows therespond_topolicy it actually enforces and the channels it actually listens on — the advertisement can never drift from enforcement (a deployment that clamps--respond-toto owner-only also clamps what gets advertised, so records never promise an authorization the author gate would refuse).statustoofflineon graceful shutdown.agents_from_events→RelayAgentInfo→RawRelayAgentparse:name(fromsession_title, omitted when absent so the npub fallback engages),agent_type(normalized agent command identity),status,respond_to(theRespondToDisplayliterals),respond_to_allowlist(sorted), index-alignedchannels/channel_ids,capabilities,channel_add_policy.channel_add_policyis carried forward from the current head record before every publish, so a policy set viabuzz channels set-add-policysurvives the republish and the relay's kind:10100 side effect (handle_agent_profile) stays satisfied. On a failed head fetch the publish is skipped entirely — never fabricate a policy.RestClient::submit_event) rather than the WS path, which silently drops non-observer events while disconnected.With records published, stock desktop and mobile clients admit the agent through the already-pinned consumer chain (
relay-only anyone agents are visible when a channel is shared,relay-only shared agents emit an outbound mention tag when selected, allowlist specs). Unit tests pin the producer schema; a new integration test (desktop/src-tauri/tests/agent_directory_schema.rs) pins the cross-crate contract from the consumer side.Commit 2 —
fix(desktop): member agents with unknown invocability stay mentionableaddCandidateranisAgentIdentityInAllowedListbeforeshouldHideAgentFromMentions, making the latter's documented member-agent rule ("unknown invocability ⇒ show" — the Option B comment, and its unit tests) unreachable dead code: any member agent absent from both the managed list and the directory was dropped by the first check.This commit removes the redundant first check at that call site.
shouldHideAgentFromMentionsalready implements the intended policy: invocable ⇒ show; non-member non-invocable ⇒ hide; member with an explicit not-invocable directory record ⇒ hide; member with no record ⇒ show. The last case is the useful fallback while a fleet's harnesses haven't (yet) published records — and once they have (commit 1 publishes for every mode, includingowner-only), non-invocable member agents gain an explicit exclusion record and correctly disappear, so the stale-agent concern that motivated the stricter gate is answered by data rather than by hiding every foreign member agent. One e2e expectation is updated accordingly (stale channel-member agents … stay hidden→… stay mentionable).This commit is separable — if maintainers prefer to keep the fail-closed member behavior from #4913, commit 1 alone fixes the shared-agent flow for record-publishing harnesses.
Testing
cargo test -p buzz-acp(incl. 6 newdirectoryunit tests: consumer-schema shape,respond_toliterals, name-key omission, allowlist ordering, dm/unknown channel exclusion, add-policy carry-forward/defaults)desktop/src-tauri/tests/agent_directory_schema.rsintegration test (consumer-side contract pin)desktop:tsc --noEmit, biome, full unit suite, and the fullmentions.spec.tse2e suite (58 specs) with the one updated expectationcargo clippy -p buzz-acp --all-targets -- -D warnings,cargo fmt --check@autocomplete in the shared channel → mention carries theptag → agent responds. (This deployment shape — a 24/7 host running shared community agents — is what surfaced the bug.)Notes / non-goals
buzz channels set-add-policystill replaces the whole record (set-add-policy replaces the full kind:10100 record, hiding the agent from mention autocomplete #3663/buzz channels set-add-policy replaces the caller's whole kind:10100 agent profile, erasing name and respond_to #4490); this PR makes the damage self-healing (next harness publish restores the full record, policy carried forward) but doesn't change the CLI.statusis cosmetic to eligibility (presence remains the liveness signal).🤖 Generated with Claude Code