Context
After the canonical Memory Core plane was rebuilt at exact dev revision 92c0a49fdaa5b1ced9b261e138164eee648f643e, the multi-minute wedge class disappeared but list_messages retained a clean-plane latency floor.
Same-plane receipts:
healthcheck: 65 ms;
get_message on a related-ticket-bearing message: 10 ms;
list_messages filtered to an identity with zero matching rows, limit: 1: 6,806 ms;
- the next
list_messages returning five rows: 7,144 ms.
The empty-result control removes response size, sorting the returned page, and pull-request-state enrichment as explanations for the floor.
To assert which layer owns the remaining cost, I traced and measured the exact deployed path:
- the compact message-WAL marker read took 10 ms;
- the global SQLite gap-count query took 105 ms;
- reading all 3,185 accepted message-WAL records took 50 ms;
- the same deployed store contained 186,643 nodes, 91,732 edges, 3,184 projected message ids, 2,848 distinct
SENT_BY sources, and 85 broadcasts with SENT_TO -> AGENT:* but zero DELIVERED_TO rows.
The global repair predicate is therefore truthy before every list, while the bounded view repair does not make that global predicate converge.
This is the measured production form of the follow-up already identified after closed #15369: a legitimate zero-recipient broadcast is byte-indistinguishable from total delivery-cohort loss, so the then-new zero-cohort predicate can remain true and force a WAL scan on every list.
The Problem
MailboxService.listMessages() pays a global integrity-repair tax before it can answer any caller-scoped mailbox query.
A single non-converging graph/WAL discrepancy anywhere in the plane keeps hasMailboxGraphProjectionGap() true. Every list then:
- reads the accepted message WAL;
- scans until 250 records matching the requested mailbox view have been inspected;
- hydrates each message vicinity;
- runs repeated in-memory edge searches and storage fallbacks;
- only afterwards performs the actual mailbox list traversal.
The repair call is bounded, but it is not progress-bearing. The same caller-visible read can repeatedly inspect the same healthy or non-repairable prefix while the global gate remains open for unrelated historical records. Increasing the SDK timeout would only hide this work amplification.
This floor is distinct from the live server-wide wedge tracked by #16677. It contaminates incident timing and spends several seconds on every ordinary mailbox poll, but it does not explain the historical cross-tool wait cohorts by itself.
The Architectural Reality
The owner is ai/services/memory-core/MailboxService.mjs:
- lines 970-1000:
hasMailboxGraphProjectionGap() reduces several global graph/WAL comparisons to one Boolean;
- lines 1997-2044:
repairMessageGraphIntegrity() opens the full accepted WAL when that Boolean is true and stops after a view-scoped limit;
- lines 2069-2095: every
listMessages() call invokes that repair before listing;
- lines 1009-1052 and 627-655: each inspected record hydrates a graph vicinity and checks delivery edges;
- lines 2119-2250: the list path then traverses the graph again to build the caller result.
get_message is message-id scoped and does not pay the global list repair unless that exact cached projection is damaged. The observed 10 ms versus 6,806 ms control matches that source boundary.
No ChromaDB or embedding call exists in the list_messages path. A slow list_messages row therefore cannot be used as evidence that an embedding-provider wait released the call.
The scoped Agent OS structure map confirms ai/services/memory-core/ as the owning folder; MailboxService.mjs owns the read/repair contract and helpers/messageWalStore.mjs owns accepted-record and projection-marker reads.
The Fix
Replace the non-progress-bearing global-Boolean/read-hot-path composition with a targetable, convergent repair contract:
- Classify graph/WAL discrepancies into exact candidate message ids and reasons, rather than only
gap: true.
- Distinguish a genuinely damaged positive delivery cohort from a legitimate zero-audience or legacy/unknown broadcast. The accepted WAL already carries the send-time audience; an immutable intended-cohort fact may be projected for new records, while historical records need an explicit compatibility disposition.
- Repair exact candidate ids and record progress/residual disposition. Do not rescan the same healthy/non-repairable 250-record prefix on every list.
- Keep caller-scoped
listMessages() bounded. If global reconciliation requires a sweep, schedule/own it outside the ordinary list hot path or consume a bounded candidate index whose work advances.
- Preserve
#15369's real self-heal: deleting every DELIVERED_TO edge from a broadcast with a known positive audience must still be detected and repaired.
- Preserve the current
list_messages request/response schema and authorization boundary.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| Graph/WAL gap classification |
accepted message WAL routing + persisted MESSAGE/SENT_BY/SENT_TO/DELIVERED_TO rows |
Return exact candidate ids, reason, and intended-cohort disposition; no unscoped Boolean that stays permanently true |
Legacy/unknown rows are surfaced for bounded reconciliation, never assumed damaged on every list |
method JSDoc |
positive-damage, zero-audience, legacy/unknown matrix |
repairMessageGraphIntegrity |
candidate classifier + immutable accepted record |
Repair exact ids and advance a cursor/state; repeated healthy reads do not re-inspect the same prefix |
residual failures remain observable and retryable without blocking all mailbox reads |
method JSDoc |
call-count/progress witnesses |
listMessages hot path |
caller identity, box, filters, pagination |
Bounded caller-scoped read; no full-WAL parse or 250-record repair when no target-relevant candidate exists |
explicit bounded failure if caller-relevant integrity cannot be established |
existing MCP docs unchanged |
empty-result and populated-result latency/operation-count controls |
| Intended broadcast cohort |
send-time audience snapshot in message WAL |
Preserve enough immutable intent to distinguish audience-size zero from lost positive cohort |
historical record derives from WAL or receives an explicit unknown disposition |
Anchor-and-Echo JSDoc |
zero, one, multi-recipient, and historical fixtures |
| Public MCP contract |
openapi.yaml list_messages operation |
Request and response remain shape-compatible |
none |
no public doc delta unless behavior note is warranted |
schema smoke + existing mailbox suites |
Decision Record impact
none — this enforces the existing durable mailbox and per-recipient delivery-cohort contract. It does not change service ownership, transport, authorization, or public MCP shape.
Acceptance Criteria
Out of Scope
- The server-wide live-process wedge and shared wait cohorts in #16677.
- Read-state loss / stale-wake reconciliation in #16541.
- Raising MCP or healthcheck timeouts.
- Removing WAL-backed repair or weakening genuine total-cohort-loss recovery.
- ChromaDB, embedding-provider, or model-server tuning.
- A new MCP diagnostic tool.
Avoided Traps
- Every zero cohort is corruption. False: a single-resident deployment can broadcast to an empty audience legitimately.
- Remove the zero-cohort term. That reintroduces the silent total-cohort-loss defect fixed by
#15369.
- Make the global scan larger. More repeated work does not create convergence.
- Blame PR-state enrichment. The zero-result control still took 6,806 ms.
- Blame embeddings. The exact list call graph contains no Chroma/model operation.
- Treat a faster rebuild as repair. Process replacement cleared the wedge class, not this list-path cost.
Related
Successor to #15369
Related: #16677
Related: #16541
Related: #14797
Duplicate and Collision Sweep
- Live latest-open sweep: checked the latest 20 open GitHub issues created-descending immediately before filing; no equivalent ticket existed.
- Recent A2A sweep: checked the latest 30 all-state messages; no overlapping lane claim or intent existed.
- Semantic KB sweep returned adjacent broadcast/read-state/WAL tickets but no ticket owning the non-converging list repair floor.
- Exact repository/GitHub searches for
repairMessageGraphIntegrity, MESSAGE_GRAPH_REPAIR_LIMIT, zero-recipient broadcasts, and full-WAL-per-list behavior found the closed predecessors above, not an open successor.
Origin Session ID: b93c021e-d387-4c4f-8ae5-4d7d2d007303
Retrieval Hint: "Memory Core list_messages empty result 6806ms non-converging global graph gap repair 250 WAL records zero delivery cohort"
Context
After the canonical Memory Core plane was rebuilt at exact
devrevision92c0a49fdaa5b1ced9b261e138164eee648f643e, the multi-minute wedge class disappeared butlist_messagesretained a clean-plane latency floor.Same-plane receipts:
healthcheck: 65 ms;get_messageon a related-ticket-bearing message: 10 ms;list_messagesfiltered to an identity with zero matching rows,limit: 1: 6,806 ms;list_messagesreturning five rows: 7,144 ms.The empty-result control removes response size, sorting the returned page, and pull-request-state enrichment as explanations for the floor.
To assert which layer owns the remaining cost, I traced and measured the exact deployed path:
SENT_BYsources, and 85 broadcasts withSENT_TO -> AGENT:*but zeroDELIVERED_TOrows.The global repair predicate is therefore truthy before every list, while the bounded view repair does not make that global predicate converge.
This is the measured production form of the follow-up already identified after closed
#15369: a legitimate zero-recipient broadcast is byte-indistinguishable from total delivery-cohort loss, so the then-new zero-cohort predicate can remain true and force a WAL scan on every list.The Problem
MailboxService.listMessages()pays a global integrity-repair tax before it can answer any caller-scoped mailbox query.A single non-converging graph/WAL discrepancy anywhere in the plane keeps
hasMailboxGraphProjectionGap()true. Every list then:The repair call is bounded, but it is not progress-bearing. The same caller-visible read can repeatedly inspect the same healthy or non-repairable prefix while the global gate remains open for unrelated historical records. Increasing the SDK timeout would only hide this work amplification.
This floor is distinct from the live server-wide wedge tracked by #16677. It contaminates incident timing and spends several seconds on every ordinary mailbox poll, but it does not explain the historical cross-tool wait cohorts by itself.
The Architectural Reality
The owner is
ai/services/memory-core/MailboxService.mjs:hasMailboxGraphProjectionGap()reduces several global graph/WAL comparisons to one Boolean;repairMessageGraphIntegrity()opens the full accepted WAL when that Boolean is true and stops after a view-scoped limit;listMessages()call invokes that repair before listing;get_messageis message-id scoped and does not pay the global list repair unless that exact cached projection is damaged. The observed 10 ms versus 6,806 ms control matches that source boundary.No ChromaDB or embedding call exists in the
list_messagespath. A slowlist_messagesrow therefore cannot be used as evidence that an embedding-provider wait released the call.The scoped Agent OS structure map confirms
ai/services/memory-core/as the owning folder;MailboxService.mjsowns the read/repair contract andhelpers/messageWalStore.mjsowns accepted-record and projection-marker reads.The Fix
Replace the non-progress-bearing global-Boolean/read-hot-path composition with a targetable, convergent repair contract:
gap: true.listMessages()bounded. If global reconciliation requires a sweep, schedule/own it outside the ordinary list hot path or consume a bounded candidate index whose work advances.#15369's real self-heal: deleting everyDELIVERED_TOedge from a broadcast with a known positive audience must still be detected and repaired.list_messagesrequest/response schema and authorization boundary.Contract Ledger
repairMessageGraphIntegritylistMessageshot pathopenapi.yamllist_messagesoperationDecision Record impact
none— this enforces the existing durable mailbox and per-recipient delivery-cohort contract. It does not change service ownership, transport, authorization, or public MCP shape.Acceptance Criteria
listMessages()calls.DELIVERED_TOcohort is deleted still triggers exact-id repair and reappears correctly for every intended recipient.get_message,mark_read, archive/retraction, broadcast authorization, and thelist_messagesOpenAPI schema remain unchanged.npx playwright testinvocation is used.Out of Scope
Avoided Traps
#15369.Related
Successor to #15369
Related: #16677
Related: #16541
Related: #14797
Duplicate and Collision Sweep
repairMessageGraphIntegrity,MESSAGE_GRAPH_REPAIR_LIMIT, zero-recipient broadcasts, and full-WAL-per-list behavior found the closed predecessors above, not an open successor.Origin Session ID: b93c021e-d387-4c4f-8ae5-4d7d2d007303
Retrieval Hint: "Memory Core list_messages empty result 6806ms non-converging global graph gap repair 250 WAL records zero delivery cohort"