@melandlabs/memory-store 0.2.6 → 0.3.0

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.
@@ -108,16 +108,17 @@ __export(sqlite_raw_message_store_exports, {
108
108
  lexicalSearchRawMessages: () => lexicalSearchRawMessages,
109
109
  resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
110
110
  });
111
- function resolveSQLiteRawMessageDbPath() {
111
+ function resolveSQLiteRawMessageDbPath(dbPath) {
112
+ if (dbPath && dbPath.length > 0) return dbPath;
112
113
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
113
114
  return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
114
115
  }
115
116
  function isSQLiteRawMessageStorageAvailable(_env) {
116
117
  return true;
117
118
  }
118
- async function getSQLiteRawMessageManager(_env) {
119
+ async function getSQLiteRawMessageManager(options = {}) {
119
120
  if (!manager) {
120
- const dbPath = resolveSQLiteRawMessageDbPath();
121
+ const dbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
121
122
  mkdirSync(dirname(dbPath), { recursive: true });
122
123
  manager = new SQLiteRawMessageManager({
123
124
  dbPath,
@@ -163,7 +164,7 @@ __export(raw_message_store_exports, {
163
164
  getRawMessageStorageBackend: () => getRawMessageStorageBackend,
164
165
  isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
165
166
  });
166
- function resolveBackend(env) {
167
+ function resolveBackend(_env) {
167
168
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
168
169
  if (explicit === "postgres") return "postgres";
169
170
  return "sqlite";
@@ -190,33 +191,43 @@ function createRawMessageStore(options = {}) {
190
191
  }
191
192
  return pg;
192
193
  }
193
- return await getSQLiteRawMessageManager();
194
+ return await getSQLiteRawMessageManager({
195
+ dbPath: options.dbPath
196
+ });
194
197
  },
195
198
  async close() {
196
199
  await closeSQLiteRawMessageManager();
197
200
  }
198
201
  };
199
202
  }
203
+ function resolveDbPath(config) {
204
+ if (!config) return void 0;
205
+ if (config.dbPath) return config.dbPath;
206
+ if (config.db && typeof config.db === "object" && "path" in config.db) {
207
+ return config.db.path;
208
+ }
209
+ return void 0;
210
+ }
200
211
  function configureRawMessageStore(config) {
201
212
  moduleConfig = config;
202
- moduleStore = createRawMessageStore({ env: config.env });
213
+ moduleStore = createRawMessageStore({ env: config.env, dbPath: resolveDbPath(config) });
203
214
  return moduleStore;
204
215
  }
205
216
  function getRawMessageStorageBackend() {
206
217
  if (!moduleStore) {
207
- moduleStore = createRawMessageStore({ env: moduleConfig?.env });
218
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
208
219
  }
209
220
  return moduleStore.getBackend();
210
221
  }
211
222
  function isRawMessageStorageAvailable() {
212
223
  if (!moduleStore) {
213
- moduleStore = createRawMessageStore({ env: moduleConfig?.env });
224
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
214
225
  }
215
226
  return moduleStore.isAvailable();
216
227
  }
217
228
  async function getRawMessageManager() {
218
229
  if (!moduleStore) {
219
- moduleStore = createRawMessageStore({ env: moduleConfig?.env });
230
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
220
231
  }
221
232
  return moduleStore.getManager();
222
233
  }
@@ -286,6 +297,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
286
297
  function normalizeUnifiedMemoryMergeStrategy(value) {
287
298
  return value === "rrf" ? "rrf" : "similarity";
288
299
  }
300
+ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
301
+ if (value === "rewrite" || value === "iterative") {
302
+ return value;
303
+ }
304
+ return defaultStrategy;
305
+ }
289
306
  var DEFAULT_RRF_K = 60;
290
307
  function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
291
308
  if (!Array.isArray(lists) || lists.length === 0) {
@@ -308,7 +325,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
308
325
  }
309
326
  }
310
327
  }
311
- const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
328
+ const ranked = order.map((key) => {
329
+ const entry = scores.get(key);
330
+ if (!entry) {
331
+ throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
332
+ }
333
+ return entry;
334
+ }).sort((a, b) => {
312
335
  if (b.rrf !== a.rrf) {
313
336
  return b.rrf - a.rrf;
314
337
  }
@@ -353,14 +376,222 @@ function isRawMemorySemanticResult(result) {
353
376
  function deriveLexicalKeywords(query) {
354
377
  return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
355
378
  }
379
+ function parseDateBoundary(value, role) {
380
+ const trimmed = value.trim();
381
+ if (!trimmed) {
382
+ return void 0;
383
+ }
384
+ const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
385
+ if (dateOnlyMatch) {
386
+ const year = Number.parseInt(dateOnlyMatch[1], 10);
387
+ const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
388
+ const day = Number.parseInt(dateOnlyMatch[3], 10);
389
+ if (role === "from") {
390
+ return Date.UTC(year, month, day);
391
+ }
392
+ return Date.UTC(year, month, day + 1) - 1;
393
+ }
394
+ const parsed = Date.parse(trimmed);
395
+ if (Number.isNaN(parsed)) {
396
+ return void 0;
397
+ }
398
+ return parsed;
399
+ }
400
+ function getCandidateTimestamp(metadata) {
401
+ for (const key of ["timestamp", "createdAt", "time"]) {
402
+ const value = metadata[key];
403
+ if (typeof value === "number") {
404
+ return value;
405
+ }
406
+ if (typeof value === "string") {
407
+ const parsed = Date.parse(value);
408
+ if (!Number.isNaN(parsed)) {
409
+ return parsed;
410
+ }
411
+ }
412
+ }
413
+ return void 0;
414
+ }
415
+ function filterByDateRange(items, dateFrom, dateTo) {
416
+ if (!dateFrom && !dateTo) {
417
+ return items;
418
+ }
419
+ const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
420
+ const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
421
+ if (fromMs === void 0 && toMs === void 0) {
422
+ return items;
423
+ }
424
+ return items.filter((item) => {
425
+ const ts = getCandidateTimestamp(item.metadata);
426
+ if (ts === void 0) {
427
+ return true;
428
+ }
429
+ if (fromMs !== void 0 && ts < fromMs) {
430
+ return false;
431
+ }
432
+ if (toMs !== void 0 && ts > toMs) {
433
+ return false;
434
+ }
435
+ return true;
436
+ });
437
+ }
438
+ async function embedQueryVariant(embedQuery, input, query) {
439
+ return embedQuery({
440
+ userId: input.userId,
441
+ query,
442
+ authToken: input.authToken
443
+ });
444
+ }
445
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
446
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
447
+ let semantic = [];
448
+ if (isRawMessageChromaEnabled()) {
449
+ try {
450
+ semantic = (await Promise.all(
451
+ filters.map((filter) => {
452
+ const botId = "botId" in filter ? filter.botId : void 0;
453
+ return searchRawMessagesWithChroma({
454
+ userId: input.userId,
455
+ queryEmbedding,
456
+ limit,
457
+ threshold,
458
+ botId
459
+ });
460
+ })
461
+ )).flat().map(toMemoryResult);
462
+ logger.log?.("[memory-store] Raw message semantic search completed", {
463
+ backend: "chroma",
464
+ dimensions: queryEmbedding.length,
465
+ count: semantic.length
466
+ });
467
+ } catch (error) {
468
+ logger.warn?.(
469
+ "[memory-store] Chroma raw message search failed; falling back to database search:",
470
+ error
471
+ );
472
+ }
473
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
474
+ const searchRawMessagesAnn = deps.searchRawMessagesAnn;
475
+ semantic = (await Promise.all(
476
+ filters.map(
477
+ (filter) => searchRawMessagesAnn({
478
+ userId: input.userId,
479
+ queryEmbedding,
480
+ limit,
481
+ threshold,
482
+ botId: "botId" in filter ? filter.botId : void 0
483
+ })
484
+ )
485
+ )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
486
+ }
487
+ if (semantic.length === 0) {
488
+ try {
489
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
490
+ const manager2 = await getRawMessageManager2();
491
+ if (typeof manager2.searchMessagesSemantically === "function") {
492
+ const results = await manager2.searchMessagesSemantically({
493
+ userId: input.userId,
494
+ queryEmbedding,
495
+ limit,
496
+ threshold
497
+ });
498
+ semantic = results.map((r) => ({
499
+ type: "memory",
500
+ id: r.id,
501
+ content: r.content,
502
+ similarity: r.similarity,
503
+ metadata: r.metadata ?? {}
504
+ }));
505
+ }
506
+ } catch (error) {
507
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
508
+ }
509
+ }
510
+ return semantic;
511
+ }
512
+ function mergeByMaxScore(lists) {
513
+ const best = /* @__PURE__ */ new Map();
514
+ for (const list of lists) {
515
+ for (const hit of list) {
516
+ const existing = best.get(hit.id);
517
+ if (!existing || hit.similarity > existing.similarity) {
518
+ best.set(hit.id, hit);
519
+ }
520
+ }
521
+ }
522
+ return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
523
+ }
524
+ function toIterativeRecallCandidate(hit) {
525
+ return {
526
+ id: hit.id,
527
+ content: hit.content,
528
+ similarity: hit.similarity,
529
+ metadata: hit.metadata
530
+ };
531
+ }
532
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
533
+ if (keywords.length === 0) {
534
+ return [];
535
+ }
536
+ if (typeof deps.searchRawMessagesLexical === "function") {
537
+ try {
538
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
539
+ const searchRawMessagesLexical = deps.searchRawMessagesLexical;
540
+ return (await Promise.all(
541
+ filters.map(
542
+ (botId) => searchRawMessagesLexical({
543
+ userId: input.userId,
544
+ keywords,
545
+ limit: Math.ceil(limit / filters.length),
546
+ botId
547
+ })
548
+ )
549
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
550
+ ...toMemoryResult(hit),
551
+ metadata: { ...hit.metadata, scoring: "bm25" }
552
+ }));
553
+ } catch (error) {
554
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
555
+ }
556
+ }
557
+ try {
558
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
559
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
560
+ return (await Promise.all(
561
+ filters.map(
562
+ (botId) => lexicalSearchRawMessages2({
563
+ userId: input.userId,
564
+ keywords,
565
+ limit: Math.ceil(limit / filters.length),
566
+ botId
567
+ })
568
+ )
569
+ )).flat().filter(Boolean).map(
570
+ (r) => toMemoryResult(
571
+ r
572
+ )
573
+ );
574
+ } catch (error) {
575
+ logger.warn?.("[memory-store] SQLite lexical search failed:", error);
576
+ }
577
+ return [];
578
+ }
356
579
  function createUnifiedSearch(deps = {}) {
357
580
  const logger = deps.logger ?? console;
581
+ function resolveReasoningStrategy(input) {
582
+ const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
583
+ if (requested !== "none") {
584
+ return requested;
585
+ }
586
+ return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
587
+ }
358
588
  async function searchUnifiedMemory(input) {
359
589
  const query = input.query.trim();
360
590
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
361
591
  const limit = clampUnifiedMemorySearchLimit(input.limit);
362
592
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
363
593
  const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
594
+ const reasoningStrategy = resolveReasoningStrategy(input);
364
595
  const warnings = [];
365
596
  if (!query) {
366
597
  return {
@@ -372,10 +603,21 @@ function createUnifiedSearch(deps = {}) {
372
603
  };
373
604
  }
374
605
  let memorySubs;
606
+ const reasoningInfo = { strategy: reasoningStrategy };
607
+ if (input.dateFrom || input.dateTo) {
608
+ reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
609
+ }
375
610
  if (sources.includes("memory")) {
376
611
  if (isRawMessageStorageAvailable()) {
377
612
  try {
378
- memorySubs = await runMemorySource(input, limit, threshold, warnings);
613
+ memorySubs = await runMemorySource(
614
+ input,
615
+ limit,
616
+ threshold,
617
+ warnings,
618
+ reasoningStrategy,
619
+ reasoningInfo
620
+ );
379
621
  } catch (error) {
380
622
  logger.warn?.("[memory-store] memory source failed:", error);
381
623
  warnings.push({
@@ -454,120 +696,155 @@ function createUnifiedSearch(deps = {}) {
454
696
  limit,
455
697
  strategy: mergeStrategy
456
698
  });
457
- return {
699
+ const output = {
458
700
  query: input.query,
459
701
  sources,
460
702
  results: merged,
461
703
  count: merged.length,
462
704
  warnings
463
705
  };
706
+ if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
707
+ output.reasoning = reasoningInfo;
708
+ }
709
+ return output;
464
710
  }
465
- async function runMemorySource(input, limit, threshold, warnings) {
711
+ async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
712
+ if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
713
+ warnings.push({
714
+ source: "memory",
715
+ code: "memory_iterative_planner_not_configured",
716
+ message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
717
+ });
718
+ if (reasoningInfo) reasoningInfo.degraded = true;
719
+ }
720
+ if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
721
+ warnings.push({
722
+ source: "memory",
723
+ code: "memory_query_rewrite_not_configured",
724
+ message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
725
+ });
726
+ if (reasoningInfo) reasoningInfo.degraded = true;
727
+ }
728
+ if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
729
+ const planner = deps.reasoning.iterativePlanner;
730
+ const result = await planner.plan({
731
+ query: input.query,
732
+ dateFrom: input.dateFrom,
733
+ dateTo: input.dateTo,
734
+ executor: {
735
+ search: async (request) => {
736
+ const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
737
+ const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
738
+ let semanticHits = [];
739
+ if (typeof deps.embedQuery === "function") {
740
+ try {
741
+ const semanticQuery = keywords2.join(" ");
742
+ const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
743
+ semanticHits = await runSemanticSearchForEmbedding(
744
+ deps,
745
+ input,
746
+ queryEmbedding,
747
+ limit,
748
+ threshold,
749
+ logger
750
+ );
751
+ } catch (error) {
752
+ logger.warn?.(
753
+ "[memory-store] Iterative semantic search failed; using lexical results only:",
754
+ error
755
+ );
756
+ }
757
+ }
758
+ const candidates = filterByDateRange(
759
+ mergeByMaxScore([
760
+ lexicalHits.map(toIterativeRecallCandidate),
761
+ semanticHits.map(toIterativeRecallCandidate)
762
+ ]),
763
+ request.dateFrom,
764
+ request.dateTo
765
+ );
766
+ return { candidates };
767
+ }
768
+ }
769
+ });
770
+ if (reasoningInfo) {
771
+ reasoningInfo.iterations = result.stats.iterations;
772
+ reasoningInfo.evidenceCount = result.evidence.length;
773
+ if (planner.lastDegraded?.()) {
774
+ reasoningInfo.degraded = true;
775
+ }
776
+ }
777
+ const semantic2 = result.evidence.map((candidate) => ({
778
+ type: "memory",
779
+ id: candidate.id,
780
+ content: candidate.content,
781
+ similarity: candidate.similarity,
782
+ metadata: { ...candidate.metadata, reasoning: "iterative" }
783
+ }));
784
+ return { semantic: semantic2, lexical: [] };
785
+ }
466
786
  if (typeof deps.embedQuery !== "function") {
467
787
  warnings.push({
468
788
  source: "memory",
469
789
  code: "memory_lexical_search_fallback",
470
790
  message: "Semantic search not configured, using keyword search as fallback"
471
791
  });
472
- const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
473
- const keywords = deriveLexicalKeywords(input.query);
474
- let lexical2 = [];
475
- if (keywords.length > 0) {
476
- try {
477
- const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
478
- const results = await Promise.all(
479
- filters2.map(
480
- (botId) => lexicalSearchRawMessages2({
481
- userId: input.userId,
482
- keywords,
483
- limit: Math.ceil(limit / filters2.length),
484
- botId
485
- })
486
- )
487
- );
488
- lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
489
- } catch (error) {
490
- logger.warn?.("[memory-store] Default lexical search failed:", error);
491
- }
492
- }
792
+ const keywords2 = deriveLexicalKeywords(input.query);
793
+ const lexical2 = filterByDateRange(
794
+ await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
795
+ input.dateFrom,
796
+ input.dateTo
797
+ );
493
798
  return { semantic: [], lexical: lexical2 };
494
799
  }
495
- const queryEmbedding = await deps.embedQuery({
496
- userId: input.userId,
497
- query: input.query,
498
- authToken: input.authToken
499
- });
500
- const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
501
800
  let semantic = [];
502
- if (isRawMessageChromaEnabled()) {
801
+ const embedQuery = deps.embedQuery;
802
+ if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
503
803
  try {
504
- semantic = (await Promise.all(
505
- filters.map((filter) => {
506
- const botId = "botId" in filter ? filter.botId : void 0;
507
- return searchRawMessagesWithChroma({
508
- userId: input.userId,
509
- queryEmbedding,
510
- limit,
511
- threshold,
512
- botId
513
- });
514
- })
515
- )).flat().map(toMemoryResult);
516
- logger.log?.("[memory-store] Raw message semantic search completed", {
517
- backend: "chroma",
518
- dimensions: queryEmbedding.length,
519
- count: semantic.length
804
+ const variants = await deps.reasoning.queryRewriter.rewrite({
805
+ query: input.query,
806
+ userId: input.userId,
807
+ authToken: input.authToken
520
808
  });
521
- } catch (error) {
522
- logger.warn?.(
523
- "[memory-store] Chroma raw message search failed; falling back to database search:",
524
- error
809
+ if (reasoningInfo) {
810
+ reasoningInfo.rewrittenQueries = variants;
811
+ if (deps.reasoning.queryRewriter.lastDegraded?.()) {
812
+ reasoningInfo.degraded = true;
813
+ }
814
+ }
815
+ const embeddings = await Promise.all(
816
+ variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
817
+ );
818
+ const lists = await Promise.all(
819
+ embeddings.map(
820
+ (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
821
+ )
525
822
  );
823
+ semantic = mergeByMaxScore(lists);
824
+ } catch (error) {
825
+ logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
826
+ warnings.push({
827
+ source: "memory",
828
+ code: "memory_query_rewrite_failed",
829
+ message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
830
+ });
831
+ if (reasoningInfo) reasoningInfo.degraded = true;
526
832
  }
527
- } else if (typeof deps.searchRawMessagesAnn === "function") {
528
- semantic = (await Promise.all(
529
- filters.map(
530
- (filter) => deps.searchRawMessagesAnn({
531
- userId: input.userId,
532
- queryEmbedding,
533
- limit,
534
- threshold,
535
- botId: "botId" in filter ? filter.botId : void 0
536
- })
537
- )
538
- )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
539
833
  }
540
834
  if (semantic.length === 0) {
541
- try {
542
- const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
543
- const manager2 = await getRawMessageManager2();
544
- if (typeof manager2.searchMessagesSemantically === "function") {
545
- const results = await manager2.searchMessagesSemantically({
546
- userId: input.userId,
547
- queryEmbedding,
548
- limit
549
- });
550
- semantic = results.map((r) => ({
551
- type: "memory",
552
- id: r.id,
553
- content: r.content,
554
- similarity: r.similarity,
555
- metadata: r.metadata ?? {}
556
- }));
557
- }
558
- } catch (error) {
559
- logger.warn?.("[memory-store] SQLite semantic search failed:", error);
560
- }
835
+ const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
836
+ semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
561
837
  }
562
838
  let lexical = [];
563
- if (typeof deps.searchRawMessagesLexical === "function") {
564
- const keywords = deriveLexicalKeywords(input.query);
565
- if (keywords.length > 0) {
839
+ const keywords = deriveLexicalKeywords(input.query);
840
+ if (keywords.length > 0) {
841
+ if (typeof deps.searchRawMessagesLexical === "function") {
566
842
  try {
567
843
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
844
+ const searchRawMessagesLexical = deps.searchRawMessagesLexical;
568
845
  lexical = (await Promise.all(
569
846
  lexFilters.map(
570
- (botId) => deps.searchRawMessagesLexical({
847
+ (botId) => searchRawMessagesLexical({
571
848
  userId: input.userId,
572
849
  keywords,
573
850
  limit,
@@ -586,20 +863,24 @@ function createUnifiedSearch(deps = {}) {
586
863
  message: error.message ?? "memory_lexical_search_failed"
587
864
  });
588
865
  }
866
+ } else if (input.mergeStrategy === "rrf") {
867
+ warnings.push({
868
+ source: "memory",
869
+ code: "memory_lexical_search_not_configured",
870
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
871
+ });
589
872
  }
590
- } else if (input.mergeStrategy === "rrf") {
591
- warnings.push({
592
- source: "memory",
593
- code: "memory_lexical_search_not_configured",
594
- message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
595
- });
596
873
  }
597
- return { semantic, lexical };
874
+ return {
875
+ semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
876
+ lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
877
+ };
598
878
  }
599
879
  async function searchRawMemorySemantically(input) {
600
880
  const limit = clampUnifiedMemorySearchLimit(input.limit);
601
881
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
602
- const sub = await runMemorySource(input, limit, threshold, []);
882
+ const reasoningStrategy = resolveReasoningStrategy(input);
883
+ const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
603
884
  return mergeAcrossSources({
604
885
  memorySubs: sub,
605
886
  insightHits: [],
@@ -640,4 +921,4 @@ function mergeAcrossSources(input) {
640
921
  return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
641
922
  }
642
923
 
643
- export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
924
+ export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };