Repository navigation
Design: publisher-scoped notice visibility — let a route read the state of notices it published #460
Description
Problem (design question)
A route can publish a notice but never learn what became of it. (await agent()).notices.read() returns only the deliveries attempted on the current invocation, and inbox() returns only pending notices addressed to the current principal. Neither shows the publisher the state of the notices it published (pending / attempted / acknowledged / expired), and no route-level surface shows the ledger as a whole. AgentNoticeLedger.read() does exist, but only the generated wiring holds a ledger — routes get the request-bound handle.
Concretely, examples/worktree-proximity's coordinator/status tool cannot report "N notices still pending for actor X"; it reports topology facts only.
This is deliberately how #264 landed ("cross-recipient reads are structurally impossible"), so this issue is a design question, not a bug: is there a scope narrower than "whole ledger" that a coordinator legitimately needs?
Evidence (main @ 284141958)
packages/rsc-runtime/src/notices/contract.ts:179-185—AgentNoticesHandle = { acknowledge, inbox, publish, read }.packages/rsc-runtime/src/notices/ledger.ts:393-396, 471-476—read()returnsdeliveriescomputed at admission for this invocation only;:284-296—inboxProgramfiltersstate === 'pending' && recipientMatchesPrincipal(...).packages/rsc-runtime/src/notices/ledger.ts:487-490—AgentNoticeLedger.read()returns the full snapshot, but is not reachable from a route.examples/worktree-proximity/src/mcp/coordinator/tools/status.tsx:36-64— result carriesactors,activeActivities,refusals,revision; no notice counts.examples/worktree-proximity/README.md:103-105: "(await agent()).notices.read()exposes only deliveries attempted for the current invocation. The coordinator status therefore reports topology facts only and does not claim a whole-ledger pending count."- Add recipient-aware notices and capability-gated delivery #99 stage-3 comment (feat(notices): expose the recipient-scoped MCP inbox resource (#99 stage 3) #264): "cross-recipient reads are structurally impossible (recipient matching runs against the invocation's observed identity axes only)".
Proposed shape (pick one; both are small)
A. Publisher-scoped read (preferred). Record the publishing principal on the notice at publish() (identity axes + lineage.conversation, no content duplication) and add
interface AgentNoticesHandle {
/** Notices this principal published, with their current state and receipts; never other publishers' or other recipients' notices. */
published(): Promise<readonly AgentNotice[]>;
}Authorization gets a new phase: 'published' so the authorizer can still refuse. This gives a publisher "was my warning attempted / acknowledged?" without any cross-recipient exposure.
B. Aggregate counts only. notices.summary() → { pending, attempted, acknowledged, expired } for notices whose recipient is under the caller's lineage root. Coarser, no content, but depends on the lineage-addressed recipient axis (sibling issue).
Explicitly not proposed: exposing AgentNoticeLedger.read() on the request handle.
Out of scope / related
- Redaction/retention (feat(notices): redaction contract and retention policy — close out #99 acceptance item 7 #437, Add recipient-aware notices and capability-gated delivery #99 item 7) — a
published()view would need to honour whatever redaction lands there. - Delivery evidence semantics are unchanged:
attemptedremains the strongest cross-actor state (Add recipient-aware notices and capability-gated delivery #99 survey).
Acceptance
coordinator/status in examples/worktree-proximity reports, for the publishing actor, how many of its proximity notices are pending vs attempted vs acknowledged; a route-unit test in tests/route-unit/routes.test.ts publishes from actor A, admits on actor B's next event, and asserts A's published() shows attempted while B's inbox() is empty and A's inbox() never showed the notice.
Activity
ScriptedAlchemy commentedon Sep 3, 2026
Related: #457 (lineage tree), #458 (lineage-addressed notices — option B depends on it), #459 (providers see lineage/state). Context: #99 stage 3 (#264) recipient-scoped inbox decision; #437 redaction/retention.
chore(examples): use built-in framework surface over hand-rolled setup (
feat(notices): publisher-scoped notices.published() with per-notice s…
feat(notices): publisher-scoped notices.published() with per-notice s…
feat(notices): publisher-scoped notices.published() with per-notice s…
feat(notices): publisher-scoped notices.published() with per-notice s…
feat(notices): publisher-scoped notices.published() with per-notice s…