Context
add_message already appends its canonical message intent to the durable message WAL before graph projection. However, the current MCP handler still awaits that derived projection before returning the successful receipt. During the #16677 Memory Core saturation incidents, a caller can therefore time out after durable acceptance but before receiving the acknowledgement, leaving the operator unable to distinguish “not written” from “written; response lost.”
This is the unclosed edge of resolved predecessor #13891 / PR #13898: WAL-before-ack shipped, but graph work can still withhold the ack. The predecessor stays closed; this successor owns only the remaining receipt boundary.
The Problem
MailboxService.addMessage() awaits appendWalMessage() and then awaits _projectMessageWalRecord(). Projection failures are converted to projectionStatus: pending, but a slow or stuck projection does not fail—it simply holds the MCP request open. A transport timeout then carries no disposition even though the accepted record may already be durable. Blind retry can duplicate a message; assuming failure can discard a continuity record; hand-verifying every write is the current runbook mitigation.
The defect is not that the WAL or replay topology is absent. It is that the public acknowledgement boundary remains coupled to derived graph work despite the WAL being the acceptance authority.
The Architectural Reality
ai/services/memory-core/MailboxService.mjs owns message validation, canonical WAL admission, graph projection, and the direct-call compatibility contract.
ai/mcp/server/memory-core/toolService.mjs owns the public MCP dispatch boundary and is the narrow place to elect deferred projection without changing internal callers.
ai/services/memory-core/helpers/messageWalStore.mjs owns accepted records and graph-projection markers.
ai/daemons/message/ plus the Memory Core in-process message drain own retry of pending WAL records after process exit or projection failure.
ai/mcp/server/memory-core/openapi.yaml owns the tool-visible acknowledgement semantics.
The Agent OS structure map was run immediately before filing. It confirms these existing owners; no new file or service boundary is introduced.
The Fix
- Add an explicit
deferProjection option to MailboxService.addMessage(), defaulting to false so internal callers retain immediate projection.
- Bind only the MCP
add_message handler with deferProjection: true.
- After the awaited WAL append, schedule the ordinary idempotent projector on the next event-loop turn and immediately return
status: sent plus projectionStatus: pending.
- Keep the message-WAL drain as the durable retry owner if the process exits or the best-effort scheduled projection fails.
- Document that a successful MCP receipt proves WAL durability, not immediate graph visibility.
- Add a production-bound test that pauses the real projector and proves the receipt and WAL record exist first; then release projection and prove the pending marker retires.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
MCP add_message receipt |
toolService.mjs binding + MailboxService.addMessage() |
Return only after WAL append, without awaiting derived projection |
Pre-WAL validation/append errors still reject; post-WAL projection reports pending |
OpenAPI operation + service JSDoc |
Paused production-projector control |
Direct MailboxService.addMessage() callers |
Existing service method contract |
Preserve immediate projection by default |
Existing projection failure remains accepted/pending |
Method JSDoc |
Full MailboxService suite |
| Deferred graph projection |
Message WAL record + projection marker |
Project on next event-loop turn; retire pending record only after delivery-critical graph state exists |
In-process/daemon WAL drain retries after failure or process exit |
Scheduler JSDoc |
Projection-enter/release + pending-drain assertions |
| Receipt vocabulary |
Memory Core OpenAPI |
status: sent means durably accepted; projectionStatus: pending means derived visibility/wake may lag |
No claim of immediate read-after-write graph visibility |
openapi.yaml |
OpenAPI validator + service parity lint |
Decision Record impact
none — this introduces no diagnosis/action class and no deployment topology. It preserves the accepted message-WAL/replay design and changes only when its public receipt is released.
Acceptance Criteria
Out of Scope
- Root-cause classification, mailbox-query bounding, health-path responsiveness, or container recovery for
#16677.
add_memory latency and its additional post-WAL dependencies; that remains #16808 / PR #16812 authority.
- Changing message WAL format, replay idempotency, delivery-edge semantics, or wake policy.
- Blindly retrying any timed-out write.
Avoided Traps
- A successful receipt means immediately queryable. It means durably accepted; graph visibility and wake delivery may still be pending.
- A timeout means the write failed. No receipt is ambiguous and must remain so; this patch reduces the post-WAL ambiguity window but cannot classify every transport failure.
- Make every caller asynchronous. Only the MCP boundary needs early acknowledgement; internal consumers retain the established immediate-projection behavior.
- Reopen the resolved predecessor. Regressions and missed contract edges get a linked successor, never a reopened resolved ticket.
Related
Related: #16677
Related: #13891
Related: #13892
Related: #16808
Origin Session ID: 878f05af-2c4e-4da2-a5c2-9e4af666fcb8
Retrieval Hint: "add_message WAL durable receipt before graph projection timeout ambiguous write #16677"
Creation-gate record: live latest-20 open sweep at 2026-08-09T23:14Z found no equivalent; all-state A2A latest-30 found no competing claim; Knowledge Base semantic and local exact sweeps found resolved predecessor #13891 plus replay sibling #13892, not an open successor.
Context
add_messagealready appends its canonical message intent to the durable message WAL before graph projection. However, the current MCP handler still awaits that derived projection before returning the successful receipt. During the#16677Memory Core saturation incidents, a caller can therefore time out after durable acceptance but before receiving the acknowledgement, leaving the operator unable to distinguish “not written” from “written; response lost.”This is the unclosed edge of resolved predecessor
#13891/ PR#13898: WAL-before-ack shipped, but graph work can still withhold the ack. The predecessor stays closed; this successor owns only the remaining receipt boundary.The Problem
MailboxService.addMessage()awaitsappendWalMessage()and then awaits_projectMessageWalRecord(). Projection failures are converted toprojectionStatus: pending, but a slow or stuck projection does not fail—it simply holds the MCP request open. A transport timeout then carries no disposition even though the accepted record may already be durable. Blind retry can duplicate a message; assuming failure can discard a continuity record; hand-verifying every write is the current runbook mitigation.The defect is not that the WAL or replay topology is absent. It is that the public acknowledgement boundary remains coupled to derived graph work despite the WAL being the acceptance authority.
The Architectural Reality
ai/services/memory-core/MailboxService.mjsowns message validation, canonical WAL admission, graph projection, and the direct-call compatibility contract.ai/mcp/server/memory-core/toolService.mjsowns the public MCP dispatch boundary and is the narrow place to elect deferred projection without changing internal callers.ai/services/memory-core/helpers/messageWalStore.mjsowns accepted records and graph-projection markers.ai/daemons/message/plus the Memory Core in-process message drain own retry of pending WAL records after process exit or projection failure.ai/mcp/server/memory-core/openapi.yamlowns the tool-visible acknowledgement semantics.The Agent OS structure map was run immediately before filing. It confirms these existing owners; no new file or service boundary is introduced.
The Fix
deferProjectionoption toMailboxService.addMessage(), defaulting tofalseso internal callers retain immediate projection.add_messagehandler withdeferProjection: true.status: sentplusprojectionStatus: pending.Contract Ledger
add_messagereceipttoolService.mjsbinding +MailboxService.addMessage()MailboxService.addMessage()callersstatus: sentmeans durably accepted;projectionStatus: pendingmeans derived visibility/wake may lagopenapi.yamlDecision Record impact
none— this introduces no diagnosis/action class and no deployment topology. It preserves the accepted message-WAL/replay design and changes only when its public receipt is released.Acceptance Criteria
add_messagehandler returns success only after the exactMESSAGE:*record is readable from the message WAL.status: sentandprojectionStatus: pending.MESSAGEgraph node is required to exist when that durable receipt returns.Out of Scope
#16677.add_memorylatency and its additional post-WAL dependencies; that remains#16808/ PR#16812authority.Avoided Traps
Related
Related: #16677
Related: #13891
Related: #13892
Related: #16808
Origin Session ID: 878f05af-2c4e-4da2-a5c2-9e4af666fcb8
Retrieval Hint: "add_message WAL durable receipt before graph projection timeout ambiguous write #16677"
Creation-gate record: live latest-20 open sweep at 2026-08-09T23:14Z found no equivalent; all-state A2A latest-30 found no competing claim; Knowledge Base semantic and local exact sweeps found resolved predecessor
#13891plus replay sibling#13892, not an open successor.