Skip to content

Rehydrate per-session audit verify key on operator restart - #20

Merged
josephschorr merged 1 commit into
mainfrom
fix-operator-restart-audit-key-rehydrate
Oct 6, 2026
Merged

josephschorr merged 1 commit into
mainfrom
fix-operator-restart-audit-key-rehydrate

Conversation

@josephschorr

Copy link
Copy Markdown
Member

Summary

After an operator restart, a session could fail with MemoryUnavailable: ... 403 ... no usable key <keyID> for session:... on its next append-only write (for example, when recording a user turn). This was observed in practice after the operator was OOM-killed and restarted.

Background

The operator keeps each session's memory bearer token and its audit signing (verify) key in process memory, rebuilt from durable state on every reconcile. Both are needed to accept an append-only write: the token authenticates the caller, and the key verifies the provenance signature on what it writes.

The bearer token was restored at the very top of reconcile, on every path. The audit verify key, however, was only re-registered much later in reconcile — past the many early returns that a terminal or transiently-gated session takes. So after a restart the operator would accept a session's token (no 401) but hold no key to verify what that token signed, and the write was rejected with a 403. A terminal session, which short-circuits before the late registration on every subsequent reconcile, could never recover.

Fix

Restore the audit verify key in the same early, every-path step that already restores the bearer token, reading it from the public key the operator anchored on the session's status. Both registration sites now go through a single shared helper so they cannot drift apart.

Testing

  • Added a regression test for the restart scenario: an empty registry (a freshly restarted operator) and a session that short-circuits before the late registration — asserting the verify key is restored anyway.
  • Unit, integration, and e2e suites pass.

The operator's token registry is process memory and starts empty after a
restart. reregisterMemoryToken restored each session's memory bearer token on
every reconcile path, but the per-session audit public key was registered only
at step 4 of Reconcile, below ~30 early returns. A restarted operator therefore
accepted a session's bearer token yet held no key to verify what that bearer
signed, so every append-only write failed provenance verification with
"403 ... no usable key <keyID>". A terminal session, whose reap short-circuits
before step 4 forever, could never recover.

Restore the verify key alongside the token in reregisterMemoryToken, reading it
from status (the same source step 4 uses) through a shared registerAuditVerifyKey
helper so the two registration sites cannot drift.
@vercel

vercel Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
openagentprimitives Ready Ready Preview Oct 5, 2026 9:24pm UTC

Request Review

@josephschorr
josephschorr merged commit 2769864 into main Oct 6, 2026
9 checks passed

This branch was successfully deployed

1 active deployment
Preview — b6be6fc1 Deployed Oct 5, 2026 by vercel[bot]
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