Skip to content

feat(runtime): add durable next-event notice ledger - #184

Merged
ScriptedAlchemy merged 1 commit into
mainfrom
wave5/99-notices-core
Sep 1, 2026
Merged

feat(runtime): add durable next-event notice ledger#184
ScriptedAlchemy merged 1 commit into
mainfrom
wave5/99-notices-core

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

  • add the optional @agent-bundle/runtime/notices ledger on the existing state kernel, persisting detached finite Agent Document snapshots with atomic publish/dedupe/expire/withdraw transitions
  • fill AgentRequestContext.notices with a request-bound read/publish handle and attempt matching notices only on admitted event invocations after publish-time and delivery-time authorization
  • expose only the evidenced v1 states (pending, attempted, expired, unavailable, withdrawn); stronger delivery/read/acknowledgement states, MCP inboxes, routers, timers, and workers remain deferred
  • preserve stateless packaging: root/plugin entries contain no notice or state implementation, and the notice subpath does not load node:sqlite

Closes the stages 1-2 narrow-core slice of #99; #99 stays open for deferred stages 3-4.

Test plan

  • pnpm exec rstest run packages/rsc-runtime/tests/agent-request.test.ts packages/rsc-runtime/tests/agent-document.test.ts packages/rsc-runtime/tests/state-kernel.test.ts packages/rsc-runtime/tests/notices-ledger.test.ts --config rstest.unit.config.ts (49 passed)
  • pnpm build
  • AGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 pnpm exec rstest run packages/rsc-runtime/tests/notices-sqlite-cross-process.test.ts packages/rsc-runtime/tests/state-packaging.test.ts --config rstest.integration.config.ts (6 passed)
  • pnpm typecheck
  • pnpm lint (0 errors, 0 warnings)

Build the narrow notice core on the existing state kernel so recipient-scoped snapshots survive restarts and only evidence attempted next-event delivery.
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2946362

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@agent-bundle/runtime Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T20:03:02.626234Z 2946362 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy
ScriptedAlchemy merged commit 88eb4b9 into main Sep 1, 2026
5 of 9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2946362cac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +279 to +280
const candidates = before.state.notices.filter((notice) =>
notice.state === 'pending'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve deliveries when replaying an admitted invocation

When an event invocation is retried sequentially with the same invocation.id—for example after the first process committed admission but died before the handler read the notice—the notice is already attempted, so this pending-only filter produces no candidates and the dispatch/delivery reconstruction block is skipped. The retry therefore sees read() return [] even though an attempted receipt for that exact invocation is durable; recover matching existing receipts from the snapshot so replaying an admitted invocation exposes the same delivery.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in #202 (merged as d63dd3b): deliveries are now derived from the current state's attempted receipts for the invocation id, so replaying an admitted invocation observes the same notice set instead of an empty read (idempotency model per #142/#149). A review follow-up on #202 also scopes replayed deliveries to the requesting principal, so an id reused by a different principal is not treated as a replay. Regression tests in notices-ledger.test.ts.

Comment on lines +281 to +283
&& (notice.expiresAt === undefined
|| Date.parse(notice.expiresAt) > admissionTime)
&& recipientMatchesPrincipal(notice.recipient, request.principal));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude notices published after the event began

For delayed or out-of-order admission, an event whose startedAt precedes a notice's createdAt still selects that notice because eligibility checks only expiry and recipient. This lets a backdated event consume a notice that did not exist when the event began, violating the next-event-after-publication ordering; require the notice creation time to be no later than the admission time.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in #202 (merged as d63dd3b): admission now excludes notices whose createdAt is after the event's startedAt. Regression test: a notice published after the event start stays pending and undelivered.

Comment on lines +269 to +270
openRequest(request: AgentNoticeRequest): Promise<AgentNoticeRequestLease> {
return runPromise(Effect.gen(function*() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass the request signal to authorization effects

If an async delivery authorizer stalls and the invocation's AbortSignal is aborted after the store read, this boundary keeps waiting because runPromise is not given the signal and the authorizer receives no other cancellation mechanism. The same omission exists in the request-bound publish boundary, so an aborted request can remain hung in either authorization phase; run these Effect programs with the request signal so interruption reaches the public Promise.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in #202 (merged as d63dd3b): the request AbortSignal is passed into the openRequest runPromise boundary, so delivery authorization is interrupted on abort instead of hanging. Regression test: a never-resolving authorizer plus an aborted request rejects with AbortError under a timeout guard.

ScriptedAlchemy added a commit that referenced this pull request Sep 1, 2026
…re authorization (#202)

* fix(notices): stable replay deliveries, admission ordering, abort-aware authorization

Post-merge review findings from #184: derive event deliveries from the
current state's attempted receipts so replaying an admitted invocation id
observes the same notice set instead of an empty read; exclude notices
created after the event's startedAt from admission; pass the request
AbortSignal into the openRequest program so delivery authorization cannot
hang past an abort.

* fix(notices): scope replayed deliveries to the requesting principal

Review follow-up on #202: replaying an invocation id reused by a
different principal must not observe the prior principal's notices —
deliveries are filtered through recipientMatchesPrincipal before receipt
matching, so an id match with a foreign recipient is a fresh (empty)
observation, not a replay.
@ScriptedAlchemy
ScriptedAlchemy deleted the wave5/99-notices-core branch September 3, 2026 05:30
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