Round-3 remediation — durable RMM memory (Postgres) + cache-hit request-id so cache-served responses are auditable
2026-08-09
LOCKSTEP TRACEABILITY MATRIX --- api_endpoints: [ "POST /v1/memory/entries", "GET /v1/memory/entries", "GET /v1/governance/artifacts/{request_id}", "none", ] sdk_methods_updated: ["none — no wire-shape change"] mcp_tools_updated: ["br_memory_store/list/query now durable across tasks (no schema change)"] ---
What We Built
Two Round-3 defects, both found by the stochastic review and both now fixed and revert-tested.
- RMM memory is durable and shared across tasks. Every memory surface
(POST /v1/memory/entries, MCP br_memory_store, dashboard, and completions auto-injection) was reading/writing a per-task ephemeral SQLite file — so on the 2-task ECS fleet a write handled by one task was invisible to a read routed to the other (store returned ok+id, list returned total:0 for the owner), and every redeploy wiped it. The durable PostgresRmmStore existed in the tree but was wired nowhere and had three bugs. This wave adds migration v68 (rmm_core_memory + rmm_archival_memory, FORCE RLS, tenant-scoped), fixes the store (RLS-scoped queries, snapshot-scoped delete-reconcile that never clobbers a concurrent task's inserts, awaited flush on close), adds a single withRmm() accessor (EFS → Postgres → ephemeral+warn), and repoints every RMM call site — including removing the injector's fs.existsSync → return null guard so memory_facts is finally non-zero after a store.
- Cache-served responses are auditable. A prompt-cache hit wrote a correct,
cache_hit-tagged
completion_audit row — but under a fresh randomUUID(), while traceContextMiddleware re-asserts the canonical req_ id on the response header _after_ next() returns. So the row the caller could look up by and the row that was written were different ids, and GET /v1/governance/artifacts/{id} 404'd for the id the client held. Fix: the cache-hit path now reuses c.get("requestId") (the canonical id) instead of minting an orphan. Two one-line changes; the artifact assembler already handled lineage-less rows and needed no change.
Why It Matters
Round 3 pinpointed both precisely by triangulation: cost-owner saw the cache-hit $0 ledger row while compliance-officer saw its artifact 404 (one half-landed fix); agent-consumer, claim-auditor, and the auditor all saw memory writes vanish on read-back — a defect so pervasive it undercut tenant-security's isolation proof. These are the two highest-leverage Round-4 fixes.
How It Works
Memory: withRmm(tenantId, fn, {readOnly}) resolves the backend once, runs the unchanged sync RMM logic inside a per-request Postgres load→operate→flush cycle with set_config('app.current_tenant') for RLS; reads skip the flush to keep the completions hot path cheap. Cache-hit: reuse the id the request already owns.
The Numbers
- Migration v68. Memory suite 122 passing; both revert-tests fail against pre-fix behavior and pass after (quoted in the review ledger).
pnpm tsgoclean on the combined tree.
Competitive Edge
The review found these; the platform fixed them and re-measures. Durable, tenant-isolated agent memory that survives task recycling — and cache responses that still land in the evidence chain — is exactly the "prove what happened" thesis holding under its own audit.
Lockstep Checklist
- [x] API Routes: memory + prompt-cache middleware updated; no route add/remove/method change.
- [x] TS SDK / Python SDK: no wire-shape change — none needed.
- [x] MCP Schemas:
br_memory_*behavior fixed (durable); no schema change. - [x] Master Record: no new capability; defects tracked in
docs/reviews/2026-08-08/ledger.json.
Known follow-up: src/api/routes/agentic-completions.ts RMM handle escapes into the streaming agent-run loop and is left on the ephemeral path with a TODO(memory-persistence) — a durability gap on that one write path, tracked for a dedicated fix. Remaining Round-4 clusters (cost-truthfulness, router explain/health, platform-sre alerting, dashboard nav/CORS) are architected and queued.