fix(ts-oss): isolate entity store from memory store when dbPath unset
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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$/,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user