Skip to content

fix(agents): track channel membership, not a stale profile copy - #6577

Draft
cyberzero000 wants to merge 1 commit into
block:mainfrom
cyberzero000:fix/agent-mention-eligibility
Draft

fix(agents): track channel membership, not a stale profile copy#6577
cyberzero000 wants to merge 1 commit into
block:mainfrom
cyberzero000:fix/agent-mention-eligibility

Conversation

@cyberzero000

Copy link
Copy Markdown

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 @mention 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 this bug — it resolves @name against 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 is
maintained by the relay, so it cannot go stale. respond_to and
respondToAllowlist are still enforced — this widens discovery, not
authority, and there is a test for exactly that.

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. The profile
is read-modify-written so fields the harness does not own (display_name,
capabilities, channel_add_policy) survive. A missing profile is left
missing: 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:

  • Two concurrent appliers would both start from the pre-change profile, and the
    later write would drop the earlier channel.
  • Two out-of-order appliers for the same channel settle on the wrong answer — an
    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_ids wrong indefinitely behind
a 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 warnings clean.
  • pnpm test (desktop) — 5400 passed, 0 failed. tsc --noEmit and biome check clean.
  • Mutation-checked: reverting relayAgentCanRespondInChannel to consult only
    channelIds drops two of the file's 34 tests
    (relay membership stands in for a stale channelIds, and the
    getMentionableAgentPubkeys channel-scope case).

Note

Split out of #5806. It stands alone and has no dependency on the rest of that
branch.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant