Context
Surfaced while completing #12435 (aiConfig snapshot/restore isolation). With the config-leaf restore in place, a full-affected-set --workers=1 run still has one cross-spec failure that config-restore does not fix: QueryReRanker.spec "StorageRouter re-ranker should handle valid queries without crashing" fails because its seeded memories are not found (result.ids[0].length is 0). The same spec passes alone (7/7).
Proven pre-existing: checking out the pre-#12435 (HEAD~1) versions of the same specs and re-running the identical --workers=1 group produces the identical failure (57 passed / 1 failed). So this is orthogonal to — and not introduced by — the aiConfig config-leaf bleed #12435 addresses.
The Problem
The Memory Core lifecycle services are process-singletons. Memory_LifecycleService._initPromise, Memory_ChromaManager.client, and the ChromaManager collection caches bind to whichever spec inits the lifecycle first in a --workers=1 worker process. Specs guard init with if (!_initPromise) initAsync(); else ready();, so a later spec calling ready() inherits the first spec's collection binding.
The divergence that fails the test: Memory_Service.addMemory (the seed) writes through the bound ChromaManager (first spec's collection), while StorageRouter.getMemoryCollection() resolves the collection name from the (now config-restored) aiConfig at call time (the later spec's name). Seed and query land in different collections → 0 results.
The asymmetry that lets the stale binding survive: cleanupChromaManager() resets _initPromise = null plus the ChromaManager collection caches, but TestLifecycleHelper.cleanupGraphService() (the graph-only cleanup used by graph specs) does not. A spec whose afterAll uses the graph-only cleanup leaves the lifecycle bound for the next spec in the worker.
The Architectural Reality
test/playwright/unit/ai/services/memory-core/util.mjs — cleanupChromaManager() resets _initPromise + ChromaManager caches; TestLifecycleHelper.cleanupGraphService() clears the graph db only (no _initPromise / ChromaManager-cache reset).
test/playwright/unit/ai/services/memory-core/QueryReRanker.spec.mjs — beforeAll boots the lifecycle via the if (!_initPromise) … else ready() short-circuit, seeds 3 memories via Memory_Service.addMemory, then the failing test queries via StorageRouter.getMemoryCollection().
- Distinct from #12331 / #12180 / #12143, which target orphan
test-* collection accumulation / store-persistence cleanup across runs. This is an in-memory singleton-binding bleed within a single --workers=1 run — a test-correctness issue, not store persistence.
The Fix (candidate prescriptions)
- Unify spec cleanup so every memory-core / graph spec resets the lifecycle singleton in
afterAll — e.g. have cleanupGraphService also null _initPromise + the ChromaManager caches, OR introduce a shared resetMemoryCoreLifecycle() that both cleanup paths call.
- OR drop the
else ready() short-circuit so each describe re-inits unconditionally (heavier per-spec cost; weigh during implementation).
Prefer the unified-reset-helper approach (single source of truth invoked by both cleanup paths).
Acceptance Criteria
Out of Scope
- Orphan
test-* collection accumulation / store purge (#12331, #12180, #12143).
- aiConfig config-leaf snapshot/restore (#12435 — shipped).
- Redesigning the lifecycle-singleton pattern itself.
Related
- Surfaced while completing #12435. The AC3 (
--workers=1 green) residual for that ticket lives here.
- Sibling test-isolation cluster: #12331 (unified test-store isolation), #12180, #12143. May fold into #12331's unified-isolation scope — maintainer/operator triage.
Decision Record impact
none.
Live latest-open sweep: checked latest 20 open issues at 2026-06-05T20:55Z; nearest neighbours (#12331/#12180/#12143) target store-accumulation, not in-memory singleton binding — no equivalent found.
Origin Session ID: b457a732-8cec-4fac-8ace-bcb977e1b076
Retrieval Hint: "Memory Core lifecycle singleton _initPromise binding bleed --workers=1 QueryReRanker StorageRouter seed query cleanupGraphService reset"
Context
Surfaced while completing #12435 (aiConfig snapshot/restore isolation). With the config-leaf restore in place, a full-affected-set
--workers=1run still has one cross-spec failure that config-restore does not fix:QueryReRanker.spec"StorageRouter re-ranker should handle valid queries without crashing" fails because its seeded memories are not found (result.ids[0].lengthis 0). The same spec passes alone (7/7).Proven pre-existing: checking out the pre-#12435 (HEAD~1) versions of the same specs and re-running the identical
--workers=1group produces the identical failure (57 passed / 1 failed). So this is orthogonal to — and not introduced by — the aiConfig config-leaf bleed #12435 addresses.The Problem
The Memory Core lifecycle services are process-singletons.
Memory_LifecycleService._initPromise,Memory_ChromaManager.client, and the ChromaManager collection caches bind to whichever spec inits the lifecycle first in a--workers=1worker process. Specs guard init withif (!_initPromise) initAsync(); else ready();, so a later spec callingready()inherits the first spec's collection binding.The divergence that fails the test:
Memory_Service.addMemory(the seed) writes through the bound ChromaManager (first spec's collection), whileStorageRouter.getMemoryCollection()resolves the collection name from the (now config-restored) aiConfig at call time (the later spec's name). Seed and query land in different collections → 0 results.The asymmetry that lets the stale binding survive:
cleanupChromaManager()resets_initPromise = nullplus the ChromaManager collection caches, butTestLifecycleHelper.cleanupGraphService()(the graph-only cleanup used by graph specs) does not. A spec whoseafterAlluses the graph-only cleanup leaves the lifecycle bound for the next spec in the worker.The Architectural Reality
test/playwright/unit/ai/services/memory-core/util.mjs—cleanupChromaManager()resets_initPromise+ ChromaManager caches;TestLifecycleHelper.cleanupGraphService()clears the graph db only (no_initPromise/ ChromaManager-cache reset).test/playwright/unit/ai/services/memory-core/QueryReRanker.spec.mjs—beforeAllboots the lifecycle via theif (!_initPromise) … else ready()short-circuit, seeds 3 memories viaMemory_Service.addMemory, then the failing test queries viaStorageRouter.getMemoryCollection().test-*collection accumulation / store-persistence cleanup across runs. This is an in-memory singleton-binding bleed within a single--workers=1run — a test-correctness issue, not store persistence.The Fix (candidate prescriptions)
afterAll— e.g. havecleanupGraphServicealso null_initPromise+ the ChromaManager caches, OR introduce a sharedresetMemoryCoreLifecycle()that both cleanup paths call.else ready()short-circuit so each describe re-inits unconditionally (heavier per-spec cost; weigh during implementation).Prefer the unified-reset-helper approach (single source of truth invoked by both cleanup paths).
Acceptance Criteria
--workers=1run (the 13 #12435 specs) is green — QueryReRanker's StorageRouter seed→query passes in the group, not only alone.Out of Scope
test-*collection accumulation / store purge (#12331, #12180, #12143).Related
--workers=1green) residual for that ticket lives here.Decision Record impact
none.
Live latest-open sweep: checked latest 20 open issues at 2026-06-05T20:55Z; nearest neighbours (#12331/#12180/#12143) target store-accumulation, not in-memory singleton binding — no equivalent found.
Origin Session ID: b457a732-8cec-4fac-8ace-bcb977e1b076 Retrieval Hint: "Memory Core lifecycle singleton _initPromise binding bleed --workers=1 QueryReRanker StorageRouter seed query cleanupGraphService reset"