@melandlabs/memory-store 0.1.6 → 0.2.1

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.
Files changed (42) hide show
  1. package/dist/{config-D6MzksfT.d.ts → config-BYfTzG3h.d.ts} +17 -0
  2. package/dist/contracts.d.ts +7 -0
  3. package/dist/contracts.js +0 -2
  4. package/dist/http.d.ts +1 -1
  5. package/dist/http.js +355 -67
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +287 -52
  8. package/dist/mcp.d.ts +1 -1
  9. package/dist/mcp.js +313 -60
  10. package/dist/policies/memory-graph-correction-policy.js +0 -2
  11. package/dist/policies/memory-graph-write-policy.js +0 -2
  12. package/dist/search/unified-search.d.ts +39 -3
  13. package/dist/search/unified-search.js +311 -56
  14. package/dist/server/cli-http.js +360 -66
  15. package/dist/server/cli-mcp.js +318 -59
  16. package/dist/server/cli-shared.d.ts +1 -1
  17. package/dist/server/cli-shared.js +0 -2
  18. package/dist/storage/chroma-memory-index.js +0 -2
  19. package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
  20. package/dist/storage/postgres-raw-message-factory.js +0 -2
  21. package/dist/storage/raw-message-store.d.ts +9 -1
  22. package/dist/storage/raw-message-store.js +0 -2
  23. package/dist/storage/sqlite-raw-message-store.d.ts +15 -2
  24. package/dist/storage/sqlite-raw-message-store.js +8 -3
  25. package/dist/storage/sqlite-vector-index.d.ts +1 -1
  26. package/dist/storage/sqlite-vector-index.js +0 -2
  27. package/package.json +81 -93
  28. package/dist/contracts.js.map +0 -1
  29. package/dist/http.js.map +0 -1
  30. package/dist/index.js.map +0 -1
  31. package/dist/mcp.js.map +0 -1
  32. package/dist/policies/memory-graph-correction-policy.js.map +0 -1
  33. package/dist/policies/memory-graph-write-policy.js.map +0 -1
  34. package/dist/search/unified-search.js.map +0 -1
  35. package/dist/server/cli-http.js.map +0 -1
  36. package/dist/server/cli-mcp.js.map +0 -1
  37. package/dist/server/cli-shared.js.map +0 -1
  38. package/dist/storage/chroma-memory-index.js.map +0 -1
  39. package/dist/storage/postgres-raw-message-factory.js.map +0 -1
  40. package/dist/storage/raw-message-store.js.map +0 -1
  41. package/dist/storage/sqlite-raw-message-store.js.map +0 -1
  42. package/dist/storage/sqlite-vector-index.js.map +0 -1
package/dist/mcp.js CHANGED
@@ -1,14 +1,22 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { z } from 'zod';
4
1
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
5
2
  import { ChromaVectorStore } from '@melandlabs/rag';
6
3
  import { mkdirSync } from 'fs';
7
4
  import { homedir } from 'os';
8
- import { join, dirname } from 'path';
5
+ import { dirname, join } from 'path';
9
6
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { z } from 'zod';
10
10
 
11
- // src/mcp.ts
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
12
20
 
13
21
  // src/contracts.ts
14
22
  function normalizeTimestampToMs(value) {
@@ -32,14 +40,10 @@ function rawMessageToMemoryRecord(message) {
32
40
  embeddingContentHash: message.embeddingContentHash
33
41
  };
34
42
  }
35
-
36
- // src/storage/chroma-memory-index.ts
37
- var DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
38
- var RAW_VECTOR_BACKEND_ENV_KEYS = [
39
- "RAW_MESSAGE_VECTOR_STORE_BACKEND",
40
- "MEMORY_VECTOR_STORE_BACKEND",
41
- "VECTOR_STORE_BACKEND"
42
- ];
43
+ var init_contracts = __esm({
44
+ "src/contracts.ts"() {
45
+ }
46
+ });
43
47
  function getBackend(keys) {
44
48
  for (const key of keys) {
45
49
  const value = process.env[key]?.trim().toLowerCase();
@@ -142,21 +146,51 @@ async function searchRawMessagesWithChroma(input) {
142
146
  metadata: result.metadata ?? {}
143
147
  })).slice(0, input.limit);
144
148
  }
149
+ var DEFAULT_RAW_MESSAGES_COLLECTION, RAW_VECTOR_BACKEND_ENV_KEYS;
150
+ var init_chroma_memory_index = __esm({
151
+ "src/storage/chroma-memory-index.ts"() {
152
+ init_contracts();
153
+ DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
154
+ RAW_VECTOR_BACKEND_ENV_KEYS = [
155
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
156
+ "MEMORY_VECTOR_STORE_BACKEND",
157
+ "VECTOR_STORE_BACKEND"
158
+ ];
159
+ }
160
+ });
145
161
 
146
162
  // src/storage/postgres-raw-message-factory.ts
147
- var factory = null;
148
163
  async function resolvePostgresFactory(env) {
149
- return null;
164
+ if (!factory) return null;
165
+ return await factory(env);
150
166
  }
151
167
  function hasPostgresFactory() {
152
168
  return factory !== null;
153
169
  }
154
- var manager = null;
155
- var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
170
+ var factory;
171
+ var init_postgres_raw_message_factory = __esm({
172
+ "src/storage/postgres-raw-message-factory.ts"() {
173
+ factory = null;
174
+ }
175
+ });
176
+
177
+ // src/storage/sqlite-raw-message-store.ts
178
+ var sqlite_raw_message_store_exports = {};
179
+ __export(sqlite_raw_message_store_exports, {
180
+ __resetSQLiteRawMessageManagerForTests: () => __resetSQLiteRawMessageManagerForTests,
181
+ closeSQLiteRawMessageManager: () => closeSQLiteRawMessageManager,
182
+ getSQLiteRawMessageManager: () => getSQLiteRawMessageManager,
183
+ isSQLiteRawMessageStorageAvailable: () => isSQLiteRawMessageStorageAvailable,
184
+ lexicalSearchRawMessages: () => lexicalSearchRawMessages,
185
+ resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
186
+ });
156
187
  function resolveSQLiteRawMessageDbPath() {
157
188
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
158
189
  return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
159
190
  }
191
+ function isSQLiteRawMessageStorageAvailable(_env) {
192
+ return true;
193
+ }
160
194
  async function getSQLiteRawMessageManager(_env) {
161
195
  if (!manager) {
162
196
  const dbPath = resolveSQLiteRawMessageDbPath();
@@ -176,8 +210,35 @@ async function closeSQLiteRawMessageManager() {
176
210
  await manager.close();
177
211
  manager = null;
178
212
  }
213
+ function __resetSQLiteRawMessageManagerForTests() {
214
+ manager = null;
215
+ }
216
+ async function lexicalSearchRawMessages(input) {
217
+ const mgr = await getSQLiteRawMessageManager();
218
+ if (typeof mgr.lexicalSearchMessages !== "function") {
219
+ return [];
220
+ }
221
+ return mgr.lexicalSearchMessages(input);
222
+ }
223
+ var manager, DEFAULT_SQLITE_PATH;
224
+ var init_sqlite_raw_message_store = __esm({
225
+ "src/storage/sqlite-raw-message-store.ts"() {
226
+ init_chroma_memory_index();
227
+ manager = null;
228
+ DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
229
+ }
230
+ });
179
231
 
180
232
  // src/storage/raw-message-store.ts
233
+ var raw_message_store_exports = {};
234
+ __export(raw_message_store_exports, {
235
+ closeRawMessageStore: () => closeRawMessageStore,
236
+ configureRawMessageStore: () => configureRawMessageStore,
237
+ createRawMessageStore: () => createRawMessageStore,
238
+ getRawMessageManager: () => getRawMessageManager,
239
+ getRawMessageStorageBackend: () => getRawMessageStorageBackend,
240
+ isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
241
+ });
181
242
  function resolveBackend(env) {
182
243
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
183
244
  if (explicit === "postgres") return "postgres";
@@ -187,7 +248,7 @@ function resolveEnv(_env) {
187
248
  return {};
188
249
  }
189
250
  function createRawMessageStore(options = {}) {
190
- resolveEnv(options.env);
251
+ const env = resolveEnv(options.env);
191
252
  return {
192
253
  getBackend() {
193
254
  return resolveBackend();
@@ -197,7 +258,7 @@ function createRawMessageStore(options = {}) {
197
258
  },
198
259
  async getManager() {
199
260
  if (resolveBackend() === "postgres") {
200
- const pg = await resolvePostgresFactory();
261
+ const pg = await resolvePostgresFactory(env);
201
262
  if (!pg) {
202
263
  throw new Error(
203
264
  "OPENCONTEXT_MEMORY_STORE_BACKEND=postgres but no Postgres factory is registered. Call registerPostgresFactory() at startup, or unset the env var to use the default SQLite backend."
@@ -212,14 +273,47 @@ function createRawMessageStore(options = {}) {
212
273
  }
213
274
  };
214
275
  }
215
- var moduleStore = null;
216
- var moduleConfig = null;
276
+ function configureRawMessageStore(config) {
277
+ moduleConfig = config;
278
+ moduleStore = createRawMessageStore({ env: config.env });
279
+ return moduleStore;
280
+ }
281
+ function getRawMessageStorageBackend() {
282
+ if (!moduleStore) {
283
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
284
+ }
285
+ return moduleStore.getBackend();
286
+ }
217
287
  function isRawMessageStorageAvailable() {
218
288
  if (!moduleStore) {
219
289
  moduleStore = createRawMessageStore({ env: moduleConfig?.env });
220
290
  }
221
291
  return moduleStore.isAvailable();
222
292
  }
293
+ async function getRawMessageManager() {
294
+ if (!moduleStore) {
295
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
296
+ }
297
+ return moduleStore.getManager();
298
+ }
299
+ async function closeRawMessageStore() {
300
+ if (!moduleStore) return;
301
+ await moduleStore.close();
302
+ moduleStore = null;
303
+ }
304
+ var moduleStore, moduleConfig;
305
+ var init_raw_message_store = __esm({
306
+ "src/storage/raw-message-store.ts"() {
307
+ init_postgres_raw_message_factory();
308
+ init_sqlite_raw_message_store();
309
+ moduleStore = null;
310
+ moduleConfig = null;
311
+ }
312
+ });
313
+
314
+ // src/search/unified-search.ts
315
+ init_chroma_memory_index();
316
+ init_raw_message_store();
223
317
 
224
318
  // src/search/utilities.ts
225
319
  var DEFAULT_LIMIT = 10;
@@ -249,7 +343,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
249
343
  }
250
344
  return Math.min(1, Math.max(-1, parsed));
251
345
  }
252
- function mergeUnifiedMemorySearchResults(results, limit) {
346
+ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
347
+ const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
348
+ if (strategy === "rrf") {
349
+ const lists = options.rankedLists ?? [
350
+ { name: "memory-semantic", hits: results }
351
+ ];
352
+ return mergeUnifiedMemorySearchResultsRrf(lists, limit);
353
+ }
253
354
  return [...results].sort((a, b) => {
254
355
  const scoreDelta = b.similarity - a.similarity;
255
356
  if (scoreDelta !== 0) {
@@ -258,6 +359,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
258
359
  return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
259
360
  }).slice(0, limit);
260
361
  }
362
+ function normalizeUnifiedMemoryMergeStrategy(value) {
363
+ return value === "rrf" ? "rrf" : "similarity";
364
+ }
365
+ var DEFAULT_RRF_K = 60;
366
+ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
367
+ if (!Array.isArray(lists) || lists.length === 0) {
368
+ return [];
369
+ }
370
+ const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
371
+ const scores = /* @__PURE__ */ new Map();
372
+ const order = [];
373
+ for (const list of lists) {
374
+ for (let index = 0; index < list.hits.length; index += 1) {
375
+ const hit = list.hits[index];
376
+ const key = `${hit.type}::${hit.id}`;
377
+ const contribution = 1 / (safeK + index + 1);
378
+ const existing = scores.get(key);
379
+ if (existing) {
380
+ existing.rrf += contribution;
381
+ } else {
382
+ scores.set(key, { hit, rrf: contribution });
383
+ order.push(key);
384
+ }
385
+ }
386
+ }
387
+ const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
388
+ if (b.rrf !== a.rrf) {
389
+ return b.rrf - a.rrf;
390
+ }
391
+ return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
392
+ }).slice(0, limit);
393
+ return ranked.map(({ hit, rrf }) => ({
394
+ ...hit,
395
+ metadata: { ...hit.metadata, rrfScore: rrf }
396
+ }));
397
+ }
261
398
  function toKnowledgeResult(result) {
262
399
  return {
263
400
  type: "knowledge",
@@ -289,6 +426,9 @@ function isRawMemorySemanticResult(result) {
289
426
  }
290
427
 
291
428
  // src/search/unified-search.ts
429
+ function deriveLexicalKeywords(query) {
430
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
431
+ }
292
432
  function createUnifiedSearch(deps = {}) {
293
433
  const logger = deps.logger ?? console;
294
434
  async function searchUnifiedMemory(input) {
@@ -296,8 +436,8 @@ function createUnifiedSearch(deps = {}) {
296
436
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
297
437
  const limit = clampUnifiedMemorySearchLimit(input.limit);
298
438
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
439
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
299
440
  const warnings = [];
300
- const results = [];
301
441
  if (!query) {
302
442
  return {
303
443
  query,
@@ -307,17 +447,17 @@ function createUnifiedSearch(deps = {}) {
307
447
  warnings
308
448
  };
309
449
  }
450
+ let memorySubs;
310
451
  if (sources.includes("memory")) {
311
452
  if (isRawMessageStorageAvailable()) {
312
453
  try {
313
- const memoryHits = await runMemorySource(input, limit, threshold);
314
- results.push(...memoryHits);
454
+ memorySubs = await runMemorySource(input, limit, threshold, warnings);
315
455
  } catch (error) {
316
456
  logger.warn?.("[memory-store] memory source failed:", error);
317
457
  warnings.push({
318
458
  source: "memory",
319
459
  code: "memory_search_failed",
320
- message: error.message ?? "memory_search_failed"
460
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
321
461
  });
322
462
  }
323
463
  } else {
@@ -328,9 +468,10 @@ function createUnifiedSearch(deps = {}) {
328
468
  });
329
469
  }
330
470
  }
471
+ const insightHits = [];
331
472
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
332
473
  try {
333
- const insightHits = await deps.searchInsights({
474
+ const hits = await deps.searchInsights({
334
475
  userId: input.userId,
335
476
  query: input.query,
336
477
  limit,
@@ -339,8 +480,8 @@ function createUnifiedSearch(deps = {}) {
339
480
  includeArchived: input.includeArchivedInsights,
340
481
  authToken: input.authToken
341
482
  });
342
- for (const hit of insightHits) {
343
- results.push({
483
+ for (const hit of hits) {
484
+ insightHits.push({
344
485
  type: "insight",
345
486
  id: hit.id,
346
487
  content: hit.content,
@@ -356,16 +497,11 @@ function createUnifiedSearch(deps = {}) {
356
497
  message: error.message ?? "insights_search_failed"
357
498
  });
358
499
  }
359
- } else if (sources.includes("insights")) {
360
- warnings.push({
361
- source: "insights",
362
- code: "insights_search_not_configured",
363
- message: "Insights search is not configured for this host."
364
- });
365
- }
500
+ } else if (sources.includes("insights")) ;
501
+ const knowledgeHits = [];
366
502
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
367
503
  try {
368
- const knowledgeHits = await deps.searchKnowledge({
504
+ const hits = await deps.searchKnowledge({
369
505
  userId: input.userId,
370
506
  query: input.query,
371
507
  options: {
@@ -375,8 +511,8 @@ function createUnifiedSearch(deps = {}) {
375
511
  },
376
512
  authToken: input.authToken
377
513
  });
378
- for (const hit of knowledgeHits) {
379
- results.push(toKnowledgeResult(hit));
514
+ for (const hit of hits) {
515
+ knowledgeHits.push(toKnowledgeResult(hit));
380
516
  }
381
517
  } catch (error) {
382
518
  logger.warn?.("[memory-store] knowledge source failed:", error);
@@ -386,14 +522,14 @@ function createUnifiedSearch(deps = {}) {
386
522
  message: error.message ?? "knowledge_search_failed"
387
523
  });
388
524
  }
389
- } else if (sources.includes("knowledge")) {
390
- warnings.push({
391
- source: "knowledge",
392
- code: "knowledge_search_not_configured",
393
- message: "Knowledge search is not configured for this host."
394
- });
395
- }
396
- const merged = mergeUnifiedMemorySearchResults(results, limit);
525
+ } else if (sources.includes("knowledge")) ;
526
+ const merged = mergeAcrossSources({
527
+ memorySubs,
528
+ insightHits,
529
+ knowledgeHits,
530
+ limit,
531
+ strategy: mergeStrategy
532
+ });
397
533
  return {
398
534
  query: input.query,
399
535
  sources,
@@ -402,9 +538,35 @@ function createUnifiedSearch(deps = {}) {
402
538
  warnings
403
539
  };
404
540
  }
405
- async function runMemorySource(input, limit, threshold) {
541
+ async function runMemorySource(input, limit, threshold, warnings) {
406
542
  if (typeof deps.embedQuery !== "function") {
407
- throw new Error("embedQuery is not configured");
543
+ warnings.push({
544
+ source: "memory",
545
+ code: "memory_lexical_search_fallback",
546
+ message: "Semantic search not configured, using keyword search as fallback"
547
+ });
548
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
549
+ const keywords = deriveLexicalKeywords(input.query);
550
+ let lexical2 = [];
551
+ if (keywords.length > 0) {
552
+ try {
553
+ const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
554
+ const results = await Promise.all(
555
+ filters2.map(
556
+ (botId) => lexicalSearchRawMessages2({
557
+ userId: input.userId,
558
+ keywords,
559
+ limit: Math.ceil(limit / filters2.length),
560
+ botId
561
+ })
562
+ )
563
+ );
564
+ lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
565
+ } catch (error) {
566
+ logger.warn?.("[memory-store] Default lexical search failed:", error);
567
+ }
568
+ }
569
+ return { semantic: [], lexical: lexical2 };
408
570
  }
409
571
  const queryEmbedding = await deps.embedQuery({
410
572
  userId: input.userId,
@@ -412,9 +574,10 @@ function createUnifiedSearch(deps = {}) {
412
574
  authToken: input.authToken
413
575
  });
414
576
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
577
+ let semantic = [];
415
578
  if (isRawMessageChromaEnabled()) {
416
579
  try {
417
- const chromaHits = (await Promise.all(
580
+ semantic = (await Promise.all(
418
581
  filters.map((filter) => {
419
582
  const botId = "botId" in filter ? filter.botId : void 0;
420
583
  return searchRawMessagesWithChroma({
@@ -429,18 +592,16 @@ function createUnifiedSearch(deps = {}) {
429
592
  logger.log?.("[memory-store] Raw message semantic search completed", {
430
593
  backend: "chroma",
431
594
  dimensions: queryEmbedding.length,
432
- count: chromaHits.length
595
+ count: semantic.length
433
596
  });
434
- return chromaHits;
435
597
  } catch (error) {
436
598
  logger.warn?.(
437
599
  "[memory-store] Chroma raw message search failed; falling back to database search:",
438
600
  error
439
601
  );
440
602
  }
441
- }
442
- if (typeof deps.searchRawMessagesAnn === "function") {
443
- const rows = (await Promise.all(
603
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
604
+ semantic = (await Promise.all(
444
605
  filters.map(
445
606
  (filter) => deps.searchRawMessagesAnn({
446
607
  userId: input.userId,
@@ -451,19 +612,113 @@ function createUnifiedSearch(deps = {}) {
451
612
  })
452
613
  )
453
614
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
454
- return rows;
455
615
  }
456
- return [];
616
+ if (semantic.length === 0) {
617
+ try {
618
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
619
+ const manager2 = await getRawMessageManager2();
620
+ if (typeof manager2.searchMessagesSemantically === "function") {
621
+ const results = await manager2.searchMessagesSemantically({
622
+ userId: input.userId,
623
+ queryEmbedding,
624
+ limit
625
+ });
626
+ semantic = results.map((r) => ({
627
+ type: "memory",
628
+ id: r.id,
629
+ content: r.content,
630
+ similarity: r.similarity,
631
+ metadata: r.metadata ?? {}
632
+ }));
633
+ }
634
+ } catch (error) {
635
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
636
+ }
637
+ }
638
+ let lexical = [];
639
+ if (typeof deps.searchRawMessagesLexical === "function") {
640
+ const keywords = deriveLexicalKeywords(input.query);
641
+ if (keywords.length > 0) {
642
+ try {
643
+ const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
644
+ lexical = (await Promise.all(
645
+ lexFilters.map(
646
+ (botId) => deps.searchRawMessagesLexical({
647
+ userId: input.userId,
648
+ keywords,
649
+ limit,
650
+ botId
651
+ })
652
+ )
653
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
654
+ ...toMemoryResult(hit),
655
+ metadata: { ...hit.metadata, scoring: "bm25" }
656
+ }));
657
+ } catch (error) {
658
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
659
+ warnings.push({
660
+ source: "memory",
661
+ code: "memory_lexical_search_failed",
662
+ message: error.message ?? "memory_lexical_search_failed"
663
+ });
664
+ }
665
+ }
666
+ } else if (input.mergeStrategy === "rrf") {
667
+ warnings.push({
668
+ source: "memory",
669
+ code: "memory_lexical_search_not_configured",
670
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
671
+ });
672
+ }
673
+ return { semantic, lexical };
457
674
  }
458
675
  async function searchRawMemorySemantically(input) {
459
676
  const limit = clampUnifiedMemorySearchLimit(input.limit);
460
677
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
461
- return runMemorySource(input, limit, threshold);
678
+ const sub = await runMemorySource(input, limit, threshold, []);
679
+ return mergeAcrossSources({
680
+ memorySubs: sub,
681
+ insightHits: [],
682
+ knowledgeHits: [],
683
+ limit,
684
+ strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
685
+ });
462
686
  }
463
687
  return { searchUnifiedMemory, searchRawMemorySemantically };
464
688
  }
689
+ function mergeAcrossSources(input) {
690
+ const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
691
+ const all = [
692
+ ...memorySubs.semantic,
693
+ ...memorySubs.lexical,
694
+ ...input.insightHits,
695
+ ...input.knowledgeHits
696
+ ];
697
+ if (input.strategy !== "rrf") {
698
+ return mergeUnifiedMemorySearchResults(all, input.limit);
699
+ }
700
+ const lists = [];
701
+ if (memorySubs.semantic.length > 0) {
702
+ lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
703
+ }
704
+ if (memorySubs.lexical.length > 0) {
705
+ lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
706
+ }
707
+ if (input.insightHits.length > 0) {
708
+ lists.push({ name: "insights", hits: input.insightHits });
709
+ }
710
+ if (input.knowledgeHits.length > 0) {
711
+ lists.push({ name: "knowledge", hits: input.knowledgeHits });
712
+ }
713
+ if (lists.length <= 1) {
714
+ return mergeUnifiedMemorySearchResults(all, input.limit);
715
+ }
716
+ return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
717
+ }
465
718
 
466
719
  // src/mcp.ts
720
+ init_chroma_memory_index();
721
+ init_raw_message_store();
467
722
  var DEFAULT_NAME = "@melandlabs/memory-store";
468
723
  var DEFAULT_VERSION = "0.1.0";
469
724
  async function embedMissingMessages(messages, deps) {
@@ -623,5 +878,3 @@ async function startMcpServer(options = {}) {
623
878
  }
624
879
 
625
880
  export { startMcpServer };
626
- //# sourceMappingURL=mcp.js.map
627
- //# sourceMappingURL=mcp.js.map
@@ -37,5 +37,3 @@ function resolveMemoryGraphCorrectionPolicy(userId, environment = process.env) {
37
37
  }
38
38
 
39
39
  export { resolveMemoryGraphCorrectionPolicy };
40
- //# sourceMappingURL=memory-graph-correction-policy.js.map
41
- //# sourceMappingURL=memory-graph-correction-policy.js.map
@@ -74,5 +74,3 @@ function resolveUntrustedRawMemoryGraphWritePolicy() {
74
74
  }
75
75
 
76
76
  export { CHAT_MEMORY_EVIDENCE_ID_PREFIX, MEMORY_GRAPH_SUMMARY_ID_PREFIXES, isReservedChatMemoryEvidenceId, isReservedMemoryGraphSummaryId, resolveAllowlistedMemoryGraphPolicy, resolveMemoryGraphWritePolicy, resolveUntrustedRawMemoryGraphWritePolicy, sanitizeUntrustedMemoryMetadata };
77
- //# sourceMappingURL=memory-graph-write-policy.js.map
78
- //# sourceMappingURL=memory-graph-write-policy.js.map
@@ -1,4 +1,4 @@
1
- import { d as UnifiedSearchDeps } from '../config-D6MzksfT.js';
1
+ import { d as UnifiedSearchDeps } from '../config-BYfTzG3h.js';
2
2
 
3
3
  /**
4
4
  * Pure helpers used by the unified memory search pipeline.
@@ -23,6 +23,30 @@ interface UnifiedMemorySearchInput {
23
23
  includeArchivedInsights?: boolean;
24
24
  botIds?: string[];
25
25
  documentIds?: string[];
26
+ /**
27
+ * Optional ISO-8601 timestamp. When set, graph-aware retrieval and other
28
+ * snapshot consumers drop nodes / edges whose `applicability.validFrom` /
29
+ * `validUntil` window is closed at that instant. Backward-compatible:
30
+ * omitted `asOf` keeps the legacy behaviour exactly.
31
+ */
32
+ asOf?: string;
33
+ /**
34
+ * How to merge per-source result lists. Defaults to `"similarity"` —
35
+ * the legacy global sort by similarity, then type/id tie-break. The
36
+ * alternate value `"rrf"` invokes reciprocal-rank fusion across the
37
+ * lists fed via the (internal) `rankedLists` option.
38
+ */
39
+ mergeStrategy?: UnifiedMemoryMergeStrategy;
40
+ }
41
+ type UnifiedMemoryMergeStrategy = "similarity" | "rrf";
42
+ /**
43
+ * A single ranked list from one retrieval channel. Used by the RRF merge to
44
+ * combine multiple signals (semantic + lexical + insights + knowledge) without
45
+ * flattening them into a single similarity score first.
46
+ */
47
+ interface UnifiedMemoryRankedList {
48
+ name: "memory-bm25" | "memory-semantic" | "insights" | "knowledge";
49
+ hits: UnifiedMemorySearchResult[];
26
50
  }
27
51
  interface UnifiedMemorySearchResult {
28
52
  type: "memory" | "insight" | "knowledge";
@@ -41,7 +65,19 @@ interface UnifiedMemorySearchOutput {
41
65
  declare function normalizeUnifiedMemorySearchSources(sources: unknown): UnifiedMemorySearchSource[];
42
66
  declare function clampUnifiedMemorySearchLimit(limit: unknown): number;
43
67
  declare function clampUnifiedMemorySearchThreshold(threshold: unknown): number;
44
- declare function mergeUnifiedMemorySearchResults(results: UnifiedMemorySearchResult[], limit: number): UnifiedMemorySearchResult[];
68
+ declare function mergeUnifiedMemorySearchResults(results: UnifiedMemorySearchResult[], limit: number, options?: {
69
+ strategy?: UnifiedMemoryMergeStrategy;
70
+ rankedLists?: UnifiedMemoryRankedList[];
71
+ }): UnifiedMemorySearchResult[];
72
+ declare function normalizeUnifiedMemoryMergeStrategy(value: unknown): UnifiedMemoryMergeStrategy;
73
+ /**
74
+ * Reciprocal Rank Fusion across multiple ranked lists. Each list contributes
75
+ * `1 / (k + rank + 1)` to a hit's accumulated score. Hits are de-duplicated by
76
+ * `(type, id)` so the same message surfaced by both lexical and semantic
77
+ * channels naturally outranks single-channel matches. Tie-break is
78
+ * deterministic: descending RRF score, then `(type, id)` lexical.
79
+ */
80
+ declare function mergeUnifiedMemorySearchResultsRrf(lists: UnifiedMemoryRankedList[], limit: number, k?: number): UnifiedMemorySearchResult[];
45
81
  declare function toKnowledgeResult(result: {
46
82
  chunkId: string;
47
83
  documentId: string;
@@ -78,4 +114,4 @@ interface UnifiedSearch {
78
114
  }
79
115
  declare function createUnifiedSearch(deps?: UnifiedSearchDeps): UnifiedSearch;
80
116
 
81
- export { type UnifiedMemorySearchInput, type UnifiedMemorySearchOutput, type UnifiedMemorySearchResult, type UnifiedMemorySearchSource, type UnifiedMemorySearchWarning, type UnifiedSearch, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
117
+ export { type UnifiedMemoryMergeStrategy, type UnifiedMemoryRankedList, type UnifiedMemorySearchInput, type UnifiedMemorySearchOutput, type UnifiedMemorySearchResult, type UnifiedMemorySearchSource, type UnifiedMemorySearchWarning, type UnifiedSearch, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };