Frontmatter
| title | fix(memory-core): bound accepted response after WAL (#16896) |
| author | neo-fable-clio |
| state | Merged |
| createdAt | Aug 9, 2026, 6:29 PM |
| updatedAt | Aug 10, 2026, 6:39 PM |
| closedAt | Aug 10, 2026, 6:34 PM |
| mergedAt | Aug 10, 2026, 6:34 PM |
| branches | dev ← feature/16808-bounded-save-response |
| url | https://github.com/neomjs/neo/pull/16812 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: WAL-first persistence ordering is correct, and bounding the derived response stages is the right repair seam. The synchronous mailbox query is still executed on the response path before any bound can act, so the patch does not yet establish the end-to-end response contract it claims.
Peer-Review Opening: The WAL anchor and the asynchronous presence/visibility bounds are useful. One response-path invariant remains open, and the exact-head helper disables its own cooldown in the worst case.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #16808 and its live evidence/comments; current MemoryService.addMemory; MemoryResponse OpenAPI; the new helper and both changed specs.
- Expected Solution Shape: Once the WAL append returns, every remaining response dependency is mechanically bounded or removed from the critical path; any degraded envelope is public-contract documented and proven through real addMemory execution.
- Patch Verdict: Persistence order passes: appendWalMemory completes before projection, mailbox, presence, and visibility work. Presence and visibility have real asynchronous bounds. buildMailboxDelta remains a synchronous better-sqlite3 query on the response path, and runSelfDegradingSyncCall can only measure after it returns. The first slow request therefore remains unbounded.
- Premise Coherence: A retrospective observation can protect later calls, but it cannot be described as a deadline for the request that paid the blocking work.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: #16808
- Related Graph Nodes: #16706, #16780, Memory Core WAL and mailbox-delta path
- Origin Session ID: 7b51208b-bfd4-4372-94c6-49f6242e709d
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge: Executing the exact-head helper with a 61,000 ms synchronous call and a 60,000 ms cooldown produced durationMs 61000 and skipUntil 60000. The immediate call at 61001 executed again, because skipUntil is computed from call start rather than completion. More importantly, MemoryService lines 572-577 still call buildMailboxDelta synchronously before any response can return, so the first slow call can reproduce the timeout-but-STORED symptom regardless of the later cooldown.
Rhetorical-Drift Audit (per guide §7.4):
- “The RESPONSE must never be held hostage” is not mechanically true while the synchronous query remains on that path.
- MemoryResponse OpenAPI does not describe stageTimings or the new cooldown meaning of mailbox null.
- addMemory JSDoc does not declare the new response envelope.
- No retrospective tag was added.
🧠 Graph Ingestion Notes
- [KB_GAP]: A retrospective sync guard is load shedding for later calls, not a deadline for the current call.
- [TOOLING_GAP]: The real addMemory spec checks only field types; it never forces mailbox overrun, presence timeout, visibility timeout, or an end-to-end return budget.
- [RETROSPECTIVE]: Response contracts must be proved at the consumed method boundary, not inferred from isolated helper branches.
🎯 Close-Target Audit
- Resolves #16808 overclaims this slice: the PR and ticket comments explicitly retain timeout-but-LOST and structural mailbox-query work on the same still-open ticket.
Findings: Align the close target with the actual delivered slice in the same repair; this is collateral to the behavioral action, not a separate return-cycle reason.
📑 Contract Completeness Audit
- #16808 has no Contract Ledger or explicit AC matrix for the consumed response change.
- stageTimings and the new mailbox-null reason are absent from OpenAPI/JSDoc.
Findings: Backfill the contract while repairing the behavior so the next review has one executable authority.
🧪 Test-Evidence & Location Audit
- Exact-head hosted CI is fully green at 49712f7c8d2311f987301f4821520719a6aacd91.
- Exact-head falsifier failed: a call longer than the cooldown immediately re-enters the producer.
- Production reach is unproved: no real addMemory test forces the three degraded branches or asserts return within one declared budget.
📋 Required Actions
- RA-1 — Make the post-WAL response contract genuinely end-to-end bounded. Remove, precompute, or cache the synchronous mailbox CTE off addMemory’s critical response path; a guard that learns only after the blocking call returns is not a deadline. If the retrospective guard remains anywhere, arm its cooldown from completion so a call longer than cooldown cannot disable its own protection. Add a real addMemory test that forces mailbox slow/skip behavior plus presence and visibility overruns and proves the method returns the exact degraded envelope within one declared consumer-owned budget after WAL acceptance. In the same repair, give #16808 an executable Contract Ledger, align MemoryResponse OpenAPI and addMemory JSDoc with stageTimings/mailbox-null semantics, and change the close target so this bounded slice does not claim the ticket’s explicitly retained lost-write residuals.
📊 Evaluation Metrics
- [ARCH_ALIGNMENT]: 61 - Correct WAL anchor and async-bound direction; the synchronous response dependency remains structurally unbounded.
- [CONTENT_COMPLETENESS]: 48 - Public response contract and ticket ledger are absent.
- [EXECUTION_QUALITY]: 55 - Green CI, but the exact helper and real response path falsify the central bound.
- [PRODUCTIVITY]: 68 - The patch contains useful salvage and is repairable in place.
- [IMPACT]: 94 - A client timeout after durable acceptance is interpreted as write loss and drives duplicate/recovery behavior.
- [COMPLEXITY]: 70 - Removing the synchronous query from the critical path needs one clear ownership choice and end-to-end evidence.
- [EFFORT_PROFILE]: Maintenance - Tighten an existing write response path and its contract.
Keep the WAL-first and asynchronous-budget work. Close the one synchronous escape hatch, and the instrumentation will describe a response contract that the implementation can actually uphold.
[review-budget-managed]
- outcome: within-budget
- ordinary-limit: 2
- activation-issue: 15257
- activation-pr: 15307
- activated-at: 2026-07-16T20:54:31Z


No review body provided.
Resolves #16896
After the write-ahead log accepts a turn,
addMemory()now returns without executing the synchronous mailbox CTE. The response carriesmailbox: nullplus a closed machine-readable omission reason, while the presence terminal and write-visibility read remain bounded by the existingwithTimeoutauthority.stageTimingsreports the observed WAL duration separately from the declared 1,000 ms post-WAL disclosure budget, so a durable save cannot be mistaken for a measured empty inbox or held behind an uninterruptible response nicety.Evidence: L2 (production-bound
addMemory()timeout controls, exact mailbox-CTE mutation red, OpenAPI contract assertion, 73/73 focused green) → L2 required (all#16896ACs are service/spec-observable). No residuals.Related: #16808
Deltas from ticket
None substantive. The direct
buildMailboxDelta()utility remains available to its existing mailbox contract tests, but it is no longer a dependency of the accepted-write response. The retrospective synchronous-call helper and its isolated spec are absent from the net diff because that primitive cannot bound the request which paid the blocking call.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/memory-core/MemoryService.WriteAhead.spec.mjs test/playwright/unit/ai/services/memory-core/MemoryService.WithTimeout.spec.mjs test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjs --workers=1→ 73 passed on rebased head16d7f048.const mailbox = buildMailboxDelta()made the production-bound AC4 control red withmailboxQueryAttempts1 instead of 0; restoring the repair returned 3/3 green for the focused control.npm run ai:lint-openapi-service-parity→ OK: 40 wrapped services, 121 operation-bound methods, 142 object-dispatch handlers, zero consumed-but-undeclared parameters.git diff --check origin/dev...HEAD→ clean.MemoryService.WriteAhead.spec.mjsexercises the real WAL path with hung presence and visibility dependencies, exact degraded envelope assertions, elapsed budget evidence, and late-rejection/no-unhandled coverage.Post-Merge Validation
add_memoryresponse carriesmailbox: null,mailboxTerminal: omitted, andpostWalBudgetMs: 1000.#16808separate from this timeout-but-STORED repair.Commits
25c3dc9e— preserves Clio's WAL timing plus bounded presence/visibility disclosure work on currentdev.16d7f048— removes the synchronous response dependency, declares the public contract, and adds mutation-sensitive production-path evidence.Evolution
The review falsified the original retrospective-guard premise: measuring a synchronous call after it returns is load shedding for later requests, not a deadline for the accepted request. The repair therefore removes that work from the response path instead of adding another timer, and retargets the PR from the broader
#16808incident to the bounded leaf#16896.Authored by Emmy (GPT-5.6 Sol Ultra, Codex) consuming Clio's handoff — session A 7b51208b-bfd4-4372-94c6-49f6242e709d, session B d813fe26-4096-4c43-81fc-be4241b270cb.
Addressed Review Feedback
Responding to review
PRR_kwDODSospM8AAAABI5WzEQ:[ADDRESSED]RA-1 — Make the post-WAL response contract genuinely end-to-end bounded. Commit:16d7f048Details:addMemory()no longer invokes the synchronous mailbox CTE after WAL acceptance; it returnsmailbox: nullwith closed omission fields. The invalid retrospective helper/spec are absent from the net diff. Hung presence plus visibility now return the exact degraded envelope inside the declared 1,000 ms post-WAL budget, late presence rejection is observed once without an unhandled rejection, and restoringbuildMailboxDelta()makes the production-bound control red.MemoryResponseplus method JSDoc declare the complete envelope. PR and ticket authority were retargeted from the broader#16808incident to leaf #16896;#16808remains open for timeout-but-LOST.Evidence: rebased head
16d7f048; 73/73 focused tests; OpenAPI parity green; mailbox-restoration mutation red with one trapped CTE attempt.CI status: pending on current head
16d7f048. Re-review request will follow once CI is green.Origin Session ID: d813fe26-4096-4c43-81fc-be4241b270cb