Skip to content

fix(acp,core,relay): publish agent profiles on kind:10100 and move channel-add policy to 10101 - #4994

Open
jongouveia wants to merge 3 commits into
block:mainfrom
jongouveia:feat/acp-agent-profile-10100
Open

fix(acp,core,relay): publish agent profiles on kind:10100 and move channel-add policy to 10101#4994
jongouveia wants to merge 3 commits into
block:mainfrom
jongouveia:feat/acp-agent-profile-10100

Conversation

@jongouveia

Copy link
Copy Markdown

Problem

Kind 10100 is doing two incompatible jobs, and because it is replaceable (one event per author) the two overwrite each other.

As an agent profile. crates/buzz-core/src/kind.rs documents KIND_AGENT_PROFILE = 10100 as "Agent metadata + owner reference (replaceable, agent-authored)", and three consumers read it that way:

  • desktop/src-tauri/src/nostr_convert.rsagents_from_events
  • desktop/src-tauri/src/commands/agent_discovery.rs — queries kinds: [10100]
  • the mobile app — nostr_filters.dart (agentProfiles()), agent_identity_provider.dart (AgentDirectoryEntry.fromEvent)

As a channel-add policy. buzz channels set-add-policy publishes kind:10100 with {"channel_add_policy": ...}, and the relay's side effect requires that field, rejecting anything without it:

kind:10100 missing channel_add_policy field

Nothing ever published the agent profile side, so the conflict stayed dormant. The consequence is visible on mobile: a running agent never appears in the directory, and because the presence cache only tracks pubkeys the app has discovered, it also never shows as online.

Changes

1. buzz-acp publishes the agent profile (kind:10100).

Published over the authenticated POST /events bridge once channel discovery completes, and republished when membership changes so channel_ids stays current. Content carries the fields consumers already parse: name, display_name, agent_type, channels, channel_ids, capabilities, status, respond_to, respond_to_allowlist.

The name is resolved once at startup from the agent's own kind:0 display_name, falling back to the normalized runtime command when no kind:0 profile exists. Without that, every agent would advertise its runtime ("codex-acp", "claude-agent-acp") instead of its name. Publishing is gated on the existing presence flag and a failure only warns.

2. The channel-add policy moves to its own kind (10101).

KIND_CHANNEL_ADD_POLICY = 10101 is registered in ALL_KINDS with the usual replaceable compile-time assertion. The relay accepts it under the same UsersWrite scope and routes it to the policy handler, renamed from handle_agent_profile to handle_channel_add_policy since that is all it ever did. The CLI publishes the new kind.

Back-compat runs both ways: a kind:10100 that still carries channel_add_policy is honored for older clients, and one without it is a no-op instead of an error.

No migration. The enforced policy lives in the users table and is written by the side effect, so existing rows stay valid.

The desktop and mobile clients need no changes — they already read 10100 as the agent profile.

Testing

just ci passes on this branch merged with main (exit 0).

New coverage:

  • agent_profile_event_contains_mobile_directory_fields — event shape, kind, author, sorted channel and allowlist arrays
  • agent_profile_event_falls_back_to_runtime_identity_without_kind0_profile — name fallback
  • channel_add_policy_kind_is_registered_and_replaceable
  • channel_add_policy_is_global_only_and_requires_users_write
  • legacy_agent_profile_channel_add_policy_still_applies_policy (Postgres-gated)
  • agent_profile_without_channel_add_policy_is_a_no_op (Postgres-gated)

The two Postgres-gated tests were not exercised locally; they need just test with Postgres and Redis.

Manual check: running the built harness against a live relay logs agent profile published after discovered N channel(s), and the relay accepts the event.

jongouveia and others added 3 commits August 3, 2026 22:47
Clients build their agent directory from kind:10100 agent-profile events,
but nothing published them. The desktop app publishes only a kind:0 profile
for a managed agent, and channel-level bot membership (kind:39002) is not a
source those directories read, so a running agent never appeared in the list
and could not be tracked for presence.

buzz-acp now publishes a signed kind:10100 profile over the authenticated
POST /events bridge once channel discovery finishes, and republishes it when
membership changes so channel_ids stays current. Content carries the name,
respond_to mode, and allowlist that consumers use for mention eligibility.
The name comes from the agent's own kind:0 display name, falling back to the
normalized runtime command when no profile exists. Publishing is gated on the
existing presence flag, and a failure only warns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUks9koR8d9L3gC9ARsSLy
Signed-off-by: Jon Gouveia <jon@pixelcove.co>
Kind 10100 was doing two jobs. The registry documents it as the agent
profile, and the desktop and mobile clients both read it that way, but the
relay also used it to carry a user's channel_add_policy and rejected any
10100 without that field. Since 10100 is replaceable, an agent profile and a
policy silently overwrote each other.

Adds KIND_CHANNEL_ADD_POLICY (10101) for the policy and leaves agent profiles
on 10100. The relay accepts the new kind with the same UsersWrite scope and
routes it to the policy handler, renamed to say what it does. Older clients
still work: a 10100 carrying channel_add_policy is honored, and one without it
is now a no-op instead of an error. The CLI publishes the new kind.

No migration. The enforced policy lives in the users table and is written by
the side effect, so existing rows stay valid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUks9koR8d9L3gC9ARsSLy
Signed-off-by: Jon Gouveia <jon@pixelcove.co>
Signed-off-by: Jon Gouveia <jon@pixelcove.co>
@jongouveia
jongouveia requested a review from a team as a code owner August 6, 2026 01:45
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