From 98ed2e92024467ad12e2ef32898d139eda7f42e5 Mon Sep 17 00:00:00 2001 From: chaithanyak42 Date: Tue, 14 Apr 2026 18:36:46 +0530 Subject: [PATCH] fix(ts-oss): isolate entity store from memory store when dbPath unset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom: TS OSS `memory.search()` returns entity rows (fragment text like "a fan of", "recommending thriller movies and") mixed in with real memories. The broken rows have `hash: undefined` and `createdAt: undefined` because they came from the entity store, not the memory pipeline. Root cause: `MemoryVectorStore` uses a single `vectors` SQLite table and ignores `collectionName` internally. The entity store is created as a parallel instance with `collectionName: "${name}_entities"` in `memory/index.ts:193`, but both default to the same `dbPath` (`~/.mem0/vector_store.db`) when the caller doesn't set one. Both writers append to the same `vectors` table, so a search that reads that table returns rows from both collections. The existing mitigation at `memory/index.ts:199` (dbPath.replace) only fires when `dbPath` is set explicitly. The default (unset) case fell through with no isolation. Fix: `getDefaultVectorStoreDbPath(collectionName?)` now derives the default filename from the collection name (`vector_store_${name}.db`). `MemoryVectorStore` passes `config.collectionName` into the helper. Memory vs entity stores now land in separate files automatically, even when the caller doesn't set `dbPath`. Explicit `dbPath` users are unaffected — the existing suffix-swap in `memory/index.ts` still handles them. Co-Authored-By: Claude Opus 4.6 (1M context) --- mem0-ts/src/oss/src/memory/index.ts | 5 ++++- mem0-ts/src/oss/src/utils/sqlite.ts | 12 ++++++++++-- mem0-ts/src/oss/src/vector_stores/memory.ts | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mem0-ts/src/oss/src/memory/index.ts b/mem0-ts/src/oss/src/memory/index.ts index 71a10758d..b371d3a27 100644 --- a/mem0-ts/src/oss/src/memory/index.ts +++ b/mem0-ts/src/oss/src/memory/index.ts @@ -195,7 +195,10 @@ export class Memory { ...this.config.vectorStore.config, collectionName: entityCollectionName, }; - // For file-based stores (memory/SQLite), use a separate DB path for entities + // For file-based stores (memory/SQLite), use a separate DB path for entities. + // If dbPath was set explicitly, derive the entity path from it. If it's unset, + // leave it unset — the vector store's default-path logic now scopes by + // collectionName, so the entity store will land in its own file automatically. if (entityConfig.dbPath) { entityConfig.dbPath = entityConfig.dbPath.replace( /\.db$/, diff --git a/mem0-ts/src/oss/src/utils/sqlite.ts b/mem0-ts/src/oss/src/utils/sqlite.ts index 7da81c6c8..d752249f8 100644 --- a/mem0-ts/src/oss/src/utils/sqlite.ts +++ b/mem0-ts/src/oss/src/utils/sqlite.ts @@ -2,8 +2,16 @@ import fs from "fs"; import os from "os"; import path from "path"; -export function getDefaultVectorStoreDbPath(): string { - return path.join(os.homedir(), ".mem0", "vector_store.db"); +export function getDefaultVectorStoreDbPath(collectionName?: string): string { + // Scope the default DB file by collection name so that parallel stores + // (e.g. "memories" vs "memories_entities") don't collide in the same + // SQLite table. Without this, both collections write to the same + // `vectors` table and search results leak across them. + const filename = + collectionName && collectionName.length > 0 + ? `vector_store_${collectionName.replace(/[^a-zA-Z0-9_-]/g, "_")}.db` + : "vector_store.db"; + return path.join(os.homedir(), ".mem0", filename); } export function ensureSQLiteDirectory(dbPath: string): void { diff --git a/mem0-ts/src/oss/src/vector_stores/memory.ts b/mem0-ts/src/oss/src/vector_stores/memory.ts index 1a4f2ec21..6addf503f 100644 --- a/mem0-ts/src/oss/src/vector_stores/memory.ts +++ b/mem0-ts/src/oss/src/vector_stores/memory.ts @@ -21,7 +21,10 @@ export class MemoryVectorStore implements VectorStore { constructor(config: VectorStoreConfig) { this.dimension = config.dimension || 1536; // Default OpenAI dimension - this.dbPath = config.dbPath || getDefaultVectorStoreDbPath(); + // Scope the default path by collectionName so that parallel stores + // (e.g. memory vs entity) don't share the same `vectors` table. + this.dbPath = + config.dbPath || getDefaultVectorStoreDbPath(config.collectionName); if (!config.dbPath) { const oldDefault = path.join(process.cwd(), "vector_store.db");