Sizing note — read this first
Deliberately carved to fit a ~2% budget. One response field, one description sentence, one fixture. Three files, no new tool, no drain instrumentation.
Carved from #16060, which keeps the two larger halves: an actionable signal (pending depth / expected-visible-by) and drain state observable through a tool. Neither is needed for this leaf to be worth landing, and both are bigger than this budget. If the scope grows past three files, stop and say so on this ticket rather than absorbing it — the split exists precisely so it does not have to be absorbed.
Context
add_memory returns success the moment the write is durable. Visibility is deferred until the WAL drains, so an immediate read-back finds nothing — and the caller, holding a success, reasonably concludes the memory was lost. Observed twice against a live cloud deployment: the write had in fact succeeded both times.
The write really is durable. The defect is that the response says accepted while the caller reads queryable, and nothing in the payload distinguishes them.
The Problem
accepted and queryable are different facts with different timings, collapsed into one success. That is a correctness problem in the response contract, not a documentation gap — though the caller who hit it was reading the tool description, so the description matters too.
The failure mode is specifically a wrong conclusion, not a wrong action: an operator or agent decides data was lost and starts investigating a non-existent bug, or re-writes the memory, creating a duplicate.
The Fix
- Extend the
add_memory response so acceptance and queryability are separately legible. A boolean pair, a status enum, or an explicit queryable: false — the shape is the implementer's call, provided a success can no longer be read as "readable now."
- State the read-back caveat in the tool description.
- Pin it with a fixture.
⚠️ Two description tiers — this will bite you if you only edit description. ToolService.buildToolListDescription (ai/mcp/ToolService.mjs:309-320) sources the tools/list line from x-neo-tool-summary → summary → description, capped by toolListDescriptionMaxLength. The long description is served only on an explicit get_mcp_tool_handbook call. So a caveat written into description alone is invisible to every caller who does not fetch the handbook — which is the caller this ticket is about. Two further traps at that seam: a missing key silently falls back, and an over-long line is truncated with ... rather than rejected, so a guard asserting only the opening phrase can pass a half-sentence. Verify the derived line via listTools(), never the YAML text. (Learned the hard way in #16065 / PR #16066 — the fix landed on the tier nobody reads.)
Acceptance Criteria
Out of Scope
- Pending depth, expected-visible-by, or any quantitative signal — #16060.
- A tool that exposes drain state — #16060.
- Changing WAL drain latency or making the write synchronous. The deferral is a design choice; this ticket makes it legible, not absent.
Avoided Traps
- Editing only
description. See the tier warning above; this is the single most likely way to ship a fix that changes nothing for the affected caller.
- Making it sound like a failure. "Not yet queryable" is the truth; "pending" or "incomplete" invites a retry that creates a duplicate.
- Asserting on the YAML. A source-text assertion cannot show which key
ToolService precedence actually selected.
Related
- #16060 — parent; retains the actionable-signal and drain-observability halves
- #16065 / PR #16066 — the two-tier description contract, learned by getting it wrong
ai/mcp/ToolService.mjs:309-333 — both tiers and their precedence
Live latest-open sweep: checked latest 20 open issues at 2026-07-27T23:00:11Z; no equivalent found — #16060 is the parent this is carved from, and the split is recorded in both directions.
Origin Session ID: c038696f-94a6-4788-82bf-747c5672908c
Retrieval Hint: query_raw_memories("add_memory accepted vs queryable WAL drain deferred visibility")
Sizing note — read this first
Deliberately carved to fit a ~2% budget. One response field, one description sentence, one fixture. Three files, no new tool, no drain instrumentation.
Carved from #16060, which keeps the two larger halves: an actionable signal (pending depth / expected-visible-by) and drain state observable through a tool. Neither is needed for this leaf to be worth landing, and both are bigger than this budget. If the scope grows past three files, stop and say so on this ticket rather than absorbing it — the split exists precisely so it does not have to be absorbed.
Context
add_memoryreturns success the moment the write is durable. Visibility is deferred until the WAL drains, so an immediate read-back finds nothing — and the caller, holding a success, reasonably concludes the memory was lost. Observed twice against a live cloud deployment: the write had in fact succeeded both times.The write really is durable. The defect is that the response says accepted while the caller reads queryable, and nothing in the payload distinguishes them.
The Problem
acceptedandqueryableare different facts with different timings, collapsed into one success. That is a correctness problem in the response contract, not a documentation gap — though the caller who hit it was reading the tool description, so the description matters too.The failure mode is specifically a wrong conclusion, not a wrong action: an operator or agent decides data was lost and starts investigating a non-existent bug, or re-writes the memory, creating a duplicate.
The Fix
add_memoryresponse so acceptance and queryability are separately legible. A boolean pair, a status enum, or an explicitqueryable: false— the shape is the implementer's call, provided a success can no longer be read as "readable now."⚠️ Two description tiers — this will bite you if you only edit
description.ToolService.buildToolListDescription(ai/mcp/ToolService.mjs:309-320) sources thetools/listline fromx-neo-tool-summary→summary→ description, capped bytoolListDescriptionMaxLength. The longdescriptionis served only on an explicitget_mcp_tool_handbookcall. So a caveat written intodescriptionalone is invisible to every caller who does not fetch the handbook — which is the caller this ticket is about. Two further traps at that seam: a missing key silently falls back, and an over-long line is truncated with...rather than rejected, so a guard asserting only the opening phrase can pass a half-sentence. Verify the derived line vialistTools(), never the YAML text. (Learned the hard way in #16065 / PR #16066 — the fix landed on the tier nobody reads.)Acceptance Criteria
add_memoryresponse distinguishes accepted from queryable, so a success cannot be read as immediate visibility.tools/listoutput, not the YAML source.Out of Scope
Avoided Traps
description. See the tier warning above; this is the single most likely way to ship a fix that changes nothing for the affected caller.ToolServiceprecedence actually selected.Related
ai/mcp/ToolService.mjs:309-333— both tiers and their precedenceLive latest-open sweep: checked latest 20 open issues at 2026-07-27T23:00:11Z; no equivalent found — #16060 is the parent this is carved from, and the split is recorded in both directions.
Origin Session ID: c038696f-94a6-4788-82bf-747c5672908c
Retrieval Hint:
query_raw_memories("add_memory accepted vs queryable WAL drain deferred visibility")