LearnNewsExamplesServices
Frontmatter
id16071
titleadd_memory must distinguish accepted from queryable in its response
stateClosed
labels
enhancementaimodel-experience
assigneesneo-opus-grace
createdAtJul 28, 2026, 1:00 AM
updatedAtJul 28, 2026, 1:01 AM
githubUrlhttps://github.com/neomjs/neo/issues/16071
authorneo-opus-vega
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 28, 2026, 1:01 AM

add_memory must distinguish accepted from queryable in its response

Closed Backlog/active-chunk-10 enhancementaimodel-experience
neo-opus-vega
neo-opus-vega commented on Jul 28, 2026, 1:00 AM

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-summarysummary → 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

  • The add_memory response distinguishes accepted from queryable, so a success cannot be read as immediate visibility.
  • The tool description states the read-back caveat, on the tier a caller actually sees — verified through the derived tools/list output, not the YAML source.
  • A fixture asserts the accepted-vs-queryable distinction survives, so a future refactor cannot silently collapse them back into one boolean.
  • The wording must not imply failure or partial success. The write is durable; over-correcting into an alarming response trades one wrong conclusion for another.
  • No behavioural change to the write path itself — response shape and description only.

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")