LearnNewsExamplesServices
Frontmatter
id16842
titleReturn add_message after WAL append, before graph projection
stateClosed
labels
bugaiperformanceagent-os
assigneesneo-gpt-emmy
createdAtAug 10, 2026, 1:15 AM
updatedAtAug 10, 2026, 1:50 AM
githubUrlhttps://github.com/neomjs/neo/issues/16842
authorneo-gpt-emmy
commentsCount0
parentIssue16677
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 10, 2026, 1:50 AM

Return add_message after WAL append, before graph projection

Closed Backlog/active-chunk-14 bugaiperformanceagent-os
neo-gpt-emmy
neo-gpt-emmy commented on Aug 10, 2026, 1:15 AM

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

  1. Add an explicit deferProjection option to MailboxService.addMessage(), defaulting to false so internal callers retain immediate projection.
  2. Bind only the MCP add_message handler with deferProjection: true.
  3. After the awaited WAL append, schedule the ordinary idempotent projector on the next event-loop turn and immediately return status: sent plus projectionStatus: pending.
  4. Keep the message-WAL drain as the durable retry owner if the process exits or the best-effort scheduled projection fails.
  5. Document that a successful MCP receipt proves WAL durability, not immediate graph visibility.
  6. 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

  • The MCP add_message handler returns success only after the exact MESSAGE:* record is readable from the message WAL.
  • With the real graph projector paused indefinitely, the MCP receipt returns within a bounded test deadline with status: sent and projectionStatus: pending.
  • No MESSAGE graph node is required to exist when that durable receipt returns.
  • Releasing the scheduled projector creates the graph projection and retires the record from the pending WAL index.
  • Projection failure or process exit leaves WAL-backed retry work; it cannot erase the successful acceptance record.
  • Direct service callers keep their existing immediate-projection default.
  • OpenAPI states the durability-versus-visibility boundary.
  • Removing only the MCP defer binding makes the receipt-order witness red.

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.

tobiu closed this issue on Aug 10, 2026, 1:50 AM
tobiu referenced in commit 563cbcb - "feat(memory-core): release message receipt at WAL boundary (#16842) (#16845)" on Aug 10, 2026, 1:50 AM