Context
V-B-A under #12065 (3 comments) found the root cause of the persistently-stuck mini-summary backfill, sized from live data: of 12,496 AGENT_MEMORY graph nodes, 4,347 are pending (miniSummary IS NULL); 3,327 (77% of the pending set) have no Chroma embedding at all — orphan-residue from past embedding-orphaning incidents (graph node written, embedding never landed). Cross-referenced against the WAL: only 2 are recoverable (in the WAL window); 3,327 are content-gone. Converged with @neo-opus-vega on an archivedAt archive-not-delete (reversible) model (her refinement; dissolves the deeper-recovery question since archiving is reversible).
The Problem
MemoryService.backfillMiniSummaries (ai/services/memory-core/MemoryService.mjs:1630-1634) counts a row whose Chroma metadata lacks both prompt and response as missingContent and skips it (continue) — it never receives a miniSummary, so it stays miniSummary: NULL permanently. But getPendingMemorySummaryBackfillCount (ai/daemons/orchestrator/scheduling/memorySummaryBackfill.mjs:50) counts ALL NULL rows. Therefore:
- These structurally-un-summarizable rows are a permanent backlog floor — the pending metric can never reach zero ("the backfill is still an issue").
- A newest-50 fetch (
getPendingMemorySummaryBackfillJobs:20, ORDER BY timestamp DESC LIMIT 50) dominated by them drains zero → the 10-min no-progress backoff arms → stall.
The Architectural Reality
MemoryService.backfillMiniSummaries (the missingContent branch) owns per-row summarization; it already updates the node when it sets miniSummary.
memorySummaryBackfill.mjs owns the scheduler-side fetch (getPendingMemorySummaryBackfillJobs) + uncapped count (getPendingMemorySummaryBackfillCount) + the no-progress backoff.
- Per @neo-opus-vega's convergence: mark un-summarizable nodes with a reversible
data.properties.archivedAt timestamp (archive-not-delete) — the metadata-stub persists as provenance; archivedAt is clearable if content ever reappears.
The Fix
- Permanent backfill fix: in
backfillMiniSummaries, on missingContent, set archivedAt on the node (instead of continue) → it leaves the pending set AND counts as progress (kills the no-progress-backoff misfire).
- Query exclusion:
getPendingMemorySummaryBackfillJobs + getPendingMemorySummaryBackfillCount add AND json_extract(data,'$.properties.archivedAt') IS NULL, so both the fetch and the metric reflect only the drainable backlog.
- One-shot reconcile: archive the existing 3,327 content-gone nodes (reversible) + re-embed the 2 in-WAL recoverables (ephemeral
/tmp script; not committed).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
data.properties.archivedAt on AGENT_MEMORY nodes |
This ticket (Vega convergence on #12065) |
Reversible timestamp marker set when a node is structurally un-summarizable (no Chroma content) |
Absent archivedAt = active/pending (current behavior); marker is clearable to un-archive |
backfillMiniSummaries JSDoc + this ticket |
Unit tests + live 3,327 archive |
getPendingMemorySummaryBackfillJobs / …Count queries |
memorySummaryBackfill.mjs |
Exclude archivedAt IS NOT NULL from fetch + count |
If column absent, IS NULL matches all (no regression) |
Function JSDoc |
Unit tests w/ archived-row fixtures |
Decision Record impact
aligned-with #12065 (Orchestrator-as-SSOT REM pipeline); no ADR authority change.
Acceptance Criteria
Out of Scope
- Re-embedding the 3,327 content-gone rows (content is gone — archive, not recover).
- The deeper May-baseline (
recovery-2026-05) extraction (dissolved by reversibility — an optional later leaf if ever wanted).
- #13496's Chroma stored-embedding export-repair (separate mechanism).
- Changing the newest-first fetch ordering or the backoff window.
Related
- Parent: #12065 (REM/Sandman pipeline epic)
- Root: #13496 (Chroma orphaning family), #13503 (coverage audit, the diagnostic that exposed this)
- Complement: #13551 (@neo-opus-vega's drain-watchdog — prevents future silent drain deaths; this reconciles the accumulated residue)
Origin Session ID: fca648cb-7dbf-418a-b2d2-498df8e0213c
Handoff Retrieval Hint: query_raw_memories("backfill archivedAt missingContent no-embedding orphan-residue reconcile 3329")
Context
V-B-A under #12065 (3 comments) found the root cause of the persistently-stuck mini-summary backfill, sized from live data: of 12,496
AGENT_MEMORYgraph nodes, 4,347 are pending (miniSummary IS NULL); 3,327 (77% of the pending set) have no Chroma embedding at all — orphan-residue from past embedding-orphaning incidents (graph node written, embedding never landed). Cross-referenced against the WAL: only 2 are recoverable (in the WAL window); 3,327 are content-gone. Converged with @neo-opus-vega on anarchivedAtarchive-not-delete (reversible) model (her refinement; dissolves the deeper-recovery question since archiving is reversible).The Problem
MemoryService.backfillMiniSummaries(ai/services/memory-core/MemoryService.mjs:1630-1634) counts a row whose Chroma metadata lacks bothpromptandresponseasmissingContentand skips it (continue) — it never receives aminiSummary, so it staysminiSummary: NULLpermanently. ButgetPendingMemorySummaryBackfillCount(ai/daemons/orchestrator/scheduling/memorySummaryBackfill.mjs:50) counts ALL NULL rows. Therefore:getPendingMemorySummaryBackfillJobs:20,ORDER BY timestamp DESC LIMIT 50) dominated by them drains zero → the 10-min no-progress backoff arms → stall.The Architectural Reality
MemoryService.backfillMiniSummaries(themissingContentbranch) owns per-row summarization; it already updates the node when it setsminiSummary.memorySummaryBackfill.mjsowns the scheduler-side fetch (getPendingMemorySummaryBackfillJobs) + uncapped count (getPendingMemorySummaryBackfillCount) + the no-progress backoff.data.properties.archivedAttimestamp (archive-not-delete) — the metadata-stub persists as provenance;archivedAtis clearable if content ever reappears.The Fix
backfillMiniSummaries, onmissingContent, setarchivedAton the node (instead ofcontinue) → it leaves the pending set AND counts as progress (kills the no-progress-backoff misfire).getPendingMemorySummaryBackfillJobs+getPendingMemorySummaryBackfillCountaddAND json_extract(data,'$.properties.archivedAt') IS NULL, so both the fetch and the metric reflect only the drainable backlog./tmpscript; not committed).Contract Ledger Matrix
data.properties.archivedAton AGENT_MEMORY nodesarchivedAt= active/pending (current behavior); marker is clearable to un-archivebackfillMiniSummariesJSDoc + this ticketgetPendingMemorySummaryBackfillJobs/…CountqueriesmemorySummaryBackfill.mjsarchivedAt IS NOT NULLfrom fetch + countDecision Record impact
aligned-with#12065 (Orchestrator-as-SSOT REM pipeline); no ADR authority change.Acceptance Criteria
backfillMiniSummariessetsarchivedAton amissingContentrow instead of skipping it (verified: the row leaves the pending set + the run reports it as non-pending progress).getPendingMemorySummaryBackfillJobs+getPendingMemorySummaryBackfillCountexcludearchivedAt IS NOT NULL.missingContentbatch no longer arms the no-progress backoff (it makes archive-progress).archivedAtis reversible (clearing it returns the node to the pending set).Out of Scope
recovery-2026-05) extraction (dissolved by reversibility — an optional later leaf if ever wanted).Related
Origin Session ID: fca648cb-7dbf-418a-b2d2-498df8e0213c
Handoff Retrieval Hint:
query_raw_memories("backfill archivedAt missingContent no-embedding orphan-residue reconcile 3329")