LearnNewsExamplesServices
Frontmatter
id16896
titleBound add_memory response work after WAL acceptance
stateClosed
labels
bugaiperformance
assigneesneo-gpt-emmy
createdAtAug 10, 2026, 5:47 PM
updatedAtAug 10, 2026, 6:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/16896
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 10, 2026, 6:34 PM

Bound add_memory response work after WAL acceptance

Closed Backlog/active-chunk-15 bugaiperformance
neo-gpt-emmy
neo-gpt-emmy commented on Aug 10, 2026, 5:47 PM

Context

#16808 captured two different add_memory timeout classes on the same contended plane: timeout-but-STORED after the WAL accepted the turn, and timeout-but-LOST before durable acceptance could be verified. PR #16812 correctly moved the presence and visibility disclosures toward bounded waits, but its first review falsified the claimed end-to-end response bound: buildMailboxDelta() is a synchronous better-sqlite3 CTE executed after WAL acceptance and before the response, so a slow first call can still hold an accepted save beyond the client transport deadline.

The proposed retrospective guard learns only after that blocking call returns. An exact helper probe also showed its 60-second cooldown can disable itself: a 61-second call arms skipUntil from call start, so the immediate next call already runs again.

This ticket is the one-PR post-WAL response slice. #16808 remains open for the distinct pre-WAL timeout-but-LOST class and its plane-side instrumentation.

Structure-map gate: npm run --silent ai:structure-map -- --files --loc was attempted on 2026-08-10 and failed with Cannot create a string longer than 0x1fffffe8 characters. Placement was therefore verified directly against the existing owners: MemoryService.addMemory, helpers/withTimeout.mjs, the MCP MemoryResponse schema, and MemoryService.WriteAhead.spec.mjs. This repair introduces no new module.

The Problem

Once appendWalMemory() returns, the turn is durable and immediately available to recency reads. The remaining envelope work is derived disclosure. Today it still contains three response dependencies:

  • buildMailboxDelta() — synchronous SQLite work that cannot be interrupted by Promise.race or withTimeout;
  • TurnPresenceService.recordTurnPresence() — an asynchronous graph terminal;
  • describeWriteVisibility() — an asynchronous pending-WAL visibility read.

Bounding only the latter two leaves the first accepted request unbounded. Calling the synchronous work “self-degrading” does not change that request’s latency contract; it only sheds later calls, and its current start-anchored cooldown can fail to shed even those.

The Architectural Reality

MemoryService.addMemory() owns the accepted-write response. Its mandatory contract ends at the WAL append; graph projection, mailbox awareness, presence, and semantic-visibility reporting are derived facts. The mailbox delta is a convenience field, not durability proof. When a response nicety cannot be bounded on the Node event loop, honest absence is safer than executing it on the acceptance path.

The existing withTimeout.mjs helper is the source-owned bound for asynchronous dependencies. MemoryResponse in ai/mcp/server/memory-core/openapi.yaml and the addMemory() JSDoc are the consumed contract authorities. The production write-ahead spec is the proof boundary; an isolated helper test cannot establish the method’s elapsed response bound.

The Fix

  1. Remove buildMailboxDelta() from the post-WAL response path. Return mailbox: null and an explicit stage-timing/disposition field stating that mailbox enrichment was omitted from the accepted-write latency contract. Delete the now-unused retrospective helper and its isolated spec.
  2. Retain a bounded presence terminal and bounded visibility read using the existing withTimeout authority. Late presence settlement remains observed/handled; visibility timeout returns the existing honest per-axis degraded envelope.
  3. Measure WAL and each post-WAL stage without claiming the WAL itself is bounded by this ticket.
  4. Declare stageTimings, presenceTerminal, and mailbox omission semantics in addMemory() JSDoc and the MCP MemoryResponse schema.
  5. Add a production-bound addMemory() control with hung presence and visibility dependencies that proves the method returns the exact degraded envelope within one declared post-WAL budget. The control must also convict any reintroduction of the synchronous mailbox query on that path.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
add_memory.mailbox MemoryService.addMemory() no synchronous mailbox query after WAL acceptance null plus explicit omitted reason method JSDoc + OpenAPI production-bound elapsed-response control
add_memory.stageTimings MemoryService.addMemory() numeric WAL/visibility/presence measurements and closed dispositions null only where a stage was deliberately not run method JSDoc + OpenAPI exact envelope assertions
presence terminal TurnPresenceService.recordTurnPresence() through withTimeout completed, deferred, or failed without rejecting the accepted save late promise remains handled method JSDoc + OpenAPI hung/failing/complete controls
write visibility describeWriteVisibility() through withTimeout measured when timely honest embed-state-unavailable, while recency remains true by construction method JSDoc + OpenAPI hung/failing/complete controls

Decision Record impact

None. This tightens an existing WAL-first response contract and reuses the existing timeout authority; it introduces no new service boundary, config surface, or architectural primitive.

Acceptance Criteria

  • No synchronous mailbox SQLite work executes after WAL acceptance and before the add_memory response.
  • The response retains mailbox: null with a machine-readable omission reason; absence cannot masquerade as a measured empty inbox.
  • Hung presence and visibility dependencies cannot hold the accepted response beyond the declared post-WAL budget.
  • Late presence rejection is handled exactly once with no unhandled rejection.
  • stageTimings and terminal/fallback enums are declared in addMemory() JSDoc and MemoryResponse OpenAPI.
  • A production-bound addMemory() test proves the elapsed bound and exact degraded envelope; restoring buildMailboxDelta() to the path makes it red.
  • Existing WAL-first durability, recency visibility, and successful fast-path disclosure controls remain green.

Implementation Receipt

  • Delivered by PR #16812 at 16d7f048.
  • Focused production/contract slice: 73/73 passed after rebasing onto current dev.
  • Restoring the synchronous mailbox producer made the exact production-bound control red (mailboxQueryAttempts: 1); the repaired head returned green.
  • OpenAPI service parity: OK, zero consumed-but-undeclared parameters.

Out of Scope

  • The pre-WAL timeout-but-LOST class and payload/transport/fsync instrumentation; #16808 retains that incident owner.
  • Redesigning or indexing the mailbox CTE, or adding a cached mailbox-delta producer. That can restore enrichment later without returning synchronous work to this path.
  • Embed-drain scheduling and work-volume bounds owned by #16780.
  • A deployment-configurable response budget; this is a consumer contract, not an operator tuning leaf.

Avoided Traps

  • A retrospective timer is not a deadline for the request that paid the synchronous work.
  • Green helper tests do not prove the consumed addMemory() boundary.
  • A completed result does not prove bounded delivery; elapsed response and exact fallback shape must be measured together.
  • Do not call timeout-but-STORED and timeout-but-LOST one mechanism merely because both surface as -32001.

Related

  • #16808 — parent symptom record; retains timeout-but-LOST
  • #16780 — adjacent embed-work and saturation observability
  • PR #16812 — implementation branch to retarget to this ticket
  • PR review PRR_kwDODSospM8AAAABI5WzEQ — exact-head falsifier and required action

Origin Session ID: d813fe26-4096-4c43-81fc-be4241b270cb

Retrieval Hint: "add_memory post-WAL synchronous mailbox CTE timeout-but-STORED stageTimings"

tobiu referenced in commit 63da0ea - "fix(memory-core): bound accepted response after WAL (#16896) (#16812) on Aug 10, 2026, 6:34 PM
tobiu closed this issue on Aug 10, 2026, 6:34 PM