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

agents: store agent attention in its community (opt-in per agent) - #3095

Draft
loganj wants to merge 6 commits into
mainfrom
larry/attention-runtime-reconcile
Draft

loganj wants to merge 6 commits into
mainfrom
larry/attention-runtime-reconcile

Conversation

@loganj

@loganj loganj commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Agent attention (an agent's Interests, watches and timers) can now be stored in the agent's community instead of on one device. Every device that runs the agent, or shows its Attention tab, then sees the same set and edits the same records.
  • This is opt-in for each agent, and nothing turns it on yet. Device storage stays the default. A later change adds the migration (copy the device set to the community, compare, then switch) and a switch to turn it on.
  • The format follows the "Agent Attention configuration on the relay" design: each Interest or watch is one NIP-AR artifact (a versioned record on the relay) in a private system channel. The agent creates that channel and shares it with its owner.

Behavior for an agent whose attention is on the relay

  • Reading. The owner's session keeps the channel's set current with NIP-AR sync. Each complete set and each live change is copied into the device record. The runtime, tools and Attention tab keep reading that record, so watch matching, timer schedules and the Attention tab are unchanged.
  • Startup gate. The agent runs no watches or timers on this device until it has read one complete set.
  • Bad records. A record that fails its checks (identity, schema, fields, validator or count limits) is skipped and listed as unreadable on the Attention tab. The other records still apply. A record whose identity does not match its slug is kept under its own identity, so it cannot hide a valid record.
  • Writing. An attention change reads the set fresh, runs the existing checks, then publishes one revision for each changed object, with the head it revises. The agent signs tool writes and runtime removals. The owner signs Attention tab edits. An edit of several objects (for example, removing an Interest and its watches) publishes the watches first and the Interest last.
  • Conflicts. If another writer changed an object first, the write fails with a conflict and is not retried. If an answer is lost, the result is decided from the current head. An add of an object that was deleted restores it. An add of an object with the same live value succeeds.
  • Run state stays local. Timer progress, source completion and wake history stay in the device record and never need the relay.
  • Status. While the set is loading, out of date or unreadable, the Attention tab says so (Agent.attentionProblem). A resync or a lost channel starts again from finding the channel. If two processes create a channel at the same time, both use the oldest one and the newer one is deleted.

Not in this change

  • Migration, rollback and the per-agent switch (next change).
  • Auditable agents (owner-only writers) and pruning relay records that the device record does not have.

🤖 Generated with Claude Code

loganj and others added 5 commits November 9, 2026 09:52
An agent whose record has attentionStorage "relay" keeps its Interests,
watches and timers as NIP-AR artifacts (type buzz.agent-attention) in its
attention channel: a private system channel the agent creates, labeled
agent-attention, with its owner added as an owner.

- Reader: the owner's session keeps the channel's set with NIP-AR sync.
  Each complete set and live head is mirrored into the device record
  through setAttention, so timer, source and history rules stay those of
  a local edit. Records that fail their checks (d, schema, fields,
  validator, count limits) are skipped and reported.
- Gate: a relay agent's watches and timers run only once this session has
  its complete set.
- Writer: every attention change goes through the per-agent change queue:
  a fresh one-time sync, the existing checks, exactly one revision with
  prev, signed as the agent (tools, runtime removals) or the owner
  (Attention page edits). Conflicts fail with no retry; a lost answer is
  settled from the head; adds follow the ensure flow (restore a deletion,
  accept the same live value, refuse another).

Nothing sets the flag yet; migration comes next.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
- A record is keyed by its slug only when d derives from that slug, so a
  malformed record can't hide a valid one.
- Reader: a resync starts again from Find; concurrent creates converge on
  the oldest channel; reader state (reading, stale, can't read) shows as
  the agent's attentionProblem on the Attention tab.
- Writer: an edit of several objects publishes one revision per object,
  watches removed before their Interest, so Remove Interest works. Only
  object changes need the relay; run-state-only changes save locally. A
  live set made before this device's write no longer undoes it. An unread
  head after a lost answer reports "may not have been saved", not a
  conflict.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
A live set from before an earlier write can arrive after a later one, so
the heads each write replaced are kept together until a set shows them.
Relay attention retries use equal jitter.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Until this session has read the complete set of an agent whose attention
is on the relay, its watches wait as they do while plugins start: each
keeps its checkpoint and live events are held, instead of being treated
as Attention off, which moved every checkpoint to now. So the first
catch-up after each app start, or after the switch to the relay, still
reads what came in while the app was closed.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@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>
An edit or restore of an attention record now keeps the tags of the
head it revises that are outside NIP-AR's envelope (client tags and
root), as NIP-AR requires of editors, so editing a record another
client wrote no longer drops its tags.

The relay attention store now creates the attention channel only when
the session's relay advertises NIP-AR sync. Before, a relay with NIP-CL
and NIP-AR but no sync got a channel, then the read failed. The session
exposes artifactSync for this.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>

This branch has not been deployed

No deployments
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