Skip to content

feat(owner-mcp): let agents add, remove and re-role channel members with their owner's access - #3004

Draft
kazemek21 wants to merge 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4311_owner-member-endpoints
Draft

kazemek21 wants to merge 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4311_owner-member-endpoints

Conversation

@kazemek21

Copy link
Copy Markdown
Contributor

TL;DR

Owner tools can now change channel membership with the owner's access, not just read it. owner_member_add, owner_member_remove and owner_member_set_role route through the app's existing member services, so the owner's session signs every command and the usual rights checks apply. A change runs only while the tool call, the agent's run and the owner's binding all still hold.

What changes

  • Routing. Library agents are added through channelWrites.addAgents (inventory from session.agentChoices). People are added through addChannelMember. Removals and role changes go through member administration (administerMember). The agent never gets a signer, and no other path is added.

  • Admission. ownerMutationActive (owner-access.ts) is true only while all of these still hold:

    • the call's signal and check;
    • the agent's run (runner passes its lifetime signal through host.ownerAccess);
    • the owner's binding (same session, viewer and origin);
    • the channel, which must still be one of the owner's listed, non-system channels.

    addChannelMember now forwards this admission into the outbox's send, retry and replacement enqueue. administerMember forwards a caller signal and admission into run, which checks it just before publishing. When neither a signal nor a check is supplied, every change is refused.

  • Outcomes.

    • confirmed: a relay-signed roster read for this call shows the result, and the answer carries the role the roster shows.
    • rejected: nothing was changed.
    • unconfirmed: the change may have taken effect, and the answer carries the sent command's operation ID.

    Administration now records the signed command ID on its operation. If the turn or binding changes before the answer is released, the tool withholds the result and says the change may have happened.

  • Recovery.

    • A repeated addition resumes its existing Outbox item with a fresh admission (retry(id, active)) and never sends a second one. One still sending is only waited on.
    • A guarded person invitation can now be resumed from an explicit Add once the target is positively a person: complete agent inventory, a loaded profile, no agent marker and no Bot grant. Guarded agent invitations still need the agent's profile.
    • Removal is refused while any addition naming that member, single or batch, is still sending, so the removal can't be undone by it.
    • If an addition fails partway, a roster read decides the outcome: it is confirmed when the roster shows them and never rejected once its Outbox item was acknowledged.
  • Refusals.

    • DMs, archived channels and system channels (checked on the initial read, on the cached channel and again at dispatch).
    • The owner as a target, and the calling agent's own role.
    • Role changes in Solo chats, and a Guest role on a Solo share; Solo removals are allowed.
    • A person add over a pending Bot grant for the same key (it would grant Bot).
    • Owner or Bot as target roles.
    • Admin for anyone not confirmed as a person; an unknown classification never counts as a person.
    • Agents outside the owner's library, and people with no profile.

Docs: docs/agents.md#owner-tools and the configuration flag description.

Coverage

src/features/agents/owner-members.test.ts drives a real createRelaySession against a relay fixture that applies signed member commands to its roster and accepts an agent's message only from a member. Calls go through appClient and callSessionTool, as a harness makes them. It covers:

  • Composition: agent added → relay roster → the agent's ordinary message is accepted → removal revokes it.
  • People and roles: guest add, promotion with the confirmed role returned, and refusal on a stale expected_role.
  • Refusals:
    • unknown targets and non-library agents;
    • admin for agents or unconfirmed people;
    • self and owner targets;
    • Solo role edits (removal allowed);
    • system channels and DMs.
  • Resume: a possibly-sent addition is resumed with the same operation ID and no second command, and one the relay applied despite a lost receipt is confirmed.
  • Cancellation: nothing is published when the turn ends, the binding changes or the agent's run ends while signing.
  • No caller: a run without a caller is refused.
  • Remove during add: removal is refused while an addition is still sending.

Unit additions:

  • tool arguments and annotations (owner.test.ts);
  • caller forwarding (app-client.test.ts);
  • the admission and recorded command ID on administration (administration.test.ts);
  • guarded-invitation recovery under an explicit Add and under a caller admission that is withdrawn (members.test.ts).

Checked on a Linux workstation at the PR's parent: tsc --noEmit and biome on touched files were clean. Vitest passed 1,726 tests (1 skipped) across owner, app-client, owner-access, owner-members, members, administration, outbox, service, config-channels, session-agent-admission, the Codex runtime and MessageComposer. The repository's pre-push hook ran types and related unit tests. Browser journeys and the native app were not exercised. The agent-authored send in the composition test is judged by the relay fixture, not by a native harness turn.

Part of the owner-MCP initiative: read-only owner tools first, then membership changes through the owner's existing services. Channel management and broader writes are separate work.

🤖 Generated with Claude Code

…ith their owner's access

Adds owner_member_add, owner_member_remove and owner_member_set_role to the
owner tools. Library agents are added through channelWrites.addAgents, people
through addChannelMember, and removals and role changes through member
administration, so the owner's session signs every command and the existing
rights checks apply. Each change carries the tool call's admission (turn,
agent run and owner binding) into the outbox's send, retry and replacement and
into administration's pre-publish check. Outcomes are confirmed from a
relay-signed roster read, rejected, or unconfirmed with the sent command's ID;
a repeated addition resumes its existing Outbox item.

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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