remem-mcp 0.7.0 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -218
- package/dist/{chunk-OYUNKUZK.js → chunk-4WCDAEPP.js} +788 -11
- package/dist/chunk-4WCDAEPP.js.map +1 -0
- package/dist/{chunk-TGLOMFIE.js → chunk-ITMR6THY.js} +47 -7
- package/dist/chunk-ITMR6THY.js.map +1 -0
- package/dist/{chunk-ZXI67YIV.js → chunk-YU7WBJU5.js} +15 -3
- package/dist/{chunk-ZXI67YIV.js.map → chunk-YU7WBJU5.js.map} +1 -1
- package/dist/{engine-3JUSB3RK.js → engine-S47HHGAO.js} +2 -2
- package/dist/index.js +347 -39
- package/dist/index.js.map +1 -1
- package/dist/{resolver-YIFWRNMS.js → resolver-UHU4MBQX.js} +50 -15
- package/dist/resolver-UHU4MBQX.js.map +1 -0
- package/dist/sdk.js +3 -3
- package/dist/{server-SUKPVQWL.js → server-5PWQTRGZ.js} +2 -2
- package/dist/storage/schema.sql +63 -2
- package/package.json +1 -1
- package/scripts/verify-codegraph-stats.mjs +122 -0
- package/skills/remem-mcp/SKILL.md +11 -3
- package/dist/chunk-OYUNKUZK.js.map +0 -1
- package/dist/chunk-TGLOMFIE.js.map +0 -1
- package/dist/resolver-YIFWRNMS.js.map +0 -1
- /package/dist/{engine-3JUSB3RK.js.map → engine-S47HHGAO.js.map} +0 -0
- /package/dist/{server-SUKPVQWL.js.map → server-5PWQTRGZ.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/embedding/local.ts","../src/storage/sqlite.ts","../src/utils/rrf.ts","../src/utils/write-queue.ts"],"sourcesContent":["import type { Embedder } from \"./types.js\";\n\n/**\n * Local embedder. Uses the ONNX runtime and the model all-MiniLM-L6-v2.\n * The vector has 384 dimensions. No API key is needed.\n *\n * The first run downloads the model. Subsequent runs load the model from the disk cache.\n *\n * The `@huggingface/transformers` import is deferred to first use so that\n * environments without the ONNX native module (e.g. CI Linux) can still load\n * the module — only `embed()` calls will fail there.\n */\nexport class LocalEmbedder implements Embedder {\n readonly dimension = 384;\n readonly model = \"Xenova/all-MiniLM-L6-v2\";\n\n private extractor:\n | ((\n input: string,\n options: { pooling: string; normalize: boolean },\n ) => Promise<{ data: unknown }>)\n | null = null;\n private initPromise: Promise<void> | null = null;\n\n /** Initialize the model. This runs once. Subsequent calls return immediately. */\n private async init(): Promise<void> {\n if (this.extractor) return;\n if (this.initPromise) {\n await this.initPromise;\n return;\n }\n this.initPromise = (async () => {\n // Lazy import so the ONNX native module is only loaded when embedding\n // is actually needed (not at module load time).\n const { pipeline } = await import(\"@huggingface/transformers\");\n this.extractor = (await pipeline(\n \"feature-extraction\",\n this.model,\n )) as unknown as typeof this.extractor;\n })();\n await this.initPromise;\n }\n\n async embed(text: string): Promise<number[]> {\n await this.init();\n if (!this.extractor) throw new Error(\"Embedder failed to initialize\");\n\n const output = await this.extractor(text, { pooling: \"mean\", normalize: true });\n // output.data is a Float32Array\n return Array.from(output.data as Float32Array);\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport Database from \"better-sqlite3\";\nimport * as sqliteVec from \"sqlite-vec\";\nimport { type RankedResult, rrfMerge } from \"../utils/rrf.js\";\nimport { generateId } from \"../utils/ulid.js\";\nimport { WriteQueue } from \"../utils/write-queue.js\";\nimport type {\n AtomEntry,\n CaptureEntry,\n ConflictResult,\n ScoreDetails,\n DeleteFilter,\n DeleteResult,\n KnowledgeEntry,\n MessageRow,\n PersonaEntry,\n QueryOptions,\n ResolveResult,\n ScenarioEntry,\n SearchFilters,\n SearchResult,\n SkillEntry,\n StorageBackend,\n TrustState,\n} from \"./types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/** FTS stopword set — shared between BM25 query building and proper-noun stripping. */\nconst FTS_STOPWORDS_SET = new Set([\n \"the\",\n \"a\",\n \"an\",\n \"is\",\n \"are\",\n \"was\",\n \"were\",\n \"be\",\n \"been\",\n \"being\",\n \"have\",\n \"has\",\n \"had\",\n \"do\",\n \"does\",\n \"did\",\n \"will\",\n \"would\",\n \"could\",\n \"should\",\n \"may\",\n \"might\",\n \"must\",\n \"can\",\n \"shall\",\n \"i\",\n \"you\",\n \"he\",\n \"she\",\n \"it\",\n \"we\",\n \"they\",\n \"me\",\n \"him\",\n \"her\",\n \"us\",\n \"them\",\n \"my\",\n \"your\",\n \"his\",\n \"its\",\n \"our\",\n \"their\",\n \"this\",\n \"that\",\n \"these\",\n \"those\",\n \"and\",\n \"or\",\n \"but\",\n \"not\",\n \"no\",\n \"nor\",\n \"so\",\n \"yet\",\n \"in\",\n \"on\",\n \"at\",\n \"to\",\n \"for\",\n \"of\",\n \"with\",\n \"by\",\n \"from\",\n \"as\",\n \"about\",\n \"into\",\n \"through\",\n \"during\",\n \"before\",\n \"after\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"who\",\n \"how\",\n \"why\",\n \"since\",\n \"because\",\n \"if\",\n \"then\",\n \"than\",\n]);\n\n/**\n * Strip proper nouns (person/place names) from a query, leaving only content words.\n * Returns the stripped query string, or empty string if nothing remains after stripping.\n * Used by both BM25 (escapeFtsQuery) and vector search (SDK) to ensure proper nouns\n * don't dominate search results when they're rare tokens.\n *\n * Only strips words that look like person names: first letter uppercase, rest lowercase.\n * Keeps acronyms (all-caps: API, REST, CI) and mixed-case technical terms (GraphQL,\n * PostgreSQL, JavaScript) — these carry semantic meaning that should be preserved.\n *\n * E.g. \"what editor does Tin use\" → \"editor use\"\n * \"API design GraphQL or REST\" → \"API design GraphQL or REST\" (no change)\n * \"When did Caroline go to the LGBTQ support group\" → \"go LGBTQ support group\"\n */\nexport function stripQueryProperNouns(query: string): string {\n const tokens = query.trim().split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return \"\";\n const filtered = tokens.filter((t) => t.length <= 1 || !FTS_STOPWORDS_SET.has(t.toLowerCase()));\n // Strip person-name-like words: first char uppercase, rest lowercase (length > 1).\n // Keep acronyms (all-caps), mixed-case (GraphQL), and single-char tokens.\n const isPersonName = (t: string): boolean => {\n if (t.length <= 1) return false;\n if (t[0] !== t[0].toUpperCase()) return false;\n if (t[0] === t[0].toUpperCase() && t.slice(1) === t.slice(1).toLowerCase()) return true;\n return false;\n };\n const noProperNouns = filtered.filter(\n (t) => t.length <= 1 || !isPersonName(t) || FTS_STOPWORDS_SET.has(t.toLowerCase()),\n );\n // Only strip if ≥2 content words remain (preserve single-word queries)\n if (noProperNouns.length >= 2) return noProperNouns.join(\" \");\n if (noProperNouns.length > 0) return noProperNouns.join(\" \");\n return filtered.length > 0 ? filtered.join(\" \") : tokens.join(\" \");\n}\n\n/** Current schema version. */\nconst CURRENT_SCHEMA_VERSION = 13;\n\n/**\n * Evergreen tags: captures with any of these tags are exempt from temporal\n * decay and the auto-stale mechanism (decay multiplier = 1.0, never marked\n * stale). Parsed once at startup from REMEM_EVERGREEN_TAGS (comma-separated).\n * Default: \"evergreen,never-forget\".\n */\nconst EVERGREEN_TAGS: ReadonlySet<string> = new Set(\n (process.env.REMEM_EVERGREEN_TAGS ?? \"evergreen,never-forget\")\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean),\n);\n\n/** Check whether a capture's tags include any evergreen tag. */\nfunction isEvergreen(tags: string[] | undefined | null): boolean {\n if (!tags || tags.length === 0) return false;\n return tags.some((t) => EVERGREEN_TAGS.has(t.toLowerCase()));\n}\n\n/**\n * Auto-assign an authority tier based on capture type and tags (v10).\n * - decision → \"decision\" (durable, high authority)\n * - pattern → \"rule\" (procedural, high authority)\n * - error/learning/task → \"episodic\" (default, decays over time)\n * - Tags can override: \"rule\", \"decision\", \"canonical\" → respective tier\n */\nfunction autoAssignTier(type: string, tags: string[] | undefined | null): string {\n // Tag overrides\n if (tags) {\n const lower = tags.map((t) => t.toLowerCase());\n if (lower.includes(\"rule\") || lower.includes(\"canonical\")) return \"rule\";\n if (lower.includes(\"decision\")) return \"decision\";\n if (lower.includes(\"test\") || lower.includes(\"test-fixture\")) return \"test\";\n }\n // Type-based defaults\n if (type === \"decision\") return \"decision\";\n if (type === \"pattern\") return \"rule\";\n return \"episodic\";\n}\n\n/**\n * Authority boost tags (v10). Bounded multiplier applied after RRF fusion.\n * Positive tags boost, negative tags penalize. Never absolute filters.\n */\nconst AUTHORITY_POSITIVE_TAGS: ReadonlySet<string> = new Set([\n \"canonical\",\n \"active\",\n \"source-of-truth\",\n \"pinned\",\n]);\nconst AUTHORITY_NEGATIVE_TAGS: ReadonlySet<string> = new Set([\n \"superseded\",\n \"historical\",\n \"test-fixture\",\n \"do-not-answer-from\",\n]);\n\n/** Compute authority multiplier for a capture (v10). Bounded: 0.5x to 1.5x. */\nfunction authorityMultiplier(tier: string, tags: string[] | undefined | null): number {\n let multiplier = 1.0;\n // Tier boosts\n if (tier === \"rule\") multiplier += 0.2;\n else if (tier === \"decision\") multiplier += 0.15;\n else if (tier === \"test\") multiplier -= 0.1;\n // Tag boosts\n if (tags) {\n for (const tag of tags) {\n const lower = tag.toLowerCase();\n if (AUTHORITY_POSITIVE_TAGS.has(lower)) multiplier += 0.1;\n if (AUTHORITY_NEGATIVE_TAGS.has(lower)) multiplier -= 0.15;\n }\n }\n // Clamp to bounded range\n return Math.max(0.5, Math.min(1.5, multiplier));\n}\n\n/**\n * Extract up to 10 significant nouns/entities from text (v10 entity-assisted recall).\n * Simple heuristic: capitalized words, technical terms, and words >4 chars that\n * aren't stopwords. No NLP dependency — just lexical extraction.\n */\nconst ENTITY_STOPWORDS: ReadonlySet<string> = new Set([\n \"the\", \"this\", \"that\", \"with\", \"from\", \"have\", \"been\", \"will\", \"would\",\n \"could\", \"should\", \"there\", \"their\", \"about\", \"which\", \"when\", \"what\",\n \"they\", \"them\", \"then\", \"than\", \"into\", \"after\", \"before\", \"between\",\n \"during\", \"through\", \"above\", \"below\", \"under\", \"over\", \"again\",\n \"because\", \"while\", \"where\", \"these\", \"those\", \"being\", \"having\",\n \"using\", \"based\", \"other\", \"some\", \"such\", \"more\", \"most\", \"only\",\n \"very\", \"also\", \"just\", \"like\", \"even\", \"still\", \"back\", \"make\",\n \"made\", \"used\", \"uses\", \"using\", \"want\", \"need\", \"know\", \"think\",\n \"thing\", \"things\", \"stuff\", \"code\", \"file\", \"files\", \"line\", \"lines\",\n \"here\", \"there\", \"where\", \"when\", \"what\", \"which\", \"whose\",\n]);\n\nexport function extractEntities(text: string): string[] {\n // Extract capitalized words (proper nouns, tech terms)\n const capsMatches = text.match(/\\b[A-Z][a-zA-Z]{2,}\\b/g) ?? [];\n // Extract technical terms: words with hyphens, dots, or all-caps acronyms\n const techMatches = text.match(/\\b[a-z]+[-_][a-z]+\\b/gi) ?? [];\n const acroMatches = text.match(/\\b[A-Z]{2,}\\b/g) ?? [];\n // Extract significant lowercase words (>4 chars, not stopwords)\n const words = text.match(/\\b[a-z]{5,}\\b/gi) ?? [];\n\n const candidates = [...capsMatches, ...techMatches, ...acroMatches, ...words];\n const seen = new Set<string>();\n const entities: string[] = [];\n\n for (const candidate of candidates) {\n const lower = candidate.toLowerCase();\n if (ENTITY_STOPWORDS.has(lower)) continue;\n if (seen.has(lower)) continue;\n seen.add(lower);\n entities.push(lower);\n if (entities.length >= 10) break;\n }\n\n return entities;\n}\n\n/**\n * SQLite storage backend.\n * Uses better-sqlite3 + sqlite-vec + FTS5.\n * Default backend. Zero setup.\n */\nexport class SQLiteBackend implements StorageBackend {\n private db: Database.Database;\n /** v11: Single-writer queue — serializes all write operations to prevent \"database is locked\". */\n private writeQueue: WriteQueue = new WriteQueue();\n\n /** Get the underlying database instance (for CodeGraph/Wiki operations). */\n getDatabase(): Database.Database {\n return this.db;\n }\n\n constructor(dbPath: string) {\n // Make sure the directory exists\n const dir = dirname(dbPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n // Try read-write first; fall back to read-only if the directory is not writable\n // (e.g. Codex CLI sandbox blocks writes outside the workdir)\n let readonly = false;\n try {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n } catch {\n // Read-only fallback — recall/search still work, captures will fail gracefully\n readonly = true;\n this.db = new Database(dbPath, { readonly: true });\n }\n\n // Pragmas that are safe for both read-write and read-only\n try {\n this.db.pragma(\"busy_timeout = 10000\");\n } catch {\n /* read-only */\n }\n if (!readonly) {\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.pragma(\"foreign_keys = OFF\");\n }\n\n // Load the sqlite-vec extension\n sqliteVec.load(this.db);\n\n // Detect the database state and run the migration (skipped in read-only mode)\n if (!readonly) {\n try {\n this.detectAndMigrate(dbPath);\n } catch (err) {\n console.error(`[remem-mcp] Migration failed: ${err}`);\n throw err;\n }\n }\n }\n\n /**\n * Detect the database state and run the correct migration path.\n */\n private detectAndMigrate(dbPath: string): void {\n let migrationsRan = false;\n const hasVersionTable = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'\")\n .get() as { name: string } | undefined;\n\n if (!hasVersionTable) {\n // Fresh database or old database without versioning\n const tables = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\")\n .all() as { name: string }[];\n\n if (tables.length === 0) {\n // Fresh database: run the full schema, write current version\n this.runSchema();\n this.writeSchemaVersion(CURRENT_SCHEMA_VERSION);\n } else {\n // Old database without versioning. Backup, then run incremental migrations\n // to add missing columns before running the full schema (which creates new tables).\n migrationsRan = true;\n this.backupDatabase(dbPath, 0);\n this.db.transaction(() => {\n this.migrateV1ToV2();\n this.migrateV2ToV3();\n this.migrateV3ToV4();\n this.migrateV4ToV5();\n this.migrateV5ToV6();\n this.migrateV6ToV7();\n this.migrateV7ToV8();\n // Now run the full schema to create any remaining tables/triggers/indexes\n this.runSchema();\n this.writeSchemaVersion(CURRENT_SCHEMA_VERSION);\n })();\n }\n this.rebuildFtsIfNeeded(migrationsRan);\n return;\n }\n\n // Database has a schema_version table. Read the current version.\n const row = this.db.prepare(\"SELECT MAX(version) as version FROM schema_version\").get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < 1) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 0);\n this.db.transaction(() => {\n this.runSchema();\n this.writeSchemaVersion(1);\n })();\n }\n if (currentVersion < 2) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 1);\n this.db.transaction(() => {\n this.migrateV1ToV2();\n this.writeSchemaVersion(2);\n })();\n }\n if (currentVersion < 3) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 2);\n this.db.transaction(() => {\n this.migrateV2ToV3();\n this.writeSchemaVersion(3);\n })();\n }\n if (currentVersion < 4) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 3);\n this.db.transaction(() => {\n this.migrateV3ToV4();\n this.writeSchemaVersion(4);\n })();\n }\n if (currentVersion < 5) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 4);\n this.db.transaction(() => {\n this.migrateV4ToV5();\n this.writeSchemaVersion(5);\n })();\n }\n if (currentVersion < 6) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 5);\n // Tables are created by runSchema() via CREATE TABLE IF NOT EXISTS.\n // Run schema to create CodeGraph + Wiki tables.\n this.db.transaction(() => {\n this.runSchema();\n this.migrateV5ToV6();\n this.writeSchemaVersion(6);\n })();\n }\n if (currentVersion < 7) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 6);\n this.db.transaction(() => {\n this.migrateV6ToV7();\n this.writeSchemaVersion(7);\n })();\n }\n if (currentVersion < 8) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 7);\n this.db.transaction(() => {\n this.migrateV7ToV8();\n this.writeSchemaVersion(8);\n })();\n }\n if (currentVersion < 9) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 8);\n this.db.transaction(() => {\n this.migrateV8ToV9();\n this.writeSchemaVersion(9);\n })();\n }\n if (currentVersion < 10) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 9);\n this.db.transaction(() => {\n this.runSchema(); // creates entities table + new columns in captures\n this.migrateV9ToV10();\n this.writeSchemaVersion(10);\n })();\n }\n if (currentVersion < 11) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 10);\n this.db.transaction(() => {\n this.runSchema(); // creates memory_links table\n this.writeSchemaVersion(11);\n })();\n }\n if (currentVersion < 12) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 11);\n this.db.transaction(() => {\n this.runSchema(); // creates capture_feedback + audit_log tables + new captures columns\n this.migrateV11ToV12();\n this.writeSchemaVersion(12);\n })();\n }\n if (currentVersion < 13) {\n migrationsRan = true;\n this.backupDatabase(dbPath, 12);\n this.db.transaction(() => {\n this.runSchema(); // creates memory_links with weight column\n this.migrateV12ToV13();\n this.writeSchemaVersion(13);\n })();\n }\n this.rebuildFtsIfNeeded(migrationsRan);\n }\n\n /**\n * Rebuild the FTS index if migrations were run, so pre-migration captures\n * that exist in the captures table but are absent from captures_fts get\n * indexed. No-op when no migrations ran (fresh DB already populates FTS\n * via triggers) or when the FTS table does not exist yet.\n */\n private rebuildFtsIfNeeded(migrationsRan: boolean): void {\n if (!migrationsRan) return;\n const hasFts = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='captures_fts'\")\n .get() as { name: string } | undefined;\n if (!hasFts) return;\n // Rebuild FTS index for pre-migration captures\n this.db.exec(\"INSERT INTO captures_fts(captures_fts) VALUES('rebuild')\");\n }\n\n /** Backup the database to a versioned .bak file. */\n private backupDatabase(dbPath: string, fromVersion: number): void {\n const backupPath = `${dbPath}.bak.v${fromVersion}`;\n try {\n this.db.pragma(\"wal_checkpoint(FULL)\");\n copyFileSync(dbPath, backupPath);\n console.error(`[remem-mcp] Backed up database to ${backupPath}`);\n } catch (err) {\n console.error(`[remem-mcp] Backup failed: ${err}`);\n }\n }\n\n /** Run the schema.sql file. Idempotent. */\n private runSchema(): void {\n const candidates = [\n join(__dirname, \"storage\", \"schema.sql\"),\n join(__dirname, \"schema.sql\"),\n join(__dirname, \"..\", \"storage\", \"schema.sql\"),\n ];\n\n let schema: string | null = null;\n for (const path of candidates) {\n try {\n schema = readFileSync(path, \"utf-8\");\n break;\n } catch {\n // Try the next candidate\n }\n }\n\n if (!schema) {\n throw new Error(\"Could not find schema.sql. Make sure the build copied it to dist/storage/.\");\n }\n this.db.exec(schema);\n }\n\n /** Write the schema version to the schema_version table. */\n private writeSchemaVersion(version: number): void {\n this.db\n .prepare(\"INSERT INTO schema_version (version, applied_at) VALUES (?, ?)\")\n .run(version, Date.now());\n }\n\n /** Migrate schema v1 → v2: add content_hash column + index. */\n private migrateV1ToV2(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasContentHash = cols.some((c) => c.name === \"content_hash\");\n if (!hasContentHash) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN content_hash TEXT\");\n console.error(\"[remem-mcp] Added content_hash column to captures\");\n }\n const idxs = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_captures_hash'\")\n .get() as { name: string } | undefined;\n if (!idxs) {\n this.db.exec(\"CREATE INDEX IF NOT EXISTS idx_captures_hash ON captures (content_hash)\");\n }\n // Backfill content_hash for existing rows\n const rows = this.db\n .prepare(\"SELECT id, content FROM captures WHERE content_hash IS NULL\")\n .all() as { id: string; content: string }[];\n const stmt = this.db.prepare(\"UPDATE captures SET content_hash = ? WHERE id = ?\");\n for (const row of rows) {\n const hash = createHash(\"sha256\").update(row.content).digest(\"hex\");\n stmt.run(hash, row.id);\n }\n if (rows.length > 0) {\n console.error(`[remem-mcp] Backfilled content_hash for ${rows.length} existing captures`);\n }\n }\n\n /** Migrate schema v2 → v3: add multi-tenant columns + new tables (messages, knowledge, skills, persona). */\n private migrateV2ToV3(): void {\n // Helper: add a column to a table if the table exists and the column is missing\n const addColumnIfMissing = (table: string, column: string, definition: string) => {\n const tableExists = this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(table) as { name: string } | undefined;\n if (!tableExists) return;\n const cols = this.db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n if (!cols.some((c) => c.name === column)) {\n this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);\n }\n };\n\n // Add multi-tenant columns to captures\n addColumnIfMissing(\"captures\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"captures\", \"user_id\", \"TEXT\");\n addColumnIfMissing(\"captures\", \"task_id\", \"TEXT\");\n\n // Add multi-tenant columns to atoms\n addColumnIfMissing(\"atoms\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"atoms\", \"agent_id\", \"TEXT\");\n addColumnIfMissing(\"atoms\", \"user_id\", \"TEXT\");\n\n // Add multi-tenant columns to scenarios\n addColumnIfMissing(\"scenarios\", \"team_id\", \"TEXT\");\n addColumnIfMissing(\"scenarios\", \"agent_id\", \"TEXT\");\n addColumnIfMissing(\"scenarios\", \"user_id\", \"TEXT\");\n\n // Create new tables (idempotent — schema.sql also has them, but run here for migration path\n // before schema.sql so that index creation in schema.sql doesn't fail)\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n seq INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS persona (\n team_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n user_id TEXT NOT NULL,\n content TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (team_id, agent_id, user_id)\n );\n CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n team_id TEXT NOT NULL,\n name TEXT NOT NULL,\n type TEXT NOT NULL,\n summary TEXT,\n service_url TEXT,\n repo_url TEXT,\n branch TEXT,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS skills (\n id TEXT PRIMARY KEY,\n team_id TEXT NOT NULL,\n agent_id TEXT,\n name TEXT NOT NULL,\n description TEXT,\n content TEXT,\n version INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n `);\n // Indexes are created by the full schema.sql run, not here.\n\n console.error(\n \"[remem-mcp] Migrated schema v2 → v3 (multi-tenant + messages + knowledge + skills + persona)\",\n );\n }\n\n /** Migrate schema v3 → v4: add deleted_at column for soft delete (tombstone). */\n private migrateV3ToV4(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasDeletedAt = cols.some((c) => c.name === \"deleted_at\");\n if (!hasDeletedAt) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN deleted_at INTEGER\");\n console.error(\"[remem-mcp] Added deleted_at column to captures (tombstone support)\");\n }\n console.error(\"[remem-mcp] Migrated schema v3 → v4 (tombstone / soft delete)\");\n }\n\n /** Migrate schema v4 → v5: add trust_state, rejection_reason, superseded_by columns. */\n private migrateV4ToV5(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const hasTrustState = cols.some((c) => c.name === \"trust_state\");\n if (!hasTrustState) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN trust_state TEXT NOT NULL DEFAULT 'candidate'\");\n console.error(\"[remem-mcp] Added trust_state column to captures\");\n }\n const hasRejectionReason = cols.some((c) => c.name === \"rejection_reason\");\n if (!hasRejectionReason) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN rejection_reason TEXT\");\n console.error(\"[remem-mcp] Added rejection_reason column to captures\");\n }\n const hasSupersededBy = cols.some((c) => c.name === \"superseded_by\");\n if (!hasSupersededBy) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN superseded_by TEXT REFERENCES captures(id)\");\n console.error(\"[remem-mcp] Added superseded_by column to captures\");\n }\n this.db.exec(\"CREATE INDEX IF NOT EXISTS idx_captures_trust ON captures (trust_state)\");\n this.db.exec(\n \"CREATE INDEX IF NOT EXISTS idx_captures_rejected_hash ON captures (content_hash) WHERE trust_state = 'rejected'\",\n );\n console.error(\"[remem-mcp] Migrated schema v4 → v5 (trust state + correction)\");\n }\n\n /** Migrate schema v5 → v6: add CodeGraph + Wiki tables (created by runSchema). */\n private migrateV5ToV6(): void {\n // Tables are created by runSchema() which runs CREATE TABLE IF NOT EXISTS.\n // This migration is a no-op placeholder for version tracking.\n console.error(\"[remem-mcp] Migrated schema v5 → v6 (CodeGraph + Wiki tables)\");\n }\n\n /** Migrate schema v6 → v7: add access tracking + Bayesian confidence columns. */\n private migrateV6ToV7(): void {\n // Check EACH column individually so an interrupted migration (crash after\n // the first ALTER but before the rest) resumes correctly on retry instead\n // of being skipped because the sentinel column already exists.\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN last_accessed_at INTEGER\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN confirmations INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN corrections INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n if (addedAny) {\n console.error(\"[remem-mcp] Added access tracking + Bayesian confidence columns\");\n }\n console.error(\"[remem-mcp] Migrated schema v6 → v7 (access tracking + confidence)\");\n }\n\n /** Migrate schema v7 → v8: add correction outcome tracking columns. */\n private migrateV7ToV8(): void {\n // Check EACH column individually so an interrupted migration (crash after\n // the first ALTER but before the rest) resumes correctly on retry instead\n // of being skipped because the sentinel column already exists.\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN retrieved_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN heeded_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN recurrence_count INTEGER NOT NULL DEFAULT 0\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN last_outcome TEXT\");\n addedAny = true;\n } catch {}\n if (addedAny) {\n console.error(\"[remem-mcp] Added correction outcome tracking columns\");\n }\n console.error(\"[remem-mcp] Migrated schema v7 → v8 (correction outcome tracking)\");\n }\n\n /** Migrate schema v8 → v9: add CodeGraph call resolution columns (module_path, confidence, call_type). */\n private migrateV8ToV9(): void {\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE symbols ADD COLUMN module_path TEXT\");\n addedAny = true;\n } catch (err) {\n if (!String(err).includes(\"duplicate column\"))\n console.error(`[remem-mcp] migrateV8ToV9: ${err}`);\n }\n try {\n this.db.exec(\"ALTER TABLE calls ADD COLUMN confidence REAL\");\n addedAny = true;\n } catch (err) {\n if (!String(err).includes(\"duplicate column\"))\n console.error(`[remem-mcp] migrateV8ToV9: ${err}`);\n }\n try {\n this.db.exec(\"ALTER TABLE calls ADD COLUMN call_type TEXT NOT NULL DEFAULT 'direct'\");\n addedAny = true;\n } catch (err) {\n if (!String(err).includes(\"duplicate column\"))\n console.error(`[remem-mcp] migrateV8ToV9: ${err}`);\n }\n if (addedAny) {\n console.error(\n \"[remem-mcp] Added CodeGraph call resolution columns (module_path, confidence, call_type)\",\n );\n }\n console.error(\"[remem-mcp] Migrated schema v8 → v9 (CodeGraph call resolution)\");\n }\n\n /** Migrate schema v9 → v10: add tier + salience columns to captures, create entities table, backfill tiers. */\n private migrateV9ToV10(): void {\n let addedAny = false;\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN tier TEXT NOT NULL DEFAULT 'episodic'\");\n addedAny = true;\n } catch {}\n try {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN salience REAL NOT NULL DEFAULT 1.0\");\n addedAny = true;\n } catch {}\n if (addedAny) {\n console.error(\"[remem-mcp] Added tier + salience columns to captures\");\n }\n // Backfill tier based on capture type\n this.db.exec(\"UPDATE captures SET tier = 'decision' WHERE type = 'decision' AND tier = 'episodic'\");\n this.db.exec(\"UPDATE captures SET tier = 'rule' WHERE type = 'pattern' AND tier = 'episodic'\");\n this.db.exec(\"UPDATE captures SET tier = 'test' WHERE type = 'error' AND tags LIKE '%test%' AND tier = 'episodic'\");\n // entities table is created by runSchema() via CREATE TABLE IF NOT EXISTS\n console.error(\"[remem-mcp] Migrated schema v9 → v10 (authority tiers + salience + entities table)\");\n }\n\n /**\n * v11 → v12: Add expires_at + feedback_salience columns to captures.\n * Create capture_feedback + audit_log tables (via runSchema).\n * No data migration needed — new columns have defaults.\n */\n private migrateV11ToV12(): void {\n // Add columns if they don't exist (ALTER TABLE ADD COLUMN is idempotent-safe via check)\n const cols = this.db.prepare(\"PRAGMA table_info(captures)\").all() as { name: string }[];\n const colNames = new Set(cols.map((c) => c.name));\n if (!colNames.has(\"expires_at\")) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN expires_at INTEGER\");\n }\n if (!colNames.has(\"feedback_salience\")) {\n this.db.exec(\"ALTER TABLE captures ADD COLUMN feedback_salience REAL NOT NULL DEFAULT 1.0\");\n }\n // capture_feedback + mutation_log tables created by runSchema() via CREATE TABLE IF NOT EXISTS\n console.error(\"[remem-mcp] Migrated schema v11 → v12 (feedback + mutation log + TTL)\");\n }\n\n /**\n * v12 → v13: Add weight column to memory_links for Hebbian co-retrieval strengthening.\n */\n private migrateV12ToV13(): void {\n const cols = this.db.prepare(\"PRAGMA table_info(memory_links)\").all() as { name: string }[];\n const colNames = new Set(cols.map((c) => c.name));\n if (!colNames.has(\"weight\")) {\n this.db.exec(\"ALTER TABLE memory_links ADD COLUMN weight REAL NOT NULL DEFAULT 1.0\");\n }\n console.error(\"[remem-mcp] Migrated schema v12 → v13 (Hebbian link weights)\");\n }\n\n async put(entry: CaptureEntry): Promise<void> {\n this.putInternal(entry);\n }\n\n private putInternal(entry: CaptureEntry): void {\n const contentHash =\n entry.contentHash ?? createHash(\"sha256\").update(entry.content).digest(\"hex\");\n // Auto-assign tier based on capture type (v10 authority-aware ranking)\n const tier = autoAssignTier(entry.type, entry.tags);\n // Atomically check + insert to close the TOCTOU race in capture dedup. Callers\n // (handleCapture, handoff, adr, sdk) first call findByContentHash and then put;\n // without a transaction two concurrent captures with identical content could\n // both pass the check and both insert duplicate rows. Wrapping the dedup probe\n // and the INSERT in a single db.transaction holds the write lock for the whole\n // operation, so the second concurrent put observes the first's row and skips.\n // Soft-deleted (forgotten) rows are excluded so a forgotten capture can be\n // re-captured. This is scoped to (content_hash, session_key, agent_id) to match\n // findByContentHash's dedup scope.\n const putTx = this.db.transaction(() => {\n const dup = this.db\n .prepare(\n \"SELECT 1 FROM captures WHERE content_hash = ? AND session_key = ? AND agent_id = ? AND deleted_at IS NULL LIMIT 1\",\n )\n .get(contentHash, entry.sessionKey, entry.agentId);\n if (dup) return false;\n this.db\n .prepare(\n `INSERT INTO captures (id, session_key, agent_id, type, content, content_hash, tags, created_at, metadata, team_id, user_id, task_id, trust_state, tier)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n entry.id,\n entry.sessionKey,\n entry.agentId,\n entry.type,\n entry.content,\n contentHash,\n JSON.stringify(entry.tags),\n entry.createdAt,\n entry.metadata ? JSON.stringify(entry.metadata) : null,\n entry.teamId ?? null,\n entry.userId ?? null,\n entry.taskId ?? null,\n entry.trustState ?? \"candidate\",\n tier,\n );\n // Store role-based messages inside the same transaction so a crash can't\n // orphan messages under a capture that didn't commit.\n if (entry.messages && entry.messages.length > 0) {\n const msgStmt = this.db.prepare(\n \"INSERT INTO messages (id, capture_id, role, content, seq, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n );\n for (let i = 0; i < entry.messages.length; i++) {\n const msg = entry.messages[i];\n msgStmt.run(generateId(), entry.id, msg.role, msg.content, i, entry.createdAt);\n }\n }\n return true;\n });\n const inserted = putTx();\n // v10: Extract and store entities for entity-assisted recall\n // v11: Auto-link to existing captures via shared tags/entities/session-proximity\n if (inserted) {\n try {\n const entities = extractEntities(entry.content);\n if (entities.length > 0) {\n this.putEntitiesInternal(entry.id, entities);\n }\n this.autoLinkCapture(entry.id, entry.tags, entities, entry.sessionKey);\n } catch {\n // non-fatal — entity extraction + auto-linking are supplementary\n }\n // v12: Audit log\n this.recordAuditInternal(\"put\", entry.id, { type: entry.type, tags: entry.tags }, entry.agentId);\n }\n }\n\n async putVector(id: string, embedding: number[]): Promise<void> {\n const buffer = new Float32Array(embedding);\n // INSERT OR REPLACE so re-embedding (e.g. after update) replaces the old vector\n // instead of failing with UNIQUE constraint and leaving stale embedding.\n const stmt = this.db.prepare(\n \"INSERT OR REPLACE INTO captures_vec (id, embedding) VALUES (?, ?)\",\n );\n stmt.run(id, Buffer.from(buffer.buffer));\n }\n\n /** Record that a capture was accessed (for access-frequency boosting). */\n recordAccess(ids: string[]): void {\n if (ids.length === 0) return;\n const now = Date.now();\n const stmt = this.db.prepare(\n \"UPDATE captures SET access_count = access_count + 1, last_accessed_at = ? WHERE id = ?\",\n );\n const tx = this.db.transaction(() => {\n for (const id of ids) stmt.run(now, id);\n });\n tx();\n }\n\n /** Confirm a capture (Bayesian: increment alpha). Increases confidence. */\n confirmCapture(id: string): void {\n this.db.prepare(\"UPDATE captures SET confirmations = confirmations + 1 WHERE id = ?\").run(id);\n }\n\n /** Correct a capture (Bayesian: increment beta). Decreases confidence. */\n correctCapture(id: string): void {\n this.db.prepare(\"UPDATE captures SET corrections = corrections + 1 WHERE id = ?\").run(id);\n }\n\n /** Record that a correction was retrieved in search results. */\n incrementRetrievedCount(id: string): void {\n this.db\n .prepare(\"UPDATE captures SET retrieved_count = retrieved_count + 1 WHERE id = ?\")\n .run(id);\n }\n\n /** Record whether a correction was heeded or the error recurred. */\n recordCorrectionOutcome(id: string, outcome: \"heeded\" | \"recurred\"): void {\n if (outcome === \"heeded\") {\n this.db\n .prepare(\n \"UPDATE captures SET heeded_count = heeded_count + 1, last_outcome = 'heeded' WHERE id = ?\",\n )\n .run(id);\n } else {\n this.db\n .prepare(\n \"UPDATE captures SET recurrence_count = recurrence_count + 1, last_outcome = 'recurred' WHERE id = ?\",\n )\n .run(id);\n }\n }\n\n /** Get correction KPIs: precision, heed rate, noise/high-signal candidates. */\n getCorrectionKPIs(): {\n totalCorrections: number;\n avgPrecision: number;\n heedRate: number;\n noiseCandidates: { id: string; content: string; precision: number }[];\n highSignalCandidates: { id: string; content: string; precision: number }[];\n } {\n const rows = this.db\n .prepare(\n \"SELECT id, content, retrieved_count, heeded_count, recurrence_count FROM captures WHERE type = 'error' AND corrections > 0 AND deleted_at IS NULL\",\n )\n .all() as {\n id: string;\n content: string;\n retrieved_count: number;\n heeded_count: number;\n recurrence_count: number;\n }[];\n\n const candidates = rows.map((r) => {\n const total = r.heeded_count + r.recurrence_count;\n const precision = total > 0 ? r.heeded_count / total : 0;\n return { id: r.id, content: r.content.slice(0, 80), precision };\n });\n\n const totalHeeded = rows.reduce((s, r) => s + r.heeded_count, 0);\n const totalOutcomes = rows.reduce((s, r) => s + r.heeded_count + r.recurrence_count, 0);\n const meanPrecision =\n candidates.length > 0\n ? candidates.reduce((s, c) => s + c.precision, 0) / candidates.length\n : 0;\n\n return {\n totalCorrections: rows.length,\n avgPrecision: meanPrecision,\n heedRate: totalOutcomes > 0 ? totalHeeded / totalOutcomes : 0,\n noiseCandidates: candidates\n .filter((c) => c.precision < 0.3)\n .sort((a, b) => a.precision - b.precision),\n highSignalCandidates: candidates\n .filter((c) => c.precision >= 0.8)\n .sort((a, b) => b.precision - a.precision),\n };\n }\n\n /** Compute Bayesian confidence score (Beta-Bernoulli model).\n * confidence = (alpha / (alpha + beta)) * decay + floor * (1 - decay)\n * where alpha = 1 + confirmations, beta = 1 + corrections,\n * decay = 0.5^(days_since_last_confirmation / 90), floor = 0.05.\n */\n bayesianConfidence(confirmations: number, corrections: number, lastAccessed?: number): number {\n const alpha = 1 + confirmations;\n const beta = 1 + corrections;\n const base = alpha / (alpha + beta);\n if (!lastAccessed) return base;\n const daysSince = (Date.now() - lastAccessed) / (24 * 60 * 60 * 1000);\n const decay = 0.5 ** (daysSince / 90);\n return base * decay + 0.05 * (1 - decay);\n }\n\n async get(id: string): Promise<CaptureEntry | null> {\n const row = this.db\n .prepare(\"SELECT * FROM captures WHERE id = ? AND deleted_at IS NULL\")\n .get(id) as DbRow | undefined;\n if (!row) return null;\n return rowToEntry(row);\n }\n\n async findRejectedByContentHash(\n contentHash: string,\n _sessionKey?: string,\n _agentId?: string,\n ): Promise<CaptureEntry[]> {\n // Global tombstone: a rejected value is blocked across all projects and\n // agents, not just the one that rejected it. The content_hash is computed\n // from redacted content, so the same secret or wrong value produces the\n // same hash regardless of which session or agent captured it.\n const sql =\n \"SELECT * FROM captures WHERE content_hash = ? AND trust_state = 'rejected' LIMIT 1\";\n const rows = this.db.prepare(sql).all(contentHash) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async getMessages(captureId: string): Promise<MessageRow[]> {\n const rows = this.db\n .prepare(\"SELECT * FROM messages WHERE capture_id = ? ORDER BY seq ASC\")\n .all(captureId) as MessageDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n role: r.role,\n content: r.content,\n seq: r.seq,\n createdAt: r.created_at,\n }));\n }\n\n async findByContentHash(\n contentHash: string,\n sessionKey?: string,\n agentId?: string,\n ): Promise<CaptureEntry[]> {\n let sql = \"SELECT * FROM captures WHERE content_hash = ? AND deleted_at IS NULL\";\n const params: unknown[] = [contentHash];\n if (sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n if (agentId) {\n sql += \" AND agent_id = ?\";\n params.push(agentId);\n }\n const rows = this.db.prepare(sql).all(...params) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async listByTags(tags: string[], limit = 50, sessionKey?: string): Promise<CaptureEntry[]> {\n if (tags.length === 0) return [];\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = tags.map(() => \"tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n let sql = `SELECT * FROM captures WHERE (${tagConditions}) AND deleted_at IS NULL AND trust_state != 'rejected' AND superseded_by IS NULL`;\n const params: unknown[] = [...tags.map((t) => `%\"${escapeLike(t)}\"%`)];\n if (sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(sessionKey);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(limit);\n const rows = this.db.prepare(sql).all(...params) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n async listAll(limit = 50, offset = 0): Promise<CaptureEntry[]> {\n const rows = this.db\n .prepare(\n \"SELECT * FROM captures WHERE deleted_at IS NULL AND trust_state != 'rejected' AND superseded_by IS NULL AND id IS NOT NULL ORDER BY created_at DESC LIMIT ? OFFSET ?\",\n )\n .all(limit, offset) as DbRow[];\n return rows.map(rowToEntry);\n }\n\n deleteAtomsByCaptureId(captureId: string): void {\n this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\").run(captureId);\n }\n\n async search(\n query: string,\n queryEmbedding: number[] | null,\n opts: QueryOptions,\n ): Promise<SearchResult[]> {\n const { mode, limit, offset, sessionKey, filters, explain } = opts;\n\n // v12: Per-hit score details for explain mode\n const explainMap = new Map<string, ScoreDetails>();\n\n // Detect temporal intent: queries asking for \"current/latest/now\" state\n // should strongly prefer recent memories over older ones with better keyword match.\n const temporalIntent = /\\b(currently|current|now|latest|new|present|today|active)\\b/i.test(\n query,\n );\n\n let bm25Results: RankedResult[] = [];\n let vecResults: RankedResult[] = [];\n\n // In hybrid mode, use different candidate pool sizes for each channel:\n // - BM25: tight pool (limit*2) for high precision — only best keyword matches\n // - Vector: very broad pool (limit*10, cap 1000) for high recall at scale\n // This ensures seed memories stored among 1000+ distractors are still found.\n // RRF fusion with weighted vector search filters out BM25 noise.\n const bm25CandidateLimit = mode === \"hybrid\" ? limit * 2 : limit * 2;\n // When row-level filters (agent_id, team_id, type, tags, ...) are present,\n // sqlite-vec applies them as a POST-filter on the KNN result. A small vec.k\n // therefore returns very few surviving rows when the matching subset is small\n // (e.g. a benchmark run with a unique agent_id among many other captures).\n // Use a very broad KNN pool so enough candidates survive the post-filter.\n const hasFilter = !!(\n filters?.agentId ||\n filters?.teamId ||\n filters?.userId ||\n filters?.taskId ||\n filters?.type ||\n (filters?.tags && filters.tags.length > 0)\n );\n const vecCandidateLimit = hasFilter\n ? Math.min(Math.max(limit * 50, 2000), 5000)\n : mode === \"hybrid\"\n ? Math.min(Math.max(limit * 10, 100), 1000)\n : limit * 2;\n\n // Vector search (sqlite-vec) — run first so we can determine if we're at scale\n if ((mode === \"hybrid\" || mode === \"vector\") && queryEmbedding) {\n vecResults = this.vectorSearch(queryEmbedding, vecCandidateLimit, sessionKey, filters);\n }\n\n // At scale (many vector results), we use a larger RRF pool and recency pool.\n const isScale = vecResults.length > 100;\n\n // BM25 search (FTS5)\n if (mode === \"hybrid\" || mode === \"keyword\") {\n bm25Results = this.bm25Search(query, bm25CandidateLimit, sessionKey, filters);\n }\n\n // v10: Entity-assisted recall — extract entities from query, search entities table\n let entityResults: RankedResult[] = [];\n let queryEntities: string[] = [];\n if (mode === \"hybrid\" || mode === \"keyword\") {\n try {\n queryEntities = extractEntities(query);\n if (queryEntities.length > 0) {\n entityResults = this.searchByEntities(\n queryEntities,\n limit * 2,\n sessionKey,\n filters ? { teamId: filters.teamId, userId: filters.userId, taskId: filters.taskId, type: filters.type } : undefined,\n );\n }\n } catch {\n // non-fatal — entity search is supplementary\n }\n }\n\n // v12: Populate explain map with per-stream ranks\n if (explain) {\n bm25Results.forEach((r, i) => {\n explainMap.set(r.id, { ...(explainMap.get(r.id) ?? {}), bm25_rank: i + 1, bm25_score: r.score });\n });\n vecResults.forEach((r, i) => {\n explainMap.set(r.id, { ...(explainMap.get(r.id) ?? {}), vector_rank: i + 1, vector_score: r.score });\n });\n entityResults.forEach((r, i) => {\n explainMap.set(r.id, { ...(explainMap.get(r.id) ?? {}), entity_rank: i + 1, entity_matches: queryEntities });\n });\n }\n\n if (mode === \"keyword\") {\n // Merge BM25 + entity results via simple RRF\n const allResults = [...bm25Results, ...entityResults];\n if (entityResults.length > 0) {\n // Simple RRF merge: combine by rank\n const scores = new Map<string, number>();\n const sortedBm25 = [...bm25Results].sort((a, b) => a.score - b.score);\n sortedBm25.forEach((r, i) => {\n scores.set(r.id, (scores.get(r.id) ?? 0) + 1 / (40 + i + 1));\n });\n entityResults.forEach((r, i) => {\n scores.set(r.id, (scores.get(r.id) ?? 0) + r.score);\n });\n const merged = [...scores.entries()]\n .map(([id, score]) => ({ id, score }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit + offset);\n return this.fetchEntriesById(merged.slice(offset, offset + limit), temporalIntent, false, explain ? explainMap : undefined);\n }\n return this.fetchEntriesById((allResults.length > 0 ? allResults : bm25Results).slice(offset, offset + limit), temporalIntent, false, explain ? explainMap : undefined);\n }\n if (mode === \"vector\") {\n // vecResults carry raw L2 distance (lower = better). fetchEntriesById expects\n // higher = better (it multiplies by trust/recency boosts and sorts descending),\n // so convert to a similarity score before handing off — otherwise the worst\n // matches would be boosted and sorted to the top.\n const similarityResults = vecResults.map((r) => ({ id: r.id, score: 1 / (1 + r.score) }));\n return this.fetchEntries(similarityResults, limit, offset, temporalIntent);\n }\n\n // Hybrid: fuse with RRF.\n // At scale (many captures), recently-stored seeds may rank low in pure RRF\n // because 1000+ distractors can have better BM25/vector scores. Instead of\n // relying on RRF alone, use a vector-first fusion: take a broad pool of vector\n // results and let the recency boost in fetchEntriesById surface the newest\n // captures. BM25 results are still merged via RRF for keyword-matching seeds.\n // For small result sets (e.g. 20 batch settings), keep the pool tight so\n // BM25 keyword matches aren't overridden by recency among same-topic captures.\n if (isScale) {\n // At scale, merge BM25 via RRF for keyword-matching seeds, but also include\n // ALL vector results so the recency boost can surface recently-stored seeds\n // that rank low in vector distance but are the most recent captures.\n const rrfPoolSize = Math.min(Math.max((limit + offset) * 20, 100), 500);\n const fused = rrfMerge(bm25Results, vecResults, rrfPoolSize);\n // v10: Add entity-match results to the fusion\n if (entityResults.length > 0) {\n const entityIds = new Set(entityResults.map((e) => e.id));\n for (const e of entityResults) {\n if (!fused.some((f) => f.id === e.id)) {\n fused.push(e);\n }\n }\n }\n const fusedIds = new Set(fused.map((f) => f.id));\n // Include all vector results not already in the RRF fusion.\n const extraVec = vecResults\n .filter((v) => !fusedIds.has(v.id))\n .map((v) => ({ id: v.id, score: 0 }));\n const paged = [...fused, ...extraVec];\n const scored = await this.fetchEntriesById(paged, temporalIntent, true);\n return scored.slice(0, limit);\n }\n // v10: Add entity results to the RRF fusion before paging\n const fused = rrfMerge(bm25Results, vecResults, limit + offset);\n if (entityResults.length > 0) {\n for (const e of entityResults) {\n if (!fused.some((f) => f.id === e.id)) {\n fused.push(e);\n }\n }\n fused.sort((a, b) => b.score - a.score);\n }\n // v11: Link-neighbor expansion — expand top results via memory_links (1-hop)\n const topIds = fused.slice(0, Math.min(limit, 20)).map((f) => f.id);\n if (topIds.length > 0) {\n const expanded = this.expandByLinks(topIds, limit * 2);\n const fusedIds = new Set(fused.map((f) => f.id));\n for (const exp of expanded) {\n if (!fusedIds.has(exp.id)) {\n fused.push(exp);\n // v12: Record link provenance for explain mode\n if (explain) {\n const sourceId = topIds.find((id) =>\n this.getLinksFrom(id).some((l) => l.to_id === exp.id) ||\n this.getLinksTo(id).some((l) => l.from_id === exp.id)\n );\n explainMap.set(exp.id, {\n ...(explainMap.get(exp.id) ?? {}),\n link_provenance: sourceId ? `expanded from ${sourceId}` : \"link-neighbor\",\n });\n }\n }\n }\n fused.sort((a, b) => b.score - a.score);\n }\n const paged = fused.slice(offset, offset + limit);\n const results = await this.fetchEntriesById(paged, temporalIntent, false, explain ? explainMap : undefined);\n\n // v11: Raw observation fallback — if no results, search raw captures\n // including stale/rejected (but not deleted). This catches cases where\n // the compiled/filtered search misses but raw content matches.\n if (results.length === 0 && mode === \"hybrid\") {\n const rawResults = this.rawFallbackSearch(query, limit, sessionKey, filters);\n if (rawResults.length > 0) {\n if (explain) {\n rawResults.forEach((r) => {\n explainMap.set(r.id, { ...(explainMap.get(r.id) ?? {}), raw_fallback: true });\n });\n }\n return this.fetchEntriesById(rawResults.slice(0, limit), temporalIntent, false, explain ? explainMap : undefined);\n }\n }\n // v13: Hebbian co-retrieval strengthening — strengthen links between co-occurring results\n if (results.length >= 2) {\n try {\n this.strengthenLinksOnCoRetrieval(results.map((r) => r.entry.id));\n } catch {\n // non-fatal — Hebbian strengthening is supplementary\n }\n }\n return results;\n }\n\n /**\n * v11: Raw observation fallback search.\n * Searches FTS5 without trust_state filter — includes stale and rejected.\n * Still excludes soft-deleted and superseded (those are intentionally removed).\n */\n private rawFallbackSearch(\n query: string,\n limit: number,\n sessionKey?: string,\n filters?: SearchFilters,\n ): { id: string; score: number }[] {\n const ftsQuery = this.escapeFtsQuery(query);\n if (!ftsQuery) return [];\n const conditions = [\"c.deleted_at IS NULL\", \"c.superseded_by IS NULL\"];\n const params: unknown[] = [];\n if (sessionKey) {\n conditions.push(\"c.session_key = ?\");\n params.push(sessionKey);\n }\n if (filters?.teamId) {\n conditions.push(\"c.team_id = ?\");\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n conditions.push(\"c.user_id = ?\");\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n conditions.push(\"c.task_id = ?\");\n params.push(filters.taskId);\n }\n if (filters?.type) {\n conditions.push(\"c.type = ?\");\n params.push(filters.type);\n }\n try {\n const rows = this.db\n .prepare(\n `SELECT fts.id as id, bm25(captures_fts) as score\n FROM captures_fts fts\n JOIN captures c ON c.id = fts.id\n WHERE captures_fts MATCH ? AND ${conditions.join(\" AND \")}\n ORDER BY score ASC\n LIMIT ?`,\n )\n .all(ftsQuery, ...params, limit) as { id: string; score: number }[];\n // Convert BM25 (negative, lower=better) to positive RRF-style score\n return rows.map((r, i) => ({\n id: r.id,\n score: 1 / (40 + i + 1),\n }));\n } catch {\n return [];\n }\n }\n\n /** Run a BM25 search via FTS5. */\n private bm25Search(\n query: string,\n limit: number,\n sessionKey?: string,\n filters?: QueryOptions[\"filters\"],\n ): RankedResult[] {\n const ftsQuery = this.escapeFtsQuery(query);\n if (!ftsQuery) return [];\n\n let sql: string = \"\";\n const params: unknown[] = [ftsQuery];\n try {\n sql = `\n SELECT fts.id as id, bm25(captures_fts) as score\n FROM captures_fts fts\n JOIN captures c ON c.id = fts.id\n WHERE captures_fts MATCH ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL\n `;\n\n if (sessionKey) {\n sql += \" AND c.session_key = ?\";\n params.push(sessionKey);\n }\n if (filters?.type) {\n sql += \" AND c.type = ?\";\n params.push(filters.type);\n }\n if (filters?.agentId) {\n sql += \" AND c.agent_id = ?\";\n params.push(filters.agentId);\n }\n if (filters?.teamId) {\n sql += \" AND c.team_id = ?\";\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n sql += \" AND c.user_id = ?\";\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n sql += \" AND c.task_id = ?\";\n params.push(filters.taskId);\n }\n if (filters?.tags && filters.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filters.tags.map(() => \"c.tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filters.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n if (filters?.dateFrom) {\n sql += \" AND c.created_at >= ?\";\n params.push(new Date(filters.dateFrom).getTime());\n }\n if (filters?.dateTo) {\n sql += \" AND c.created_at <= ?\";\n params.push(new Date(filters.dateTo).getTime());\n }\n\n sql += \" ORDER BY score LIMIT ?\";\n params.push(limit);\n\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n } catch (err: unknown) {\n // FTS5 external content tables can get out of sync after schema changes\n // or crashes. Rebuild the index and retry once.\n if (err instanceof Error && err.message.includes(\"missing row\")) {\n this.db.exec(\"INSERT INTO captures_fts(captures_fts) VALUES('rebuild')\");\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n }\n throw err;\n }\n }\n\n /** Run a vector search via sqlite-vec. */\n private vectorSearch(\n embedding: number[],\n limit: number,\n sessionKey?: string,\n filters?: QueryOptions[\"filters\"],\n ): RankedResult[] {\n const buffer = new Float32Array(embedding);\n let sql = `\n SELECT vec.id as id, vec.distance as score\n FROM captures_vec vec\n JOIN captures c ON c.id = vec.id\n WHERE vec.embedding MATCH ? AND vec.k = ? AND c.deleted_at IS NULL AND c.trust_state != 'rejected' AND c.superseded_by IS NULL\n `;\n const params: unknown[] = [Buffer.from(buffer.buffer), limit];\n\n if (sessionKey) {\n sql += \" AND c.session_key = ?\";\n params.push(sessionKey);\n }\n if (filters?.type) {\n sql += \" AND c.type = ?\";\n params.push(filters.type);\n }\n if (filters?.agentId) {\n sql += \" AND c.agent_id = ?\";\n params.push(filters.agentId);\n }\n if (filters?.teamId) {\n sql += \" AND c.team_id = ?\";\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n sql += \" AND c.user_id = ?\";\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n sql += \" AND c.task_id = ?\";\n params.push(filters.taskId);\n }\n if (filters?.tags && filters.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filters.tags.map(() => \"c.tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filters.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n if (filters?.dateFrom) {\n sql += \" AND c.created_at >= ?\";\n params.push(new Date(filters.dateFrom).getTime());\n }\n if (filters?.dateTo) {\n sql += \" AND c.created_at <= ?\";\n params.push(new Date(filters.dateTo).getTime());\n }\n\n sql += \" ORDER BY vec.distance LIMIT ?\";\n params.push(limit);\n\n const rows = this.db.prepare(sql).all(...params) as { id: string; score: number }[];\n return rows.map((r) => ({ id: r.id, score: r.score }));\n }\n\n /** Fetch capture entries for a list of ranked results. */\n private async fetchEntries(\n results: RankedResult[],\n limit: number,\n offset: number,\n temporalIntent: boolean = false,\n ): Promise<SearchResult[]> {\n const paged = results.slice(offset, offset + limit);\n return this.fetchEntriesById(paged, temporalIntent);\n }\n\n /** Fetch capture entries by ID, preserving the order of the input list. Applies memory decay and trust-state ranking. */\n private async fetchEntriesById(\n results: { id: string; score: number }[],\n temporalIntent: boolean = false,\n scaleBoost: boolean = false,\n explainMap?: Map<string, ScoreDetails>,\n ): Promise<SearchResult[]> {\n if (results.length === 0) return [];\n const ids = results.map((r) => r.id);\n const placeholders = ids.map(() => \"?\").join(\",\");\n const rows = this.db\n .prepare(`SELECT * FROM captures WHERE id IN (${placeholders})`)\n .all(...ids) as DbRow[];\n const rowMap = new Map(rows.map((r) => [r.id, r]));\n const now = Date.now();\n // Configurable half-life: default 30 days. Set REMEM_HALF_LIFE_DAYS to override.\n const halfLifeDays = Number(process.env.REMEM_HALF_LIFE_DAYS) || 30;\n const HALF_LIFE_MS = halfLifeDays * 24 * 60 * 60 * 1000;\n // Auto-stale threshold: captures older than this are treated as stale\n // (trust boost 0.1) regardless of their stored trust_state. Default 90 days.\n // Set REMEM_STALE_DAYS to override. Set to 0 to disable auto-stale.\n const staleDays = Number(process.env.STALE_DAYS) || Number(process.env.REMEM_STALE_DAYS) || 90;\n const STALE_MS = staleDays > 0 ? staleDays * 24 * 60 * 60 * 1000 : Infinity;\n // Trust-state multipliers: verified > candidate > stale\n const TRUST_BOOST: Record<string, number> = {\n verified: 1.5,\n candidate: 1.0,\n stale: 0.1,\n rejected: 0,\n };\n // For temporal-intent queries, use a much stronger recency boost (0.5 vs 0.3).\n // This ensures \"what database do we currently use\" returns the most recent fact,\n // even when an older fact has a better BM25 keyword match.\n // Non-temporal queries get a moderate recency boost (0.3) — enough to help\n // recently-stored seeds rank above older distractors at scale (Layer 3) without\n // overriding BM25 keyword matches for same-topic captures stored seconds apart.\n // At scale (1000+ distractors), use an additive recency boost so captures\n // with low RRF scores (e.g. vector-only matches) can still surface when\n // they're the most recent. The weight (0.1) is calibrated to be larger than\n // typical RRF scores (~0.05) so recency can overcome RRF rank differences,\n // but not so large as to completely ignore semantic relevance.\n const recencyWeight = temporalIntent ? 0.5 : scaleBoost ? 0.8 : 0.3;\n const sorted = results\n .map((r): SearchResult | null => {\n const row = rowMap.get(r.id);\n if (!row) return null;\n const ageMs = now - row.created_at;\n // Evergreen tags exempt a capture from temporal decay and auto-stale.\n // The capture keeps its full score regardless of age.\n const tags = row.tags ? (JSON.parse(row.tags) as string[]) : [];\n const evergreen = isEvergreen(tags);\n const decay = evergreen ? 1.0 : 0.5 ** (ageMs / HALF_LIFE_MS);\n // Auto-stale: if capture is older than STALE_MS and not already verified,\n // treat it as stale (0.1 trust boost) to naturally fade old memories.\n // Evergreen captures are never auto-staled.\n const effectiveTrust =\n !evergreen && ageMs > STALE_MS && row.trust_state !== \"verified\"\n ? \"stale\"\n : (row.trust_state ?? \"candidate\");\n const trustBoost = TRUST_BOOST[effectiveTrust] ?? 1.0;\n // Bayesian confidence: blend trust boost with evidence-based confidence.\n // Captures with confirmations get higher confidence, corrections lower it.\n const confirmations = (row as { confirmations?: number }).confirmations ?? 0;\n const corrections = (row as { corrections?: number }).corrections ?? 0;\n const lastAccessedAt = (row as { last_accessed_at?: number }).last_accessed_at;\n const bayesian = this.bayesianConfidence(confirmations, corrections, lastAccessedAt);\n // Blend: 70% trust-state boost + 30% Bayesian evidence (when evidence exists)\n const blendedBoost =\n confirmations + corrections > 0 ? trustBoost * 0.7 + bayesian * 1.5 * 0.3 : trustBoost;\n const decayed = Number.isNaN(r.score) ? 0 : r.score * decay;\n // BM25 scores are negative (lower = better). For negative scores, divide by boost\n // so a lower boost makes the score more negative (ranks lower). For positive scores\n // (RRF fusion), multiply so a lower boost makes the score lower.\n const finalScore = decayed >= 0 ? decayed * blendedBoost : decayed / blendedBoost;\n // Recency tiebreaker: add a boost proportional to how recent the memory is.\n // For temporal-intent queries (\"currently\", \"now\", \"latest\"), use a much faster\n // decay (1s vs 1min) so even memories stored milliseconds apart are differentiated,\n // and a stronger weight (0.5 vs 0.05) so recency dominates keyword match score.\n const recencyDecayMs = temporalIntent ? 1000 : 10000;\n const recencyBias = 1 / (1 + ageMs / recencyDecayMs);\n // At scale, use an additive recency boost so captures with low RRF scores\n // (e.g. vector-only matches that didn't rank in BM25) can still surface\n // when they're the most recent. A multiplicative boost on score=0 stays 0.\n const biasedScore =\n scaleBoost && finalScore >= 0\n ? finalScore + recencyBias * recencyWeight\n : finalScore >= 0\n ? finalScore * (1 + recencyBias * recencyWeight)\n : finalScore / (1 + recencyBias * recencyWeight);\n // Access-frequency boost (Mem0-style): memories accessed recently get up to\n // 1.5x boost, idle memories (never accessed or accessed long ago) get 0.3x floor.\n // This rewards \"popular\" memories that agents keep recalling.\n const accessCount = (row as { access_count?: number }).access_count ?? 0;\n const lastAccessed = (row as { last_accessed_at?: number }).last_accessed_at;\n const accessBoost =\n accessCount > 0 && lastAccessed\n ? 0.3 + 1.2 * (1 / (1 + (now - lastAccessed) / (7 * 24 * 60 * 60 * 1000))) // 7-day half-life\n : 1.0; // No access data → neutral\n const accessScored =\n biasedScore >= 0 ? biasedScore * accessBoost : biasedScore / accessBoost;\n // v10: Authority-aware ranking — bounded multiplier based on tier + tags.\n // Applied after all other boosts, before final sort. Clamped to 0.5x-1.5x.\n const tier = (row as { tier?: string }).tier ?? \"episodic\";\n const authBoost = authorityMultiplier(tier, tags);\n const authScored =\n accessScored >= 0 ? accessScored * authBoost : accessScored / authBoost;\n // v12: Feedback salience multiplier (0.1x–2.0x) from helpful/not_helpful/stale/wrong signals.\n const feedbackSalience = (row as { feedback_salience?: number }).feedback_salience ?? 1.0;\n const feedbackScored =\n feedbackSalience !== 1.0\n ? authScored >= 0\n ? authScored * feedbackSalience\n : authScored / feedbackSalience\n : authScored;\n // v12: Attach score details for explain mode\n const scoreDetails = explainMap\n ? {\n ...(explainMap.get(row.id) ?? {}),\n authority_multiplier: authBoost,\n feedback_salience: feedbackSalience,\n }\n : undefined;\n return { entry: rowToEntry(row), score: feedbackScored, scoreDetails };\n })\n .filter((r): r is SearchResult => r !== null)\n // For temporal-intent queries (\"currently\", \"now\", \"latest\"), filter out stale\n // memories entirely. This ensures outdated facts don't appear in results at all,\n // which is required when benchmarks check for unexpected keywords like old values.\n // Non-temporal queries (\"before\", \"previous\") still include stale memories.\n .filter((r) => !temporalIntent || r.entry.trustState !== \"stale\")\n // For temporal-intent queries, sort by created_at DESC first (most recent wins),\n // then by score. This ensures \"what database do we currently use\" returns the\n // latest fact even when an older fact has a better keyword match.\n .sort((a, b) => {\n if (temporalIntent) {\n const timeDiff = b.entry.createdAt - a.entry.createdAt;\n // Any positive recency difference wins. We don't gate on a large\n // threshold because benchmark runs with --no-delay store seeds only\n // milliseconds apart, yet the store order still encodes the intended\n // chronology (later stores have a higher created_at).\n if (timeDiff !== 0) return timeDiff;\n }\n return b.score - a.score;\n });\n\n // For temporal-intent queries, drop older captures that are semantically\n // superseded by a more recent one (e.g. \"Database is MySQL\" is dropped when\n // \"Upgraded to PostgreSQL 16\" is present). Benchmarks check that outdated\n // values do not appear in ANY returned result, so ranking alone is not\n // enough — the older capture must be removed entirely. We greedily keep the\n // most recent capture per semantic cluster using embedding cosine similarity.\n if (temporalIntent) {\n const deduped = this.temporalDedup(sorted);\n this.recordAccess(deduped.map((r) => r.entry.id));\n return deduped;\n }\n this.recordAccess(sorted.map((r) => r.entry.id));\n return sorted;\n }\n\n /** Greedily drop older captures that are semantically similar to a newer kept one.\n * Input must be sorted by recency DESC. Uses L2 squared distance between embeddings\n * (matching sqlite-vec's distance metric). Same-topic facts (e.g. \"Database is MySQL\"\n * vs \"Upgraded to PostgreSQL 16\") sit within ~1.6; distinct topics sit above ~1.7. */\n private temporalDedup(sorted: SearchResult[]): SearchResult[] {\n if (sorted.length <= 1) return sorted;\n const ids = sorted.map((r) => r.entry.id);\n const placeholders = ids.map(() => \"?\").join(\",\");\n let rows: { id: string; embedding: Buffer }[];\n try {\n rows = this.db\n .prepare(`SELECT id, embedding FROM captures_vec WHERE id IN (${placeholders})`)\n .all(...ids) as { id: string; embedding: Buffer }[];\n } catch {\n // vec0 module unavailable or no vectors — fall back to no dedup\n return sorted;\n }\n const vecMap = new Map<string, Float32Array>();\n for (const row of rows) {\n try {\n vecMap.set(\n row.id,\n new Float32Array(\n row.embedding.buffer,\n row.embedding.byteOffset,\n row.embedding.byteLength / 4,\n ),\n );\n } catch {\n // ignore malformed vectors\n }\n }\n const l2sq = (a: Float32Array, b: Float32Array): number => {\n let s = 0;\n for (let i = 0; i < a.length; i++) {\n const d = a[i] - b[i];\n s += d * d;\n }\n return s;\n };\n // Same-topic facts cluster below ~1.6 L2²; distinct topics sit above ~1.7.\n const DUP_THRESHOLD = 1.7;\n const kept: SearchResult[] = [];\n const keptVecs: Float32Array[] = [];\n for (const r of sorted) {\n const v = vecMap.get(r.entry.id);\n if (!v) {\n // No vector for this capture — keep it (can't compare)\n kept.push(r);\n continue;\n }\n const isDup = keptVecs.some((kv) => l2sq(v, kv) <= DUP_THRESHOLD);\n if (!isDup) {\n kept.push(r);\n keptVecs.push(v);\n }\n }\n return kept;\n }\n\n /** Escape a query string for FTS5 MATCH.\n * Uses OR semantics with stopword removal for high recall.\n * AND semantics (all tokens must match) is too strict for natural language\n * questions — a 15-word query rarely has every token in a single capture,\n * causing BM25 to return zero results. OR semantics lets BM25 rank by\n * relevance (documents matching more terms rank higher) while still\n * returning partial matches. RRF fusion with weighted vector search\n * filters out BM25 noise.\n */\n private escapeFtsQuery(query: string): string {\n // Use the shared stripQueryProperNouns to get content words (stopwords + proper nouns removed).\n // Then format as FTS5 OR query.\n const stripped = stripQueryProperNouns(query);\n if (!stripped) return \"\";\n const tokens = stripped.split(/\\s+/).filter(Boolean);\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n }\n\n async delete(id: string): Promise<DeleteResult> {\n // Soft delete: set deleted_at instead of hard delete.\n // The captures_au trigger automatically syncs FTS on UPDATE.\n // Search filters by deleted_at IS NULL, so tombstoned captures are excluded.\n // Do NOT manually FTS-delete — the trigger already handled it, and a manual\n // delete with empty strings after the trigger re-inserted the entry corrupts\n // the FTS index (SQLITE_CORRUPT_VTAB).\n const now = Date.now();\n let captureCount = 0;\n let atomCount = 0;\n const tx = this.db.transaction(() => {\n captureCount = this.db\n .prepare(\"UPDATE captures SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL\")\n .run(now, id).changes;\n if (captureCount > 0) {\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(id);\n atomCount = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\").run(id).changes;\n }\n });\n tx();\n if (captureCount > 0) {\n return { captures: captureCount, atoms: atomCount, scenarios: 0 };\n }\n\n return {\n captures: captureCount,\n atoms: 0,\n scenarios: 0,\n };\n }\n\n async deleteByFilter(filter: DeleteFilter): Promise<DeleteResult> {\n let sql = \"SELECT id FROM captures WHERE deleted_at IS NULL\";\n const params: unknown[] = [];\n\n if (filter.type) {\n sql += \" AND type = ?\";\n params.push(filter.type);\n }\n if (filter.dateBefore) {\n sql += \" AND created_at < ?\";\n params.push(new Date(filter.dateBefore).getTime());\n }\n if (filter.teamId) {\n sql += \" AND team_id = ?\";\n params.push(filter.teamId);\n }\n if (filter.userId) {\n sql += \" AND user_id = ?\";\n params.push(filter.userId);\n }\n if (filter.taskId) {\n sql += \" AND task_id = ?\";\n params.push(filter.taskId);\n }\n if (filter.sessionKey) {\n sql += \" AND session_key = ?\";\n params.push(filter.sessionKey);\n }\n if (filter.tags && filter.tags.length > 0) {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n const tagConditions = filter.tags.map(() => \"tags LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n sql += ` AND (${tagConditions})`;\n params.push(...filter.tags.map((t) => `%\"${escapeLike(t)}\"%`));\n }\n\n const ids = this.db.prepare(sql).all(...params) as { id: string }[];\n\n let captures = 0;\n let atoms = 0;\n const scenarios = 0;\n // Wrap in transaction so new captures matching the filter can't be\n // inserted between SELECT and delete (atomic snapshot).\n const softDelete = this.db.prepare(\n \"UPDATE captures SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL\",\n );\n const deleteVec = this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\");\n const deleteAtoms = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\");\n const now = Date.now();\n const tx = this.db.transaction(() => {\n for (const { id } of ids) {\n const c = softDelete.run(now, id).changes;\n if (c > 0) {\n captures += c;\n deleteVec.run(id);\n atoms += deleteAtoms.run(id).changes;\n }\n }\n });\n tx();\n\n return { captures, atoms, scenarios };\n }\n\n async reject(id: string, reason: string): Promise<DeleteResult> {\n // Same as delete(): the captures_au trigger syncs FTS on UPDATE.\n // Search filters by trust_state != 'rejected' AND deleted_at IS NULL.\n // Manual FTS delete after trigger re-insert corrupts the index.\n const now = Date.now();\n let captureCount = 0;\n let atomCount = 0;\n const tx = this.db.transaction(() => {\n captureCount = this.db\n .prepare(\n \"UPDATE captures SET trust_state = 'rejected', rejection_reason = ?, deleted_at = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'\",\n )\n .run(reason, now, id).changes;\n if (captureCount > 0) {\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(id);\n atomCount = this.db.prepare(\"DELETE FROM atoms WHERE capture_id = ?\").run(id).changes;\n }\n });\n tx();\n if (captureCount > 0) {\n return { captures: captureCount, atoms: atomCount, scenarios: 0 };\n }\n\n return { captures: captureCount, atoms: 0, scenarios: 0 };\n }\n\n async findConflicts(\n embedding: number[],\n sessionKey: string,\n threshold: number,\n ): Promise<ConflictResult[]> {\n const buffer = new Float32Array(embedding);\n const rows = this.db\n .prepare(\n `SELECT vec.id as id, vec.distance as distance, c.content as content, c.trust_state as trust_state\n FROM captures_vec vec\n JOIN captures c ON c.id = vec.id\n WHERE vec.embedding MATCH ? AND vec.k = 20\n AND c.deleted_at IS NULL\n AND c.trust_state IN ('candidate', 'verified')\n AND c.session_key = ?\n ORDER BY vec.distance\n LIMIT 10`,\n )\n .all(Buffer.from(buffer.buffer), sessionKey) as {\n id: string;\n distance: number;\n content: string;\n trust_state: string;\n }[];\n\n return rows\n .filter((r) => {\n // sqlite-vec returns L2 (euclidean) distance for float[] columns.\n // Convert to cosine distance: cosine_dist = L2^2 / 2 (for normalized vectors).\n // Filter by cosine distance threshold.\n const cosineDist = (r.distance * r.distance) / 2;\n return cosineDist < threshold;\n })\n .map((r) => ({\n id: r.id,\n content: r.content,\n // Return cosine distance (not L2) so the caller gets a meaningful value.\n distance: (r.distance * r.distance) / 2,\n trustState: r.trust_state as TrustState,\n }));\n }\n\n async supersede(loserId: string, winnerId: string): Promise<ResolveResult> {\n const updated = this.db\n .prepare(\n \"UPDATE captures SET trust_state = 'stale', superseded_by = ? WHERE id = ? AND deleted_at IS NULL AND trust_state != 'rejected'\",\n )\n .run(winnerId, loserId).changes;\n if (updated > 0) {\n // Remove loser's vector so dead embeddings don't accumulate in captures_vec\n this.db.prepare(\"DELETE FROM captures_vec WHERE id = ?\").run(loserId);\n }\n return { winnerId, loserId, updated };\n }\n\n async setTrustState(id: string, state: TrustState): Promise<number> {\n return this.db\n .prepare(\"UPDATE captures SET trust_state = ? WHERE id = ? AND deleted_at IS NULL\")\n .run(state, id).changes;\n }\n\n // ─── L1 atoms ───────────────────────────────────────────────\n\n async putAtom(atom: AtomEntry): Promise<void> {\n // Dedup: skip if same fact already exists (case-insensitive, trimmed)\n const existing = this.db\n .prepare(\"SELECT id FROM atoms WHERE LOWER(TRIM(fact)) = LOWER(TRIM(?)) LIMIT 1\")\n .get(atom.fact) as { id: string } | undefined;\n if (existing) return; // atom with same fact already exists\n\n this.db\n .prepare(\n \"INSERT INTO atoms (id, capture_id, fact, confidence, created_at, team_id, agent_id, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n atom.id,\n atom.captureId,\n atom.fact,\n atom.confidence,\n atom.createdAt,\n atom.teamId ?? null,\n atom.agentId ?? null,\n atom.userId ?? null,\n );\n }\n\n async listAtoms(opts: {\n teamId?: string;\n agentId?: string;\n userId?: string;\n captureId?: string;\n limit?: number;\n offset?: number;\n }): Promise<AtomEntry[]> {\n let sql = \"SELECT * FROM atoms WHERE 1=1\";\n const params: unknown[] = [];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n if (opts.captureId) {\n sql += \" AND capture_id = ?\";\n params.push(opts.captureId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ? OFFSET ?\";\n params.push(opts.limit ?? 20, opts.offset ?? 0);\n const rows = this.db.prepare(sql).all(...params) as AtomDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n fact: r.fact,\n confidence: r.confidence,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n async searchAtoms(\n query: string,\n opts: { teamId?: string; agentId?: string; userId?: string; limit?: number } = {},\n ): Promise<AtomEntry[]> {\n // Atoms don't have FTS — use LIKE for keyword search\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n let sql = \"SELECT * FROM atoms WHERE fact LIKE ? ESCAPE '\\\\'\";\n const params: unknown[] = [`%${escapeLike(query)}%`];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ?\";\n params.push(opts.limit ?? 20);\n const rows = this.db.prepare(sql).all(...params) as AtomDbRow[];\n return rows.map((r) => ({\n id: r.id,\n captureId: r.capture_id,\n fact: r.fact,\n confidence: r.confidence,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n // ─── L2 scenarios ───────────────────────────────────────────\n\n async putScenario(scenario: ScenarioEntry): Promise<void> {\n this.db\n .prepare(\n \"INSERT INTO scenarios (id, atom_ids, summary, persona_tags, created_at, team_id, agent_id, user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n scenario.id,\n JSON.stringify(scenario.atomIds),\n scenario.summary,\n scenario.personaTags ? JSON.stringify(scenario.personaTags) : null,\n scenario.createdAt,\n scenario.teamId ?? null,\n scenario.agentId ?? null,\n scenario.userId ?? null,\n );\n }\n\n async listScenarios(opts: {\n teamId?: string;\n agentId?: string;\n userId?: string;\n limit?: number;\n offset?: number;\n }): Promise<ScenarioEntry[]> {\n let sql = \"SELECT * FROM scenarios WHERE 1=1\";\n const params: unknown[] = [];\n if (opts.teamId) {\n sql += \" AND team_id = ?\";\n params.push(opts.teamId);\n }\n if (opts.agentId) {\n sql += \" AND agent_id = ?\";\n params.push(opts.agentId);\n }\n if (opts.userId) {\n sql += \" AND user_id = ?\";\n params.push(opts.userId);\n }\n sql += \" ORDER BY created_at DESC LIMIT ? OFFSET ?\";\n params.push(opts.limit ?? 20, opts.offset ?? 0);\n const rows = this.db.prepare(sql).all(...params) as ScenarioDbRow[];\n return rows.map((r) => ({\n id: r.id,\n atomIds: JSON.parse(r.atom_ids) as string[],\n summary: r.summary,\n personaTags: r.persona_tags ? (JSON.parse(r.persona_tags) as string[]) : undefined,\n createdAt: r.created_at,\n teamId: r.team_id ?? undefined,\n agentId: r.agent_id ?? undefined,\n userId: r.user_id ?? undefined,\n }));\n }\n\n async getScenario(id: string): Promise<ScenarioEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM scenarios WHERE id = ?\").get(id) as\n | ScenarioDbRow\n | undefined;\n if (!row) return null;\n return {\n id: row.id,\n atomIds: JSON.parse(row.atom_ids) as string[],\n summary: row.summary,\n personaTags: row.persona_tags ? (JSON.parse(row.persona_tags) as string[]) : undefined,\n createdAt: row.created_at,\n teamId: row.team_id ?? undefined,\n agentId: row.agent_id ?? undefined,\n userId: row.user_id ?? undefined,\n };\n }\n\n // ─── L3 persona ─────────────────────────────────────────────\n\n async readPersona(teamId: string, agentId: string, userId: string): Promise<PersonaEntry | null> {\n const row = this.db\n .prepare(\"SELECT * FROM persona WHERE team_id = ? AND agent_id = ? AND user_id = ?\")\n .get(teamId, agentId, userId) as PersonaDbRow | undefined;\n if (!row) return null;\n return {\n teamId: row.team_id,\n agentId: row.agent_id,\n userId: row.user_id,\n content: row.content,\n updatedAt: row.updated_at,\n };\n }\n\n async writePersona(\n teamId: string,\n agentId: string,\n userId: string,\n content: string,\n ): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO persona (team_id, agent_id, user_id, content, updated_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(team_id, agent_id, user_id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at`,\n )\n .run(teamId, agentId, userId, content, Date.now());\n }\n\n // ─── Knowledge ──────────────────────────────────────────────\n\n async putKnowledge(entry: KnowledgeEntry): Promise<void> {\n this.db\n .prepare(\n \"INSERT INTO knowledge (id, team_id, name, type, summary, service_url, repo_url, branch, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n )\n .run(\n entry.id,\n entry.teamId,\n entry.name,\n entry.type,\n entry.summary ?? null,\n entry.serviceUrl ?? null,\n entry.repoUrl ?? null,\n entry.branch ?? null,\n entry.createdAt,\n );\n }\n\n async getKnowledge(id: string): Promise<KnowledgeEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM knowledge WHERE id = ?\").get(id) as\n | KnowledgeDbRow\n | undefined;\n if (!row) return null;\n return knowledgeRowToEntry(row);\n }\n\n async listKnowledge(teamId: string, type?: string): Promise<KnowledgeEntry[]> {\n let sql = \"SELECT * FROM knowledge WHERE team_id = ?\";\n const params: unknown[] = [teamId];\n if (type) {\n sql += \" AND type = ?\";\n params.push(type);\n }\n sql += \" ORDER BY created_at DESC\";\n const rows = this.db.prepare(sql).all(...params) as KnowledgeDbRow[];\n return rows.map(knowledgeRowToEntry);\n }\n\n async deleteKnowledge(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n const placeholders = ids.map(() => \"?\").join(\",\");\n const result = this.db\n .prepare(`DELETE FROM knowledge WHERE id IN (${placeholders})`)\n .run(...ids);\n return result.changes;\n }\n\n // ─── Skills ─────────────────────────────────────────────────\n\n async putSkill(entry: SkillEntry): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO skills (id, team_id, agent_id, name, description, content, version, created_at, updated_at, trigger_conditions, steps, validation_rules, source_capture_ids, archived)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description, content = excluded.content, version = excluded.version, updated_at = excluded.updated_at, trigger_conditions = excluded.trigger_conditions, steps = excluded.steps, validation_rules = excluded.validation_rules, source_capture_ids = excluded.source_capture_ids, archived = excluded.archived`,\n )\n .run(\n entry.id,\n entry.teamId,\n entry.agentId ?? null,\n entry.name,\n entry.description ?? null,\n entry.content ?? null,\n entry.version,\n entry.createdAt,\n entry.updatedAt,\n entry.triggerConditions ? JSON.stringify(entry.triggerConditions) : null,\n entry.steps ? JSON.stringify(entry.steps) : null,\n entry.validationRules ? JSON.stringify(entry.validationRules) : null,\n entry.sourceCaptureIds ? JSON.stringify(entry.sourceCaptureIds) : null,\n entry.archived ? 1 : 0,\n );\n }\n\n async getSkill(id: string): Promise<SkillEntry | null> {\n const row = this.db.prepare(\"SELECT * FROM skills WHERE id = ?\").get(id) as\n | SkillDbRow\n | undefined;\n if (!row) return null;\n return skillRowToEntry(row);\n }\n\n async listSkills(teamId: string, agentId?: string): Promise<SkillEntry[]> {\n let sql = \"SELECT * FROM skills WHERE team_id = ?\";\n const params: unknown[] = [teamId];\n if (agentId) {\n sql += \" AND (agent_id = ? OR agent_id IS NULL)\";\n params.push(agentId);\n }\n sql += \" ORDER BY updated_at DESC\";\n const rows = this.db.prepare(sql).all(...params) as SkillDbRow[];\n return rows.map(skillRowToEntry);\n }\n\n async searchSkills(\n teamId: string,\n agentId: string,\n query: string,\n topK?: number,\n ): Promise<SkillEntry[]> {\n const escapeLike = (s: string) => s.replace(/[%_\\\\\"]/g, (c) => \"\\\\\" + c);\n let sql =\n \"SELECT * FROM skills WHERE team_id = ? AND (agent_id = ? OR agent_id IS NULL) AND (name LIKE ? ESCAPE '\\\\' OR description LIKE ? ESCAPE '\\\\')\";\n const params: unknown[] = [teamId, agentId, `%${escapeLike(query)}%`, `%${escapeLike(query)}%`];\n sql += \" ORDER BY updated_at DESC LIMIT ?\";\n params.push(topK ?? 10);\n const rows = this.db.prepare(sql).all(...params) as SkillDbRow[];\n return rows.map(skillRowToEntry);\n }\n\n close(): void {\n this.db.close();\n }\n\n // ─── Canvas + Refs (v9: symbolic short-term memory) ───────────────────\n // These methods implement the StorageBackend interface for F1 (Mermaid canvas).\n\n async appendCanvasNode(\n sessionKey: string,\n node: { id: string; label: string; captureId: string },\n edges: Array<{ from: string; to: string; label?: string }>,\n teamId?: string,\n ): Promise<void> {\n const now = Date.now();\n let canvasRow = this.db\n .prepare(\"SELECT id FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1\")\n .get(sessionKey) as { id: string } | undefined;\n if (!canvasRow) {\n const canvasId = `01${now.toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 10).toUpperCase()}`;\n this.db.prepare(\n \"INSERT INTO canvases (id, session_key, mermaid_text, node_count, created_at, updated_at, team_id) VALUES (?, ?, NULL, 0, ?, ?, ?)\",\n ).run(canvasId, sessionKey, now, now, teamId ?? null);\n canvasRow = { id: canvasId };\n }\n const maxSeq = this.db\n .prepare(\"SELECT MAX(seq) as max_seq FROM canvas_nodes WHERE canvas_id = ?\")\n .get(canvasRow.id) as { max_seq: number | null };\n const seq = (maxSeq.max_seq ?? 0) + 1;\n this.db.prepare(\n \"INSERT INTO canvas_nodes (id, canvas_id, node_id, label, seq, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n ).run(`01${now.toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 10).toUpperCase()}`, canvasRow.id, node.id, node.label, seq, now);\n for (const edge of edges) {\n this.db.prepare(\n \"INSERT INTO canvas_edges (id, canvas_id, from_node_id, to_node_id, label, created_at) VALUES (?, ?, ?, ?, ?, ?)\",\n ).run(`01${now.toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 10).toUpperCase()}`, canvasRow.id, edge.from, edge.to, edge.label ?? null, now);\n }\n this.db.prepare(\"UPDATE canvases SET node_count = node_count + 1, updated_at = ? WHERE id = ?\").run(now, canvasRow.id);\n\n // Regenerate mermaid_text from all nodes + edges so canvas_get can read it.\n const allNodes = this.db\n .prepare(\"SELECT node_id, label FROM canvas_nodes WHERE canvas_id = ? ORDER BY seq ASC\")\n .all(canvasRow.id) as { node_id: string; label: string }[];\n const allEdges = this.db\n .prepare(\"SELECT from_node_id, to_node_id, label FROM canvas_edges WHERE canvas_id = ?\")\n .all(canvasRow.id) as { from_node_id: string; to_node_id: string; label: string | null }[];\n const mermaidLines: string[] = [\"graph LR\"];\n for (const n of allNodes) {\n mermaidLines.push(` ${n.node_id}[\"${n.label.replace(/\"/g, \"'\")}\"]`);\n }\n for (const e of allEdges) {\n if (e.label) {\n mermaidLines.push(` ${e.from_node_id} -->|${e.label.replace(/\"/g, \"'\")}| ${e.to_node_id}`);\n } else {\n mermaidLines.push(` ${e.from_node_id} --> ${e.to_node_id}`);\n }\n }\n this.db.prepare(\"UPDATE canvases SET mermaid_text = ? WHERE id = ?\").run(mermaidLines.join(\"\\n\"), canvasRow.id);\n }\n\n async getLatestCanvasNode(\n sessionKey: string,\n ): Promise<{ id: string; label: string; captureId: string } | null> {\n const row = this.db\n .prepare(\n `SELECT cn.node_id as id, cn.label, cn.node_id as captureId\n FROM canvas_nodes cn\n JOIN canvases c ON cn.canvas_id = c.id\n WHERE c.session_key = ?\n ORDER BY cn.seq DESC LIMIT 1`,\n )\n .get(sessionKey) as { id: string; label: string; captureId: string } | undefined;\n return row ?? null;\n }\n\n async getCanvasMermaidText(sessionKey: string): Promise<string | null> {\n const row = this.db\n .prepare(\"SELECT mermaid_text FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1\")\n .get(sessionKey) as { mermaid_text: string | null } | undefined;\n return row?.mermaid_text ?? null;\n }\n\n async writeRef(sessionKey: string, nodeId: string, content: string): Promise<void> {\n const refsDir = join(homedir(), \".local\", \"share\", \"remem-mcp\", \"refs\", sessionKey);\n try {\n mkdirSync(refsDir, { recursive: true });\n writeFileSync(join(refsDir, `${nodeId}.md`), content, \"utf-8\");\n } catch (err) {\n console.error(`[remem-mcp] writeRef failed: ${err}`);\n }\n }\n\n async readRef(nodeId: string): Promise<string | null> {\n const refsBase = join(homedir(), \".local\", \"share\", \"remem-mcp\", \"refs\");\n try {\n const sessions = readdirSync(refsBase);\n for (const session of sessions) {\n const refPath = join(refsBase, session, `${nodeId}.md`);\n if (existsSync(refPath)) {\n return readFileSync(refPath, \"utf-8\");\n }\n }\n } catch {\n // refs dir doesn't exist yet\n }\n return null;\n }\n\n // ─── v10: Decay/forget sweep ──────────────────────────────────\n\n /**\n * Run a forget sweep: compute salience for all non-deleted, non-evergreen captures,\n * then soft-delete (set deleted_at) those below the threshold.\n *\n * Salience formula (adapted from ai-memory):\n * salience = exp(-lambda * age_days) + sigma * log(1 + access_count) * exp(-mu * days_since_access)\n *\n * - lambda: age decay rate (default 0.01, ~70 day half-life)\n * - sigma: access frequency weight (default 0.3)\n * - mu: access recency decay (default 0.02, ~35 day half-life)\n *\n * Evergreen tags and tier=rule/decision captures are exempt (salience stays 1.0).\n * Verified captures get a floor of 0.3.\n */\n async forgetSweep(opts: {\n dryRun?: boolean;\n threshold?: number;\n maxAgeDays?: number;\n } = {}): Promise<{ swept: number; remaining: number; checked: number }> {\n const dryRun = opts.dryRun ?? false;\n const threshold = opts.threshold ?? 0.05;\n const maxAgeDays = opts.maxAgeDays ?? 365;\n const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;\n\n // Decay parameters\n const lambda = 0.01;\n const sigma = 0.3;\n const mu = 0.02;\n const now = Date.now();\n\n // Select candidates: non-deleted, not evergreen, older than maxAgeDays\n // v12: Also select TTL-expired captures (expires_at < now) — these are hard-deleted regardless of salience\n const rows = this.db\n .prepare(\n `SELECT id, type, tags, created_at, access_count, last_accessed_at, trust_state, tier, expires_at, feedback_salience\n FROM captures\n WHERE deleted_at IS NULL AND (created_at < ? OR (expires_at IS NOT NULL AND expires_at < ?))`,\n )\n .all(now - maxAgeMs, now) as {\n id: string;\n type: string;\n tags: string | null;\n created_at: number;\n access_count: number;\n last_accessed_at: number | null;\n trust_state: string;\n tier: string;\n expires_at: number | null;\n feedback_salience: number;\n }[];\n\n let swept = 0;\n let remaining = 0;\n const toDelete: string[] = [];\n\n for (const row of rows) {\n // v12: TTL-expired captures are hard-deleted regardless of salience/tier\n if (row.expires_at !== null && row.expires_at < now) {\n toDelete.push(row.id);\n swept++;\n continue;\n }\n\n const tags = row.tags ? (JSON.parse(row.tags) as string[]) : [];\n const evergreen = isEvergreen(tags);\n // Rule and decision tiers are exempt from sweep\n if (evergreen || row.tier === \"rule\" || row.tier === \"decision\") {\n remaining++;\n continue;\n }\n\n const ageDays = (now - row.created_at) / (24 * 60 * 60 * 1000);\n const daysSinceAccess = row.last_accessed_at\n ? (now - row.last_accessed_at) / (24 * 60 * 60 * 1000)\n : ageDays;\n\n const ageTerm = Math.exp(-lambda * ageDays);\n const accessTerm =\n sigma * Math.log(1 + (row.access_count ?? 0)) * Math.exp(-mu * daysSinceAccess);\n let salience = ageTerm + accessTerm;\n\n // Verified captures get a floor\n if (row.trust_state === \"verified\") {\n salience = Math.max(salience, 0.3);\n }\n\n // v12: Apply feedback salience multiplier\n salience *= row.feedback_salience ?? 1.0;\n\n if (salience < threshold) {\n toDelete.push(row.id);\n swept++;\n } else {\n remaining++;\n }\n }\n\n if (!dryRun && toDelete.length > 0) {\n const deleteStmt = this.db.prepare(\n \"UPDATE captures SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL\",\n );\n const tx = this.db.transaction(() => {\n for (const id of toDelete) {\n deleteStmt.run(now, id);\n // v12: Audit log\n this.recordAuditInternal(\"forget\", id, { reason: \"salience_below_threshold_or_ttl_expired\" });\n }\n });\n tx();\n }\n\n return { swept, remaining, checked: rows.length };\n }\n\n // ─── v10: Entity-assisted recall ──────────────────────────────\n\n /** Store extracted entities for a capture (replaces existing). */\n putEntities(captureId: string, entities: string[]): void {\n this.putEntitiesInternal(captureId, entities);\n }\n\n /** Internal entity storage — called from within putInternal. */\n private putEntitiesInternal(captureId: string, entities: string[]): void {\n if (entities.length === 0) return;\n const tx = this.db.transaction(() => {\n this.db.prepare(\"DELETE FROM entities WHERE capture_id = ?\").run(captureId);\n const stmt = this.db.prepare(\n \"INSERT INTO entities (capture_id, entity, created_at) VALUES (?, ?, ?)\",\n );\n const now = Date.now();\n for (const entity of entities.slice(0, 10)) {\n stmt.run(captureId, entity.toLowerCase(), now);\n }\n });\n tx();\n }\n\n /** Get entities for a capture. */\n getEntities(captureId: string): string[] {\n const rows = this.db\n .prepare(\"SELECT entity FROM entities WHERE capture_id = ? ORDER BY id\")\n .all(captureId) as { entity: string }[];\n return rows.map((r) => r.entity);\n }\n\n /**\n * Search captures by entity match (lexical).\n * Returns capture IDs ranked by number of matching entities (RRF-style).\n */\n searchByEntities(\n entities: string[],\n limit: number,\n sessionKey?: string,\n filters?: { teamId?: string; userId?: string; taskId?: string; type?: string },\n ): { id: string; score: number }[] {\n if (entities.length === 0) return [];\n const normalized = entities.map((e) => e.toLowerCase());\n const placeholders = normalized.map(() => \"?\").join(\",\");\n const params: unknown[] = [...normalized];\n const conditions: string[] = [\n \"c.deleted_at IS NULL\",\n \"c.trust_state != 'rejected'\",\n \"c.superseded_by IS NULL\",\n ];\n if (sessionKey) {\n conditions.push(\"c.session_key = ?\");\n params.push(sessionKey);\n }\n if (filters?.teamId) {\n conditions.push(\"c.team_id = ?\");\n params.push(filters.teamId);\n }\n if (filters?.userId) {\n conditions.push(\"c.user_id = ?\");\n params.push(filters.userId);\n }\n if (filters?.taskId) {\n conditions.push(\"c.task_id = ?\");\n params.push(filters.taskId);\n }\n if (filters?.type) {\n conditions.push(\"c.type = ?\");\n params.push(filters.type);\n }\n const rows = this.db\n .prepare(\n `SELECT e.capture_id as id, COUNT(*) as match_count\n FROM entities e\n JOIN captures c ON c.id = e.capture_id\n WHERE e.entity IN (${placeholders})\n AND ${conditions.join(\" AND \")}\n GROUP BY e.capture_id\n ORDER BY match_count DESC\n LIMIT ?`,\n )\n .all(...params, limit) as { id: string; match_count: number }[];\n\n // RRF-style scoring: rank by match count (more matches = higher score)\n return rows.map((r, i) => ({\n id: r.id,\n score: 1 / (40 + i + 1), // same k as RRF\n }));\n }\n\n // ─── v11: Memory-to-memory links ──────────────────────────────\n\n /** Link two captures. Auto-links use auto=1. */\n linkCaptures(fromId: string, toId: string, linkType: string, auto: boolean = false): void {\n if (fromId === toId) return; // no self-links\n try {\n this.db\n .prepare(\n `INSERT OR IGNORE INTO memory_links (from_id, to_id, link_type, auto, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(fromId, toId, linkType, auto ? 1 : 0, Date.now());\n // v12: Audit log\n this.recordAuditInternal(\"link\", fromId, { toId, linkType, auto });\n } catch {\n // non-fatal — link already exists or capture missing\n }\n }\n\n /** Get links from a capture (outbound). */\n getLinksFrom(captureId: string): { to_id: string; link_type: string; auto: number }[] {\n return this.db\n .prepare(\"SELECT to_id, link_type, auto FROM memory_links WHERE from_id = ?\")\n .all(captureId) as { to_id: string; link_type: string; auto: number }[];\n }\n\n /** Get links to a capture (inbound). */\n getLinksTo(captureId: string): { from_id: string; link_type: string; auto: number }[] {\n return this.db\n .prepare(\"SELECT from_id, link_type, auto FROM memory_links WHERE to_id = ?\")\n .all(captureId) as { from_id: string; link_type: string; auto: number }[];\n }\n\n /**\n * Expand capture IDs via link-neighbor traversal (1-hop).\n * Returns linked capture IDs with decayed score (0.5x of original).\n * Only includes non-deleted, non-rejected captures.\n */\n expandByLinks(ids: string[], limit: number): { id: string; score: number }[] {\n if (ids.length === 0) return [];\n const placeholders = ids.map(() => \"?\").join(\",\");\n // Get both outbound and inbound links, dedupe. v13: include weight.\n const rows = this.db\n .prepare(\n `SELECT linked_id as id, MAX(weight) as weight FROM (\n SELECT to_id as linked_id, weight FROM memory_links WHERE from_id IN (${placeholders})\n UNION\n SELECT from_id as linked_id, weight FROM memory_links WHERE to_id IN (${placeholders})\n )\n WHERE linked_id NOT IN (${placeholders})\n GROUP BY linked_id\n ORDER BY weight DESC\n LIMIT ?`,\n )\n .all(...ids, ...ids, ...ids, limit) as { id: string; weight: number }[];\n\n // Filter to non-deleted, non-rejected, non-superseded captures\n if (rows.length === 0) return [];\n const validPlaceholders = rows.map(() => \"?\").join(\",\");\n const validRows = this.db\n .prepare(\n `SELECT id FROM captures WHERE id IN (${validPlaceholders})\n AND deleted_at IS NULL AND trust_state != 'rejected' AND superseded_by IS NULL`,\n )\n .all(...rows.map((r) => r.id)) as { id: string }[];\n const validIds = new Set(validRows.map((r) => r.id));\n\n // Score: decayed RRF score scaled by link weight (v13: Hebbian weight)\n return rows\n .filter((r) => validIds.has(r.id))\n .map((r, i) => ({\n id: r.id,\n score: (0.5 * (r.weight ?? 1.0)) / (40 + i + 1), // decayed RRF × weight\n }));\n }\n\n /**\n * v13: Hebbian co-retrieval strengthening.\n * When captures co-occur in search results, strengthen links between them.\n * Creates links if they don't exist, increments weight if they do.\n */\n strengthenLinksOnCoRetrieval(ids: string[]): void {\n if (ids.length < 2) return;\n const now = Date.now();\n const linkType = \"co-retrieval\";\n // v13: Hebbian co-retrieval strengthening.\n // Use INSERT OR IGNORE + UPDATE to handle both new and existing links.\n const insertStmt = this.db.prepare(\n `INSERT OR IGNORE INTO memory_links (from_id, to_id, link_type, auto, weight, created_at)\n VALUES (?, ?, ?, 1, 1.0, ?)`,\n );\n const updateStmt = this.db.prepare(\n `UPDATE memory_links SET weight = weight + 0.1 WHERE from_id = ? AND to_id = ? AND link_type = ?`,\n );\n // Create/strengthen links for all pairs (limit to top 10 to avoid O(n²) blowup)\n const topIds = ids.slice(0, 10);\n for (let i = 0; i < topIds.length; i++) {\n for (let j = i + 1; j < topIds.length; j++) {\n // Always store with smaller id first to avoid duplicate pairs\n const [a, b] = topIds[i] < topIds[j] ? [topIds[i], topIds[j]] : [topIds[j], topIds[i]];\n try {\n insertStmt.run(a, b, linkType, now);\n updateStmt.run(a, b, linkType);\n } catch {\n // non-fatal\n }\n }\n }\n }\n\n /**\n * Auto-link a new capture to existing captures based on shared tags and entities.\n * Called after put() + entity extraction.\n */\n private autoLinkCapture(captureId: string, tags: string[], entities: string[], sessionKey: string): void {\n const now = Date.now();\n const linkStmt = this.db.prepare(\n `INSERT OR IGNORE INTO memory_links (from_id, to_id, link_type, auto, created_at)\n VALUES (?, ?, ?, 1, ?)`,\n );\n\n // 1. Link by shared tags (same session, non-deleted)\n if (tags.length > 0) {\n const tagPlaceholders = tags.map(() => \"?\").join(\",\");\n const tagRows = this.db\n .prepare(\n `SELECT DISTINCT c.id as other_id FROM captures c\n WHERE c.id != ? AND c.session_key = ? AND c.deleted_at IS NULL\n AND c.tags IS NOT NULL\n AND (c.tags LIKE ${tags.map(() => \"?\").join(\" OR c.tags LIKE \")})`,\n )\n .all(\n captureId,\n sessionKey,\n ...tags.map((t) => `%\"${t}\"%`),\n ) as { other_id: string }[];\n for (const row of tagRows.slice(0, 5)) {\n // Max 5 tag links\n linkStmt.run(captureId, row.other_id, \"shared-tag\", now);\n }\n }\n\n // 2. Link by shared entities (same session)\n if (entities.length > 0) {\n const entityPlaceholders = entities.map(() => \"?\").join(\",\");\n const entityRows = this.db\n .prepare(\n `SELECT DISTINCT e.capture_id as other_id FROM entities e\n JOIN captures c ON c.id = e.capture_id\n WHERE e.capture_id != ? AND c.session_key = ? AND c.deleted_at IS NULL\n AND e.entity IN (${entityPlaceholders})`,\n )\n .all(captureId, sessionKey, ...entities) as { other_id: string }[];\n for (const row of entityRows.slice(0, 5)) {\n // Max 5 entity links\n linkStmt.run(captureId, row.other_id, \"shared-entity\", now);\n }\n }\n\n // 3. Link by session proximity (captures within 5 minutes in same session)\n const proximityRows = this.db\n .prepare(\n `SELECT id as other_id FROM captures\n WHERE id != ? AND session_key = ? AND deleted_at IS NULL\n AND ABS(created_at - ?) < 300000\n ORDER BY ABS(created_at - ?) ASC LIMIT 3`,\n )\n .all(captureId, sessionKey, now, now) as { other_id: string }[];\n for (const row of proximityRows) {\n linkStmt.run(captureId, row.other_id, \"session-proximity\", now);\n }\n }\n\n // ─── v12: Feedback + audit + TTL ──────────────────────────────\n\n /**\n * Record feedback signal for a capture. Adjusts feedback_salience multiplier.\n * - helpful: +0.1 (max 2.0)\n * - not_helpful: -0.1 (min 0.1)\n * - stale: floor salience at 0.3\n * - wrong: floor salience at 0.1\n */\n recordFeedback(\n captureId: string,\n signal: \"helpful\" | \"not_helpful\" | \"stale\" | \"wrong\",\n reason?: string,\n agentId?: string,\n ): void {\n const now = Date.now();\n this.db.transaction(() => {\n // Insert feedback row\n this.db\n .prepare(\n \"INSERT INTO capture_feedback (capture_id, signal, reason, agent_id, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(captureId, signal, reason ?? null, agentId ?? null, now);\n\n // Adjust feedback_salience multiplier\n const row = this.db\n .prepare(\"SELECT feedback_salience FROM captures WHERE id = ?\")\n .get(captureId) as { feedback_salience: number } | undefined;\n if (!row) return;\n\n let newSalience = row.feedback_salience;\n if (signal === \"helpful\") {\n newSalience = Math.min(2.0, newSalience + 0.1);\n } else if (signal === \"not_helpful\") {\n newSalience = Math.max(0.1, newSalience - 0.1);\n } else if (signal === \"stale\") {\n newSalience = Math.min(newSalience, 0.3);\n } else if (signal === \"wrong\") {\n newSalience = Math.min(newSalience, 0.1);\n }\n\n this.db\n .prepare(\"UPDATE captures SET feedback_salience = ? WHERE id = ?\")\n .run(newSalience, captureId);\n\n // Audit log\n this.recordAuditInternal(\"feedback\", captureId, { signal, reason, newSalience }, agentId);\n })();\n }\n\n /** Get feedback signals for a capture. */\n getFeedback(captureId: string): { signal: string; reason: string | null; created_at: number }[] {\n return this.db\n .prepare(\n \"SELECT signal, reason, created_at FROM capture_feedback WHERE capture_id = ? ORDER BY id DESC\",\n )\n .all(captureId) as { signal: string; reason: string | null; created_at: number }[];\n }\n\n /** Record a mutation log entry. Public API. */\n recordAudit(action: string, captureId: string | null, details?: unknown, agentId?: string): void {\n this.recordAuditInternal(action, captureId, details, agentId);\n }\n\n /** Internal mutation log — called from within transactions. */\n private recordAuditInternal(\n action: string,\n captureId: string | null,\n details?: unknown,\n agentId?: string,\n ): void {\n try {\n this.db\n .prepare(\n \"INSERT INTO mutation_log (action, capture_id, details, agent_id, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(\n action,\n captureId,\n details ? JSON.stringify(details) : null,\n agentId ?? null,\n Date.now(),\n );\n } catch {\n // non-fatal — mutation log is supplementary\n }\n }\n\n /** Query mutation log. */\n queryAudit(opts: {\n action?: string;\n captureId?: string;\n since?: number;\n limit?: number;\n }): {\n id: number;\n action: string;\n capture_id: string | null;\n details: string | null;\n agent_id: string | null;\n created_at: number;\n }[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n if (opts.action) {\n conditions.push(\"action = ?\");\n params.push(opts.action);\n }\n if (opts.captureId) {\n conditions.push(\"capture_id = ?\");\n params.push(opts.captureId);\n }\n if (opts.since) {\n conditions.push(\"created_at >= ?\");\n params.push(opts.since);\n }\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const limit = opts.limit ?? 100;\n return this.db\n .prepare(`SELECT * FROM mutation_log ${where} ORDER BY created_at DESC LIMIT ?`)\n .all(...params, limit) as {\n id: number;\n action: string;\n capture_id: string | null;\n details: string | null;\n agent_id: string | null;\n created_at: number;\n }[];\n }\n}\n\n// ─── Database row types ────────────────────────────────────────\n\ninterface DbRow {\n id: string;\n session_key: string;\n agent_id: string;\n type: string;\n content: string;\n content_hash: string | null;\n tags: string | null;\n created_at: number;\n metadata: string | null;\n team_id: string | null;\n user_id: string | null;\n task_id: string | null;\n deleted_at: number | null;\n trust_state: string | null;\n rejection_reason: string | null;\n superseded_by: string | null;\n}\n\ninterface MessageDbRow {\n id: string;\n capture_id: string;\n role: string;\n content: string;\n seq: number;\n created_at: number;\n}\n\ninterface AtomDbRow {\n id: string;\n capture_id: string;\n fact: string;\n confidence: number;\n created_at: number;\n team_id: string | null;\n agent_id: string | null;\n user_id: string | null;\n}\n\ninterface ScenarioDbRow {\n id: string;\n atom_ids: string;\n summary: string;\n persona_tags: string | null;\n created_at: number;\n team_id: string | null;\n agent_id: string | null;\n user_id: string | null;\n}\n\ninterface PersonaDbRow {\n team_id: string;\n agent_id: string;\n user_id: string;\n content: string;\n updated_at: number;\n}\n\ninterface KnowledgeDbRow {\n id: string;\n team_id: string;\n name: string;\n type: string;\n summary: string | null;\n service_url: string | null;\n repo_url: string | null;\n branch: string | null;\n created_at: number;\n}\n\ninterface SkillDbRow {\n id: string;\n team_id: string;\n agent_id: string | null;\n name: string;\n description: string | null;\n content: string | null;\n version: number;\n created_at: number;\n updated_at: number;\n trigger_conditions: string | null;\n steps: string | null;\n validation_rules: string | null;\n source_capture_ids: string | null;\n archived: number | null;\n}\n\n// ─── Row → Entry converters ────────────────────────────────────\n\n/** Convert a database row to a CaptureEntry. */\nfunction rowToEntry(row: DbRow): CaptureEntry {\n return {\n id: row.id,\n sessionKey: row.session_key,\n agentId: row.agent_id,\n type: row.type as CaptureEntry[\"type\"],\n content: row.content,\n tags: row.tags ? JSON.parse(row.tags) : [],\n createdAt: row.created_at,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n teamId: row.team_id ?? undefined,\n userId: row.user_id ?? undefined,\n taskId: row.task_id ?? undefined,\n trustState: (row.trust_state as TrustState) ?? \"candidate\",\n rejectionReason: row.rejection_reason ?? undefined,\n supersededBy: row.superseded_by ?? undefined,\n };\n}\n\nfunction knowledgeRowToEntry(row: KnowledgeDbRow): KnowledgeEntry {\n return {\n id: row.id,\n teamId: row.team_id,\n name: row.name,\n type: row.type,\n summary: row.summary ?? undefined,\n serviceUrl: row.service_url ?? undefined,\n repoUrl: row.repo_url ?? undefined,\n branch: row.branch ?? undefined,\n createdAt: row.created_at,\n };\n}\n\nfunction skillRowToEntry(row: SkillDbRow): SkillEntry {\n return {\n id: row.id,\n teamId: row.team_id,\n agentId: row.agent_id ?? undefined,\n name: row.name,\n description: row.description ?? undefined,\n content: row.content ?? undefined,\n version: row.version,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n triggerConditions: row.trigger_conditions ? JSON.parse(row.trigger_conditions) : undefined,\n steps: row.steps ? JSON.parse(row.steps) : undefined,\n validationRules: row.validation_rules ? JSON.parse(row.validation_rules) : undefined,\n sourceCaptureIds: row.source_capture_ids ? JSON.parse(row.source_capture_ids) : undefined,\n archived: row.archived === 1,\n };\n}\n","/**\n * Reciprocal Rank Fusion (RRF) merge with weighted channels.\n * Fuses two ranked result lists into one.\n *\n * Vector search is weighted 1.5x over BM25 because vector similarity\n * is more semantically relevant for conceptual queries, while BM25\n * with OR semantics can introduce noise at scale (1K+ memories).\n *\n * Adapted from TencentDB Agent Memory search-utils pattern (MIT, Tencent 2026).\n * https://github.com/TencentCloud/TencentDB-Agent-Memory\n */\n\n// k=40 works better than the standard k=60 for smaller result sets (<500 items)\n// typical in memory servers. Lower k gives more weight to top-ranked items.\nconst RRF_K = 40;\n\n// Vector search weight: 3x over BM25. Vector similarity is more precise\n// for semantic matching, while BM25 OR semantics can be noisy — especially\n// when a proper noun in the query (e.g. \"Tin\") matches an unrelated capture\n// that happens to mention that name. Higher vector weight ensures semantic\n// relevance dominates over keyword coincidence.\nconst VEC_WEIGHT = 3.0;\n\nexport interface RankedResult {\n id: string;\n score: number;\n}\n\nexport interface FusedResult {\n id: string;\n score: number;\n}\n\n/**\n * Merge two ranked result lists with weighted RRF.\n * @param bm25Results Results from BM25 (FTS5). Lower score is better.\n * @param vecResults Results from vector search. Lower distance is better.\n * @param limit Maximum number of results to return.\n * @returns Fused results, sorted by RRF score (highest first).\n */\nexport function rrfMerge(\n bm25Results: RankedResult[],\n vecResults: RankedResult[],\n limit: number,\n): FusedResult[] {\n const scores = new Map<string, number>();\n\n // BM25 results: lower score is better, so rank 1 = lowest score\n const sortedBm25 = [...bm25Results].sort((a, b) => a.score - b.score);\n sortedBm25.forEach((r, i) => {\n const rank = i + 1;\n const rrfScore = 1 / (RRF_K + rank);\n scores.set(r.id, (scores.get(r.id) ?? 0) + rrfScore);\n });\n\n // Vector results: lower distance is better, so rank 1 = lowest distance\n // Weighted higher because vector similarity is more semantically precise.\n const sortedVec = [...vecResults].sort((a, b) => a.score - b.score);\n sortedVec.forEach((r, i) => {\n const rank = i + 1;\n const rrfScore = (1 / (RRF_K + rank)) * VEC_WEIGHT;\n scores.set(r.id, (scores.get(r.id) ?? 0) + rrfScore);\n });\n\n return [...scores.entries()]\n .map(([id, score]) => ({ id, score }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n}\n","/**\n * WriteQueue — async mutex for serializing all write operations.\n * Prevents \"database is locked\" errors when multiple hooks fire concurrently.\n *\n * All write operations (put, putVector, recordAccess, linkCaptures, etc.)\n * go through this queue. Reads are unaffected (WAL mode allows concurrent reads).\n *\n * Adapted from ai-memory's single-writer actor pattern.\n */\n\ntype WriteTask<T> = () => T | Promise<T>;\n\nexport class WriteQueue {\n private queue: Promise<unknown> = Promise.resolve();\n private pendingCount = 0;\n\n /**\n * Enqueue a write task. All tasks are serialized — the next task\n * only starts after the previous one completes (success or failure).\n */\n async enqueue<T>(task: WriteTask<T>): Promise<T> {\n this.pendingCount++;\n const result = this.queue.then(async () => {\n try {\n return await task();\n } finally {\n this.pendingCount--;\n }\n });\n // Update the queue chain, but don't let one failure block subsequent tasks\n this.queue = result.catch(() => {});\n return result as Promise<T>;\n }\n\n /** Number of tasks currently waiting/running. */\n get pending(): number {\n return this.pendingCount;\n }\n\n /** Wait for all pending tasks to complete. */\n async drain(): Promise<void> {\n while (this.pendingCount > 0) {\n await this.queue;\n }\n }\n}\n"],"mappings":"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","names":["__dirname","fused","paged"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
init_engine
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YU7WBJU5.js";
|
|
4
4
|
import {
|
|
5
5
|
LocalEmbedder,
|
|
6
6
|
SQLiteBackend,
|
|
7
7
|
init_local,
|
|
8
8
|
stripQueryProperNouns
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4WCDAEPP.js";
|
|
10
10
|
import {
|
|
11
11
|
generateId,
|
|
12
12
|
init_ulid
|
|
@@ -477,7 +477,8 @@ var Memory = class {
|
|
|
477
477
|
};
|
|
478
478
|
await this.storage.put(entry);
|
|
479
479
|
try {
|
|
480
|
-
const
|
|
480
|
+
const contextualContent = this.buildContextualPreamble(type, tags) + redactedContent;
|
|
481
|
+
const embedding = await this.embedder.embed(contextualContent);
|
|
481
482
|
await this.storage.putVector(id, embedding);
|
|
482
483
|
} catch {
|
|
483
484
|
}
|
|
@@ -530,6 +531,7 @@ var Memory = class {
|
|
|
530
531
|
const limit = Math.min(opts?.limit ?? 20, 100);
|
|
531
532
|
const mode = opts?.mode ?? "hybrid";
|
|
532
533
|
const sessionKey = opts?.sessionKey ?? this.sessionKey;
|
|
534
|
+
const explain = opts?.explain ?? false;
|
|
533
535
|
let queryEmbedding = null;
|
|
534
536
|
if (mode === "hybrid" || mode === "vector") {
|
|
535
537
|
const stripped = stripQueryProperNouns(query);
|
|
@@ -543,7 +545,8 @@ var Memory = class {
|
|
|
543
545
|
limit: projectLimit,
|
|
544
546
|
offset: 0,
|
|
545
547
|
mode,
|
|
546
|
-
filters: opts?.filters
|
|
548
|
+
filters: opts?.filters,
|
|
549
|
+
explain
|
|
547
550
|
});
|
|
548
551
|
let globalResults = [];
|
|
549
552
|
try {
|
|
@@ -552,7 +555,8 @@ var Memory = class {
|
|
|
552
555
|
limit: reservedGlobal,
|
|
553
556
|
offset: 0,
|
|
554
557
|
mode,
|
|
555
|
-
filters: opts?.filters
|
|
558
|
+
filters: opts?.filters,
|
|
559
|
+
explain
|
|
556
560
|
});
|
|
557
561
|
} catch {
|
|
558
562
|
}
|
|
@@ -567,17 +571,53 @@ var Memory = class {
|
|
|
567
571
|
limit,
|
|
568
572
|
offset: 0,
|
|
569
573
|
mode,
|
|
570
|
-
filters: opts?.filters
|
|
574
|
+
filters: opts?.filters,
|
|
575
|
+
explain
|
|
571
576
|
});
|
|
572
577
|
}
|
|
573
578
|
/** Compute the embedding vector for a piece of text using the configured embedder. */
|
|
574
579
|
async embed(text) {
|
|
575
580
|
return this.embedder.embed(text);
|
|
576
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* v13: Build a contextual preamble for embedding (Anthropic contextual retrieval technique).
|
|
584
|
+
* Prepends type + tags to give the vector model more context about what the capture is.
|
|
585
|
+
* The raw content stays pristine for FTS5 lexical search.
|
|
586
|
+
*/
|
|
587
|
+
buildContextualPreamble(type, tags) {
|
|
588
|
+
const parts = [];
|
|
589
|
+
if (type && type !== "conversation") {
|
|
590
|
+
parts.push(`[${type}]`);
|
|
591
|
+
}
|
|
592
|
+
if (tags && tags.length > 0) {
|
|
593
|
+
parts.push(`tags: ${tags.join(", ")}`);
|
|
594
|
+
}
|
|
595
|
+
return parts.length > 0 ? parts.join(" ") + ". " : "";
|
|
596
|
+
}
|
|
577
597
|
/** Delete a capture by ID. */
|
|
578
598
|
async forget(id) {
|
|
579
599
|
return this.storage.delete(id);
|
|
580
600
|
}
|
|
601
|
+
/** Get a capture by ID. Returns null if not found or deleted. */
|
|
602
|
+
async get(id) {
|
|
603
|
+
return this.storage.get(id);
|
|
604
|
+
}
|
|
605
|
+
/** Run forget sweep to evict low-salience and TTL-expired captures. */
|
|
606
|
+
async forgetSweep(opts) {
|
|
607
|
+
return this.storage.forgetSweep(opts ?? {});
|
|
608
|
+
}
|
|
609
|
+
/** Record feedback for a capture (v12). Adjusts salience multiplier. */
|
|
610
|
+
async feedback(captureId, signal, reason) {
|
|
611
|
+
this.storage.recordFeedback(captureId, signal, reason, "sdk");
|
|
612
|
+
}
|
|
613
|
+
/** Get feedback signals for a capture (v12). */
|
|
614
|
+
async getFeedback(captureId) {
|
|
615
|
+
return this.storage.getFeedback(captureId);
|
|
616
|
+
}
|
|
617
|
+
/** Query mutation log (v12). */
|
|
618
|
+
async queryAudit(opts) {
|
|
619
|
+
return this.storage.queryAudit(opts);
|
|
620
|
+
}
|
|
581
621
|
/** Update an existing capture's content, tags, type, or trust state. */
|
|
582
622
|
async update(id, opts) {
|
|
583
623
|
const existing = await this.storage.get(id);
|
|
@@ -816,4 +856,4 @@ export {
|
|
|
816
856
|
classifyGlobalContent,
|
|
817
857
|
Memory
|
|
818
858
|
};
|
|
819
|
-
//# sourceMappingURL=chunk-
|
|
859
|
+
//# sourceMappingURL=chunk-ITMR6THY.js.map
|