LearnNewsExamplesServices
Frontmatter
id12879
titleGuard the add_memory write path against empty-content memories
stateClosed
labels
bugaiarchitectureneeds-re-triagemodel-experience
assigneesneo-gpt
createdAtJun 11, 2026, 1:46 AM
updatedAt3:19 PM
githubUrlhttps://github.com/neomjs/neo/issues/12879
authorneo-opus-ada
commentsCount4
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJun 13, 2026, 2:21 AM

Guard the add_memory write path against empty-content memories

Closed v13.1.0/archive-v13-1-0-chunk-1 bugaiarchitectureneeds-re-triagemodel-experience
neo-opus-ada
neo-opus-ada commented on Jun 11, 2026, 1:46 AM

Context

Spun out of the #12830 corrupted-memory investigation (AC#3 write-source trace by @neo-opus-ada — the findings comment on #12830, 2026-06-10). The Memory Core holds a meaningful population of empty-content AGENT_MEMORY rows (prompt/thought/response empty / whitespace). They are not cosmetic: they inflate session-summary input (a contributor to the 30-min summarization stalls) and distort backlog/cost accounting. The investigation traced the write source: MemoryService.addMemory has no non-empty validation anywhere on its write path, so an add_memory call with empty content silently persists a corrupted memory + a meaningless vector. This ticket is the prevention half (stop new corruption). The gated cleanup of the existing set is a separate ticket, blocked on #12830's AC#2 measurement.

The Problem

add_memory is the never-fail durable write — but "never-fail" currently also means "never validates": empty / whitespace prompt+thought+response are accepted and embedded. Three trigger paths were code-evidenced (#12830 AC#3):

  1. Boot-heartbeat (strongest, by design). ai/scripts/lifecycle/resumeHarness.mjs:307 instructs every resumed agent to "call add_memory once as a boot heartbeat" — a designed low-content save on every resume cycle, harness-agnostic. Likely the dominant source, and the reason a naive empty-reject would regress a legitimate path.
  2. Agent-loop placeholder. ai/agent/Loop.mjs:454 writes thought/response with placeholder fallbacks but prompt: event.data with no fallback — a gray contributor on low-content turns.
  3. Direct MCP add_memory. ai/mcp/server/memory-core/toolService.mjs:18 binds the tool with no validation wrapper — the path external harnesses (incl. Codex) call; the operator-flagged recent @neo-gpt corruption skew points here.

The Architectural Reality

  • Write-gap (fix locus): ai/services/memory-core/MemoryService.mjs addMemoryL318 (combinedText embed-doc build), L327-334 (metadata), L353-357 (collection.add). No mandatory / min-length guard on prompt/thought/response.
  • (Note: the embed doc is always label-non-empty — "User Prompt: \nAgent Thought: \nAgent Response:" — so a document === '' check is useless; the guard MUST inspect the raw prompt/thought/response inputs.)
  • Raw content lives in the Chroma neo-agent-memory document/metadata, keyed by semanticVectorId; the SQLite Nodes row holds only metadata.
  • The load-bearing constraint: the guard cannot blanket-reject empty content — that would break the legitimate boot-heartbeat (path 1).

The Fix

  1. Add a non-empty / min-length guard to MemoryService.addMemory over the combined prompt+thought+response (reject or fail-soft a genuinely-empty save) at the write-path locus above; covers the direct-MCP (3) and agent-loop (2) paths uniformly.
  2. Fix the boot-heartbeat to write a meaningful-minimal liveness memory (resumeHarness.mjs:307) — a structured boot-marker with non-empty content — so the guard can be strict without regressing liveness.

Contract Ledger

Surface Source of Authority Proposed Fallback Consumer
add_memory empty-content input MemoryService.addMemory (MemoryService.mjs:318) Guard: reject / fail-soft a genuinely-empty (prompt+thought+response) save Documented error to caller; never a silent empty persist agents (all harnesses)
Boot-heartbeat write resumeHarness.mjs:307 Write a meaningful-minimal liveness memory (non-empty) n/a orchestrator / resumed agents

Decision Record impact

none directly. Embodies the general "no silent empty/placeholder write" integrity principle #12830 names.

Out of Scope

  • Measuring the existing corrupted set (#12830 AC#2 — the Chroma metadata scan; embedder-gated).
  • Deleting the existing corrupted set (separate gated-cleanup ticket; blocked on #12830 AC#2 + this prevention).
  • The session-summary synthesis abort-guard (the OTHER half of the 30-min stall — SessionService.mjs:576 has no timeout; routed to the session-summary-stall lane, sibling to #12831 / #12805).

Avoided Traps

  • document === '' check — useless; addMemory always builds a label-non-empty embed doc. Inspect the raw inputs.
  • Blanket empty-reject — regresses the designed boot-heartbeat (path 1); the heartbeat must be redesigned to write meaningful content first.
  • Dedicated off-AGENT_MEMORY liveness channel as the primary fix — heavier wire/schema change; the meaningful-minimal heartbeat memory closes the gap with far less blast radius. Revisit only if heartbeat volume itself becomes a cost concern.

Related

  • #12830 — Root-cause & characterize corrupted memories (parent investigation; this is its AC#3-handoff prevention spinout). Implementation offered to @neo-opus-grace per the AC#3 handoff (memory-integrity domain + the #12450 sibling).
  • #12450 — query_summaries empty: corrupt sessions vectors (sibling memory-integrity defect, different collection).
  • #10063 — Claude Code hook auto-persist turn memories (a candidate write path).
  • Gated-cleanup ticket — TO FOLLOW, blocked on #12830 AC#2.

Release classification: post-v13 (prevention follow-up; the corrupted-memory family is post-v13 per the v13 board triage in #12696).

Origin Session ID: fa0b47bc-e066-4e8b-8aec-a9de8fc69a09

Handoff Retrieval Hints:

  • Retrieval Hint: "add_memory empty content validation guard MemoryService addMemory boot-heartbeat resumeHarness corrupted memory prevention"
  • Fix locus: MemoryService.mjs addMemory L318 / L327-334 / L353-357; boot-heartbeat resumeHarness.mjs:307.