LearnNewsExamplesServices
Frontmatter
id14254
titleVerify drain auto-embed persisted a vector — prevent metadata-only rows
stateClosed
labels
enhancementaitestingarchitecture
assigneesneo-opus-vega
createdAtJun 27, 2026, 6:27 PM
updatedAtJun 27, 2026, 7:02 PM
githubUrlhttps://github.com/neomjs/neo/issues/14254
authorneo-opus-vega
commentsCount0
parentIssue14228
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJun 27, 2026, 7:02 PM

Verify drain auto-embed persisted a vector — prevent metadata-only rows

Closed v13.1.0/archive-v13-1-0-chunk-7 enhancementaitestingarchitecture
neo-opus-vega
neo-opus-vega commented on Jun 27, 2026, 6:27 PM

Context

Leaf slice of #14228 (Prevent metadata-only rows on the Chroma auto-embed write paths). #14228's AC2 names two auto-embed persist surfaces — the embed-drain and SessionService:792/:932. This slice delivers the drain path (the highest-volume, plausibly-primary #13999 mechanism); the parent retains the empirical Chroma-atomicity V-B-A (AC1) + the SessionService direct-upsert sites.

The Problem

ai/daemons/embed/drainCycle.mjs embedBatch persists pending WAL rows via collection.add({ids, metadatas, documents}) — no explicit embeddings, relying on Chroma's collection embedding-function to auto-generate the vector. The cycle treats add-success as embed-success: it re-reads the WAL pending-state (purge-race compensation), not the persisted vector. So a non-atomic auto-embed (provider timeout / oversized input / model-not-resident) persists a metadata-only row AND marks it embedded (appends the WAL embed marker → removed from pending) — the #13999 corruption shape on the highest-volume write path.

The Architectural Reality

  • ai/daemons/embed/drainCycle.mjs embedBatch (collection.add, auto-embed) → drainWalOnce (marker write at the reconcile step).
  • The atomic vector-write invariant classifyRowVector / partitionRowsByVectorValidity (ai/services/memory-core/helpers/vectorWriteInvariant.mjs) already defines vector validity for the explicit-embedding gate (#14029) — reused here as the SSOT predicate, not a parallel check.

The Fix (delivered — branch agent/14228-prevent-metadata-only-drain @ 304007ca7)

  • New verifyEmbeddedVectors({collection, records, expectedDimension, log}) in drainCycle.mjs: after add, reads the persisted vectors back (get({ids, include:['embeddings']})) and classifies each with classifyRowVector.
    • validembedded (reconciled).
    • confirmed metadata-only → deleted + retried (cooldown), never marked embedded (add is create-not-upsert, so the metadata-only row is removed for a clean re-embed).
    • unverifiable (read-back threw) → retried WITHOUT deletion (a transient read error must never destroy possibly-valid rows).
    • opt-in on expectedDimension; absent → verify skipped (logged), prior behavior preserved.
  • drainWalOnce integrates the verify between embed + reconcile; summary.metadataOnly added.
  • Wired in both drain hosts: the embed daemon + the in-process memory-core server, passing vectorDimension.

Acceptance Criteria

  • Post-add vector verify on the drain path: a missing/wrong-dim post-add vector is treated as a failed embed (retry), never a marked-embedded metadata-only row.
  • Confirmed-metadata-only rows are deleted for a clean re-embed; transient read-back failures retry without deleting.
  • Cost-bounded (one batched get per add-batch, not per-row); opt-in on expectedDimension, wired in both drain hosts.
  • Unit coverage: forced non-atomic auto-embed → retry (not a marked-embedded metadata-only row); read-failure safety; opt-in skip; no regression on the legitimate drain path.

Out of Scope

  • The empirical Chroma-atomicity V-B-A (#14228 AC1) — the sandbox cannot force a real provider failure against live Chroma; deferred to the parent. The prevention is defensive-by-design (cheap no-op when auto-embed is atomic; catches the bug when it is not), warranted by the #13999 incident regardless.
  • SessionService:792/:932 direct-upsert sites (#14228 AC2) — different (no-WAL, best-effort, log-not-delete) handling shape; separate slice.
  • The explicit-embedding write path (#14029) + the heal actuator (#14134).

Related

#14228 (parent), #14029 (explicit-embed sibling gate), #14134 (heal actuator), #13999 (incident), #14039 (v13.1 epic).

Live latest-open sweep: checked latest 20 open issues at 2026-06-27T16:25Z; no equivalent drain-slice ticket found.

Authored by Vega (@neo-opus-vega · Claude Opus 4.8, Claude Code). Origin Session ID: 09af5f18-b64c-417b-be84-8cb3305005d2.

Handoff Retrieval Hints: query_raw_memories("drain auto-embed atomicity metadata-only verify-after-add"); anchors: ai/daemons/embed/drainCycle.mjs verifyEmbeddedVectors, commit 304007ca7.

tobiu referenced in commit 22d06a0 - "fix(ai): verify drain auto-embed persisted a vector — prevent metadata-only rows (#14254) (#14255) on Jun 27, 2026, 7:02 PM
tobiu closed this issue on Jun 27, 2026, 7:02 PM