From 05a326a294c9c8083f9d0f0e0fe649924fc76e0c Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sat, 2 May 2026 23:23:37 +0200 Subject: [PATCH] fix(sf): enforceMemoryCap sweeps orphaned embeddings too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same orphan-cleanup as 1b71ddd17 but for the batch path. enforceMemoryCap calls supersedeLowestRankedMemories, which marks N lowest memories superseded in one UPDATE — bypassing the per-memory supersede embedding cleanup. The result was that capping a project at 50 memories left dead embedding rows for everything that got demoted. Now: a single DELETE-IN-SUBQUERY removes embedding rows for any memory that no longer has superseded_by IS NULL — covers both the cap path and any historical orphans from before the per-row cleanup landed. Best-effort; cap enforcement is load-bearing, embedding cleanup is not. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/resources/extensions/sf/memory-store.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/resources/extensions/sf/memory-store.ts b/src/resources/extensions/sf/memory-store.ts index 4951b0a21..99e69b16d 100644 --- a/src/resources/extensions/sf/memory-store.ts +++ b/src/resources/extensions/sf/memory-store.ts @@ -434,6 +434,12 @@ export function decayStaleMemories(thresholdUnits = 20): void { /** * Supersede lowest-ranked memories when count exceeds cap. + * + * After superseding, sweeps memory_embeddings for rows whose memory is now + * superseded — keeps the embeddings table aligned with active memories so + * loadAllEmbeddings doesn't carry dead vectors and storage doesn't grow + * unbounded. Best-effort cleanup; the cap enforcement is the load-bearing + * step. */ export function enforceMemoryCap(max = 50): void { if (!isDbAvailable()) return; @@ -451,6 +457,21 @@ export function enforceMemoryCap(max = 50): void { const excess = count - max; supersedeLowestRankedMemories(excess, new Date().toISOString()); + + // Sweep orphaned embeddings for newly-superseded memories. + try { + adapter + .prepare( + `DELETE FROM memory_embeddings WHERE memory_id IN ( + SELECT id FROM memories WHERE superseded_by IS NOT NULL + )`, + ) + .run(); + } catch { + // Orphaned rows are harmless to queries (loadAllEmbeddings filters + // by superseded_by IS NULL); skip-on-error keeps cap enforcement + // load-bearing without coupling to embedding cleanup. + } } catch { // non-fatal }