镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(agents): read owner messages and canvases over MCP - #2881

Merged
kazemek21 merged 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4285_owner-read-completeness
Nov 7, 2026
Merged

kazemek21 merged 1 commit into
block:mainfrom
kazemek21:kylez_NEXUS-4285_owner-read-completeness

Conversation

@kazemek21

@kazemek21 kazemek21 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Completes the owner-context MCP reads that #2745 started. An agent's app-hosted run can now read a message, thread or search (owner_read), the current Canvas (owner_canvas_get) and Canvas revision history (owner_canvas_history) with its signed-in owner's access, including in private channels the agent is not in. These reach the owner's session only through new typed OwnerAccess operations, and they write nothing. It also stops external app connections from getting the owner's access. Saving the owner's attachments (owner_media_get) is left out; see Outstanding.

Fixes NEXUS-4285 · Initiative status: https://blockcell.sqprod.co/sites/kylez-buzz-mcp-initiative/

How it reads

  • Same binding and fences as feat(agents): let app-hosted agents read their owner's channels and members #2745. createOwnerAccess runs each new operation on the owner's current binding. Each answer is released only if that binding still holds after the protection review of the tool result. A sign-out, account switch, reconnect, Stop or turn end returns nothing. The host still decides the agent, owner and community, and every answer names them in executedAs.
  • Channel checks come first. Every read checks the channel against the owner's list, then against the relay-signed 39000/39001/39002 records, before any content is read. System channels are refused at both steps. Searches are post-filtered to the owner's channels and report partial while that list is incomplete. Other channels' matches still fill the relay's page, so q tells the agent to narrow by channel or time when a limited page shows few results. One read fetches a page's edits, as read does; when it fills the relay's 500 cap, the answer says edits_limited instead of passing earlier text off as current.
  • Content comes from session.snapshotRead. That is the relay's own access-checked answer, which feeds no session projection. So owner reads change no unread or read state, membership, subscriptions or channel lists. A test asserts that readRemote sees only relay-signed channel records.
  • Paging matches the ordinary read and the app's Canvas history. Reads use the before/before_id cursor, fetch limit+1 to detect an older page, and apply the author's newest edit. A thread page holds at most 499, not the ordinary read's 500, because owner reads go through the relay reader, which caps a filter at 500 and the lookahead needs one. The first history page is read from the writer, as the app's history is, and so is an explicit revision, so an ID history just listed is found.
  • A newer edit by someone else no longer hides the author's. The shared edited() chose the newest edit before checking its author, so if an agent edited its message and its owner edited it later, both read and owner_read showed the original. It now picks the newest eligible edit. For owner_read that also follows the app's fold (fold.ts byAuthor, documented in docs/relay-queries.md): in a chat message's or forum post's channel, an edit by the author's owner counts when the owner's Buzz has verified that ownership from the author's signed profile. Without that verification only the author's own edits count. read passes no owner map, so it keeps author-only edits.
  • External apps lose the owner's access again. An external app connection reused the bundled run's handle, which has carried ownerAccess since feat(agents): let app-hosted agents read their owner's channels and members #2745, so it listed and could call the owner_* tools. connectExternal now drops ownerAccess from that handle, as the docs already promised.
  • Mesh agents keep it. A Mesh agent is app-hosted, so it has these tools; docs/agents.md now notes that, as its setup already warns of all tool output, members serving its model can read what they return.

There is no new logger, signer, client or lifecycle. The ordinary tools' identity, schemas and errors are unchanged: messageLine was extracted from tools.ts without behavior changes.

Tests

  • owner-access.test.ts covers:
    • a private channel the agent is not in, paged, with edits and names;
    • an archived open forum;
    • a whole thread from any of its replies;
    • search limited to the owner's channels, with limited and partial;
    • an unlisted channel, a system channel, a non-UUID channel, a channel the relay withholds, and a missing message;
    • offline (no snapshot reader);
    • the Canvas head, a revision, and a revision from another channel;
    • history paging;
    • a binding change mid-read, Stop during a content read, and a second owner's session never being read;
    • the MCP path callSessionTool → appClient → createOwnerAccess for the new tools.
  • service.test.ts proves an external connection to a bundled harness's run lists no owner_* tool and refuses a guessed owner_channel_list call, while that run's own handle does carry the owner's access. It fails without the fix.
  • owner.test.ts covers the tools' arguments, limits, continuations and errors. app-client.test.ts covers the turn checks and signal for the new wrappers.

Native gaps

  • The MCP-to-host proof runs the real tool dispatch, app client and owner-access binding against a fake owner session. It does not run a live relay or the native app.
  • The service-level test from feat(agents): let app-hosted agents read their owner's channels and members #2745 already proves that a run handle carries this binding, and that plugins and other communities never get it. It was not extended to the new tools.

Outstanding

  • owner_media_get is not in this PR. The native app_agent_media command saves a file where the agent's own tools can open it before the call's final checks run: the result protection review, and whether the owner's binding still holds after a download. No existing mechanism holds the file back until those pass, so the tool is neither listed nor callable. It needs a stage-then-publish step in the native media command first. The removed implementation is kept on the worker's preserve ref for that follow-up.

🤖 Generated with Claude Code

@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4285_owner-read-completeness branch 2 times, most recently from d74f9b6 to a9d7c24 Compare November 6, 2026 21:09
@kazemek21
kazemek21 marked this pull request as ready for review November 6, 2026 21:26
@kazemek21
kazemek21 requested a review from a team as a code owner November 6, 2026 21:26
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4285_owner-read-completeness branch 5 times, most recently from 52848df to 89b2fab Compare November 6, 2026 23:16
@kazemek21 kazemek21 changed the title feat(agents): read owner messages, canvases and attachments over MCP feat(agents): read owner messages and canvases over MCP Nov 6, 2026
Comment thread src/buzz-mcp/owner.ts Resolved
@dr0uleau
dr0uleau requested a review from a team November 6, 2026 23:46
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4285_owner-read-completeness branch 2 times, most recently from cecda48 to 8286cfd Compare November 7, 2026 00:18
@kazemek21
kazemek21 requested a review from dr0uleau November 7, 2026 00:32
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4285_owner-read-completeness branch 3 times, most recently from 63c43ea to 7074491 Compare November 7, 2026 02:09
Add owner_read, owner_canvas_get and owner_canvas_history to the owner
tools. Each is a typed, read-only OwnerAccess operation run on the owner's
current binding and released only while it holds. Content comes from the
relay's snapshot read, so no unread, membership or subscription state
changes. External app connections no longer carry the bundled run's owner
access.

Reads, owner and ordinary, now take a message's newest eligible edit, so
a newer edit by someone else no longer hides it. As in the app's fold, an
owner read also counts a chat message's or forum post's edit by its
author's verified owner in the same channel.

Fixes NEXUS-4285

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
@kazemek21
kazemek21 force-pushed the kylez_NEXUS-4285_owner-read-completeness branch from 7074491 to 158505b Compare November 7, 2026 02:44

@dr0uleau dr0uleau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 reviewed 158505b -- no P2 or worse findings remain. the owner-edit regression has test coverage, and CI passes for this commit. live relay and native checks remain unverified

@kazemek21
kazemek21 merged commit a063528 into block:main Nov 7, 2026
60 checks passed
tlongwell-block added a commit that referenced this pull request Nov 7, 2026
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>

* origin/main:
  Build sidebar headings as one fluid design-system row (#2686)
  fix(messages): swap bubble fills for reactions you've made, drop the ring (#2949)
  fix(messages): align line-start mention capsules with each other (#2899)
  fix(relay): probe the live socket on wake, focus and network (#2958)
  fix(messages): size thread avatars like the timeline's (#2890)
  build(desktop): share the bundled buzz CLI build across worktrees (#2894)
  fix(messages): render review comment mentions as chips and open at the reader's place (#2965)
  feat(agents): read owner messages and canvases over MCP (#2881)

Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
kazemek21 added a commit that referenced this pull request Nov 9, 2026
## TL;DR

Removes the six owner-session read tools that let a Buzz-hosted agent
read with its owner's own access: channel, member and DM lists, channel
details, messages and canvases. An agent's own reads and the channel
archive, unarchive and delete changes are unchanged. The tools are gone
from discovery on every harness, and a cached or direct call to one is
refused before anything is read with the owner's session.

Fixes NEXUS-4348 · Initiative:
https://blockcell.sqprod.co/sites/kylez-buzz-mcp-initiative/

## Scope

Kyle's request was to remove "the channel/member/dm list/get" endpoints
and leave the archive/delete channel changes for now. Removing
`owner_read` and the two Canvas tools as well is the initiative lead's
interpretation: they read through the same owner session and handle, and
leaving them would keep the owner-read capability this removes. If that
is wrong, the #2881 half can be restored on its own.

### Removed

From #2745 (owner channels and members):
- `owner_channel_list`, `owner_channel_get`, `owner_member_list`, their
hint text and discovery on Claude Code, Codex, Pi, the session RPC and
the harness kit.
- `src/features/agents/owner-access.ts`, the run handle's `ownerAccess`
field and the runner's host hook that built it for bundled-revision
runs.
- The `BUZZ_OWNER_MCP_COMMUNITIES` build setting (`vite.config.ts`). A
distributor build that still sets it now gets nothing from it;
`docs/configuration.md` lists it with the other retired inputs.
- The owner-tool sections in `docs/agents.md`, `docs/configuration.md`,
`docs/buzz-help.md` and `src/buzz-mcp/AGENTS.md`.

From #2881 (owner messages and canvases):
- `owner_read`, `owner_canvas_get`, `owner_canvas_history`,
`src/buzz-mcp/owner.ts` and the `Owner*` client types.
- The `docs/relay-queries.md` paragraph, and the exports of `tools.ts`
helpers that only `owner.ts` used. `edited()` keeps its author-only edit
selection, minus the owner parameter.

### Kept

- From #2745: the Stop and cancellation fences in the Claude Code, Goose
and ACP sessions (`workLifetime`, `current()`), which every Buzz tool
call now depends on, and the channel-lifecycle changes
(`channel-lifecycle.ts`, including its `records` parameter).
- `addTool` still refuses plugin tool names starting with `owner_`, so a
stale call to a retired name can never reach plugin code. There is no
other owner-prefixed restriction and no new permission layer.
- Ordinary agent-scoped reads, Codex per-thread/turn tool validation,
plugin and external isolation.

## Alternate routes checked

Nothing else reached these tools: no Rust, IPC, sidecar or RPC path, and
Mesh and external agents use the same run handle that no longer carries
owner access. Codex thread tool keys no longer carry the `:owner`
suffix, so threads created with owner tools are replaced instead of
reused with a stale tool list.

## Tests

The new tests list the six names as a fixed fixture rather than reading
them from the catalog:
- `session-tools.test.ts`: `sessionTools()` and `sessionHint()`, which
every harness lists from, name none of the six.
- `service.test.ts`: a real bundled run handle has no `ownerAccess`.
With the owner's session stubbed to return an `OWNER_ONLY` message for
an owner-only channel, each retired name is refused with `No tool
<name>` before any relay query. An ordinary `read` of that channel then
runs with the agent's own key, filtered to that channel, and returns `No
messages.`. The same client's `read` of the agent's own channel returns
its message, and no read through the owner's session ever names the
owner-only channel. External agents see none of the names and are
refused the same way.
- `codex/runtime.test.ts`: `dynamicTools` lists none of the names, each
call fails Codex's validation, a planted owner-access spy is never
called, and the thread's own `read` works.
- `app-client.test.ts`: a handle that still carries an owner capability
doesn't pass it to the tool client.
- The Claude Code and Goose Stop/timer tests now hold an ordinary
`mem_get` read instead of an owner tool.

On a Blox box with these commits applied to main: `tsc` passes, and the
12 affected Vitest files pass (claude, codex, goose, pi runtimes,
app-client, tools, session-tools, service, kit, ACP client,
channel-lifecycle). Full CI covers the rest. `git push --no-verify`
skipped the pre-push lanes (types-and-related-unit-tests, design-system,
rust-clippy); CI's `pnpm check`, sharded Vitest and clippy jobs repeat
all three.

Fail-before check: the removal tests in `service.test.ts` and
`codex/runtime.test.ts` were run against the pre-removal source
(0a5c190). Three of them fail on real assertions:
- Codex `dynamicTools` contains `owner_channel_list`.
- An external agent's call returns "Owner tools are not available to
this agent here; nothing was read." instead of `No tool
owner_channel_list`.
- The bundled handle has `ownerAccess`.

All three pass on this branch. Vitest strips types, so this check
doesn't cover `tsc`. The `session-tools.test.ts` discovery test also
passes on the old source, because that source only lists the tools when
the owner setting is on, so it doesn't count as a fail-before check.

## Not covered

- No native app run or live relay check. The removal is exercised
through the service, runner and harness tests above.
- A Buzz process already running a build with these tools keeps them
until it updates. This doesn't revoke anything remotely.

## Related open PRs

- Human work in overlapping files keeps its behavior but may need a
rebase: #2700, #2974, #3081, #2972 (cancel added tools on native Stop),
#2935, #1634, #2622, #3095, #2882.
- #3004, #3005, #3006, #3008 and #2980 build on the removed owner access
and are not part of this change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
(claude-opus-5-5) on behalf of kylez

---------

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.

2 participants