fix(agents): track channel membership, not a stale profile copy - #6577
Draft
cyberzero000 wants to merge 1 commit into
Draft
fix(agents): track channel membership, not a stale profile copy#6577cyberzero000 wants to merge 1 commit into
cyberzero000 wants to merge 1 commit into
Conversation
Desktop's `@mention` picker gated on `agent.channelIds.includes(channelId)` — the `channel_ids` array from the agent's kind:10100 profile. Nothing kept that array in sync with membership: `buzz-acp` only reads channels, and the relay never authors a 10100. So an agent invited to a new channel subscribed immediately and answered anything p-tagged, but never appeared in that channel's picker, and typed text was not consulted outside DMs. The agent was present, listening, and unmentionable until an operator republished the profile by hand. Mobile never had the bug because it resolves `@name` against relay membership. Fixed on both sides, either sufficient alone: - Desktop accepts relay membership in place of `channel_ids`. Membership is maintained by the relay and cannot drift. `respond_to` and `respondToAllowlist` are still enforced, so this widens discovery, not authority. - `buzz-acp` updates its profile when it observes a membership change it already handles. The update is a queued delta, not a rewrite from this harness's subscription set: that set is narrowed by `channels_override`, by rule matching, and by any channel whose startup subscribe failed, so publishing it wholesale would delete every channel this process happens not to serve, and two harnesses sharing a pubkey would flap the field against each other. Read-modify-write, so fields it does not own survive; a missing profile is left missing, because creating one belongs to deploy tooling. Deltas drain through a single ordered worker. kind:10100 is replaceable and read-modify-written, so two concurrent appliers would both start from the pre-change profile and the later write would drop the earlier channel, and two out-of-order appliers for the same channel would settle on the wrong answer. Queued deltas are coalesced into one publish because membership churn arrives in bursts and each replaceable write needs its own second. A failed publish keeps its deltas and retries with backoff rather than dropping them behind a single warn line. Signed-off-by: cyberzero000 <user1@cyberzerosystems.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.
Desktop's
@mentionpicker gated onagent.channelIds.includes(channelId)—the
channel_idsarray from the agent's kind:10100 profile. Nothing kept thatarray in sync with membership:
buzz-acponly reads channels, and the relaynever authors a 10100.
So an agent invited to a new channel subscribed immediately and answered
anything p-tagged, but never appeared in that channel's
@mentionpicker, andtyped text was not consulted outside DMs. The agent was present, listening, and
unmentionable until an operator republished the profile by hand.
Mobile never had this bug — it resolves
@nameagainst relay membership(
send_message_provider.dart).Fixed on both sides
Either is sufficient alone. Both are worth having: the desktop side is
immediate and cannot drift, the harness side keeps the profile honest for any
other client reading it.
Desktop accepts relay membership in place of
channel_ids. Membership ismaintained by the relay, so it cannot go stale.
respond_toandrespondToAllowlistare still enforced — this widens discovery, notauthority, and there is a test for exactly that.
buzz-acpupdates its profile when it observes a membership change italready handles. The update is a queued delta, not a rewrite from this
harness's subscription set. That set is narrowed by
channels_override, by rulematching, and by any channel whose startup subscribe failed, so publishing it
wholesale would delete every channel this process happens not to serve, and two
harnesses sharing a pubkey would flap the field against each other. The profile
is read-modify-written so fields the harness does not own (
display_name,capabilities,channel_add_policy) survive. A missing profile is leftmissing: creating one belongs to deploy tooling, and a partial profile invented
here would read as a downgrade to clients.
Why the delta queue has a single worker
kind:10100 is replaceable and read-modify-written, so:
later write would drop the earlier channel.
add landing after its own remove leaves the agent advertising a channel it
left.
One worker draining an ordered queue gives both properties. Queued deltas are
coalesced into a single publish, because membership churn arrives in bursts (a
bulk invite queues one delta per channel within milliseconds) and each
replaceable write needs its own second — publishing them one at a time would
demand more distinct seconds than the burst spans.
A failed publish keeps its deltas and retries with backoff. The queue is the
only copy, so dropping them would leave
channel_idswrong indefinitely behinda single warn line, and membership churn is rare enough that "wait for the next
change" can mean never for the life of the process.
Testing
cargo test -p buzz-acp— 810 passed, 0 failed.cargo clippy -p buzz-acp --all-targets -D warningsclean.pnpm test(desktop) — 5400 passed, 0 failed.tsc --noEmitandbiome checkclean.relayAgentCanRespondInChannelto consult onlychannelIdsdrops two of the file's 34 tests(
relay membership stands in for a stale channelIds, and thegetMentionableAgentPubkeyschannel-scope case).Note
Split out of #5806. It stands alone and has no dependency on the rest of that
branch.