@melandlabs/memory-store 0.2.7 → 0.3.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.
@@ -2,8 +2,8 @@
2
2
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
3
3
  import { ChromaVectorStore } from '@melandlabs/rag';
4
4
  import { mkdirSync } from 'fs';
5
- import { homedir } from 'os';
6
- import { dirname, join } from 'path';
5
+ import { dirname } from 'path';
6
+ import { getOpenContextPath } from '@melandlabs/env-config';
7
7
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
8
8
  import { serve } from '@hono/node-server';
9
9
  import { Hono } from 'hono';
@@ -187,7 +187,7 @@ __export(sqlite_raw_message_store_exports, {
187
187
  function resolveSQLiteRawMessageDbPath(dbPath) {
188
188
  if (dbPath && dbPath.length > 0) return dbPath;
189
189
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
190
- return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
190
+ return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
191
191
  }
192
192
  function isSQLiteRawMessageStorageAvailable(_env) {
193
193
  return true;
@@ -221,12 +221,11 @@ async function lexicalSearchRawMessages(input) {
221
221
  }
222
222
  return mgr.lexicalSearchMessages(input);
223
223
  }
224
- var manager, DEFAULT_SQLITE_PATH;
224
+ var manager;
225
225
  var init_sqlite_raw_message_store = __esm({
226
226
  "src/storage/sqlite-raw-message-store.ts"() {
227
227
  init_chroma_memory_index();
228
228
  manager = null;
229
- DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
230
229
  }
231
230
  });
232
231
 
@@ -240,7 +239,7 @@ __export(raw_message_store_exports, {
240
239
  getRawMessageStorageBackend: () => getRawMessageStorageBackend,
241
240
  isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
242
241
  });
243
- function resolveBackend(env) {
242
+ function resolveBackend(_env) {
244
243
  const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
245
244
  if (explicit === "postgres") return "postgres";
246
245
  return "sqlite";
@@ -373,6 +372,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
373
372
  function normalizeUnifiedMemoryMergeStrategy(value) {
374
373
  return value === "rrf" ? "rrf" : "similarity";
375
374
  }
375
+ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
376
+ if (value === "rewrite" || value === "iterative") {
377
+ return value;
378
+ }
379
+ return defaultStrategy;
380
+ }
376
381
  var DEFAULT_RRF_K = 60;
377
382
  function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
378
383
  if (!Array.isArray(lists) || lists.length === 0) {
@@ -395,7 +400,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
395
400
  }
396
401
  }
397
402
  }
398
- const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
403
+ const ranked = order.map((key) => {
404
+ const entry = scores.get(key);
405
+ if (!entry) {
406
+ throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
407
+ }
408
+ return entry;
409
+ }).sort((a, b) => {
399
410
  if (b.rrf !== a.rrf) {
400
411
  return b.rrf - a.rrf;
401
412
  }
@@ -440,14 +451,222 @@ function isRawMemorySemanticResult(result) {
440
451
  function deriveLexicalKeywords(query) {
441
452
  return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
442
453
  }
454
+ function parseDateBoundary(value, role) {
455
+ const trimmed = value.trim();
456
+ if (!trimmed) {
457
+ return void 0;
458
+ }
459
+ const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
460
+ if (dateOnlyMatch) {
461
+ const year = Number.parseInt(dateOnlyMatch[1], 10);
462
+ const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
463
+ const day = Number.parseInt(dateOnlyMatch[3], 10);
464
+ if (role === "from") {
465
+ return Date.UTC(year, month, day);
466
+ }
467
+ return Date.UTC(year, month, day + 1) - 1;
468
+ }
469
+ const parsed = Date.parse(trimmed);
470
+ if (Number.isNaN(parsed)) {
471
+ return void 0;
472
+ }
473
+ return parsed;
474
+ }
475
+ function getCandidateTimestamp(metadata) {
476
+ for (const key of ["timestamp", "createdAt", "time"]) {
477
+ const value = metadata[key];
478
+ if (typeof value === "number") {
479
+ return value;
480
+ }
481
+ if (typeof value === "string") {
482
+ const parsed = Date.parse(value);
483
+ if (!Number.isNaN(parsed)) {
484
+ return parsed;
485
+ }
486
+ }
487
+ }
488
+ return void 0;
489
+ }
490
+ function filterByDateRange(items, dateFrom, dateTo) {
491
+ if (!dateFrom && !dateTo) {
492
+ return items;
493
+ }
494
+ const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
495
+ const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
496
+ if (fromMs === void 0 && toMs === void 0) {
497
+ return items;
498
+ }
499
+ return items.filter((item) => {
500
+ const ts = getCandidateTimestamp(item.metadata);
501
+ if (ts === void 0) {
502
+ return true;
503
+ }
504
+ if (fromMs !== void 0 && ts < fromMs) {
505
+ return false;
506
+ }
507
+ if (toMs !== void 0 && ts > toMs) {
508
+ return false;
509
+ }
510
+ return true;
511
+ });
512
+ }
513
+ async function embedQueryVariant(embedQuery, input, query) {
514
+ return embedQuery({
515
+ userId: input.userId,
516
+ query,
517
+ authToken: input.authToken
518
+ });
519
+ }
520
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
521
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
522
+ let semantic = [];
523
+ if (isRawMessageChromaEnabled()) {
524
+ try {
525
+ semantic = (await Promise.all(
526
+ filters.map((filter) => {
527
+ const botId = "botId" in filter ? filter.botId : void 0;
528
+ return searchRawMessagesWithChroma({
529
+ userId: input.userId,
530
+ queryEmbedding,
531
+ limit,
532
+ threshold,
533
+ botId
534
+ });
535
+ })
536
+ )).flat().map(toMemoryResult);
537
+ logger.log?.("[memory-store] Raw message semantic search completed", {
538
+ backend: "chroma",
539
+ dimensions: queryEmbedding.length,
540
+ count: semantic.length
541
+ });
542
+ } catch (error) {
543
+ logger.warn?.(
544
+ "[memory-store] Chroma raw message search failed; falling back to database search:",
545
+ error
546
+ );
547
+ }
548
+ } else if (typeof deps.searchRawMessagesAnn === "function") {
549
+ const searchRawMessagesAnn = deps.searchRawMessagesAnn;
550
+ semantic = (await Promise.all(
551
+ filters.map(
552
+ (filter) => searchRawMessagesAnn({
553
+ userId: input.userId,
554
+ queryEmbedding,
555
+ limit,
556
+ threshold,
557
+ botId: "botId" in filter ? filter.botId : void 0
558
+ })
559
+ )
560
+ )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
561
+ }
562
+ if (semantic.length === 0) {
563
+ try {
564
+ const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
565
+ const manager2 = await getRawMessageManager2();
566
+ if (typeof manager2.searchMessagesSemantically === "function") {
567
+ const results = await manager2.searchMessagesSemantically({
568
+ userId: input.userId,
569
+ queryEmbedding,
570
+ limit,
571
+ threshold
572
+ });
573
+ semantic = results.map((r) => ({
574
+ type: "memory",
575
+ id: r.id,
576
+ content: r.content,
577
+ similarity: r.similarity,
578
+ metadata: r.metadata ?? {}
579
+ }));
580
+ }
581
+ } catch (error) {
582
+ logger.warn?.("[memory-store] SQLite semantic search failed:", error);
583
+ }
584
+ }
585
+ return semantic;
586
+ }
587
+ function mergeByMaxScore(lists) {
588
+ const best = /* @__PURE__ */ new Map();
589
+ for (const list of lists) {
590
+ for (const hit of list) {
591
+ const existing = best.get(hit.id);
592
+ if (!existing || hit.similarity > existing.similarity) {
593
+ best.set(hit.id, hit);
594
+ }
595
+ }
596
+ }
597
+ return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
598
+ }
599
+ function toIterativeRecallCandidate(hit) {
600
+ return {
601
+ id: hit.id,
602
+ content: hit.content,
603
+ similarity: hit.similarity,
604
+ metadata: hit.metadata
605
+ };
606
+ }
607
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
608
+ if (keywords.length === 0) {
609
+ return [];
610
+ }
611
+ if (typeof deps.searchRawMessagesLexical === "function") {
612
+ try {
613
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
614
+ const searchRawMessagesLexical = deps.searchRawMessagesLexical;
615
+ return (await Promise.all(
616
+ filters.map(
617
+ (botId) => searchRawMessagesLexical({
618
+ userId: input.userId,
619
+ keywords,
620
+ limit: Math.ceil(limit / filters.length),
621
+ botId
622
+ })
623
+ )
624
+ )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
625
+ ...toMemoryResult(hit),
626
+ metadata: { ...hit.metadata, scoring: "bm25" }
627
+ }));
628
+ } catch (error) {
629
+ logger.warn?.("[memory-store] lexical memory search failed:", error);
630
+ }
631
+ }
632
+ try {
633
+ const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
634
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
635
+ return (await Promise.all(
636
+ filters.map(
637
+ (botId) => lexicalSearchRawMessages2({
638
+ userId: input.userId,
639
+ keywords,
640
+ limit: Math.ceil(limit / filters.length),
641
+ botId
642
+ })
643
+ )
644
+ )).flat().filter(Boolean).map(
645
+ (r) => toMemoryResult(
646
+ r
647
+ )
648
+ );
649
+ } catch (error) {
650
+ logger.warn?.("[memory-store] SQLite lexical search failed:", error);
651
+ }
652
+ return [];
653
+ }
443
654
  function createUnifiedSearch(deps = {}) {
444
655
  const logger = deps.logger ?? console;
656
+ function resolveReasoningStrategy(input) {
657
+ const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
658
+ if (requested !== "none") {
659
+ return requested;
660
+ }
661
+ return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
662
+ }
445
663
  async function searchUnifiedMemory(input) {
446
664
  const query = input.query.trim();
447
665
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
448
666
  const limit = clampUnifiedMemorySearchLimit(input.limit);
449
667
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
450
668
  const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
669
+ const reasoningStrategy = resolveReasoningStrategy(input);
451
670
  const warnings = [];
452
671
  if (!query) {
453
672
  return {
@@ -459,10 +678,21 @@ function createUnifiedSearch(deps = {}) {
459
678
  };
460
679
  }
461
680
  let memorySubs;
681
+ const reasoningInfo = { strategy: reasoningStrategy };
682
+ if (input.dateFrom || input.dateTo) {
683
+ reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
684
+ }
462
685
  if (sources.includes("memory")) {
463
686
  if (isRawMessageStorageAvailable()) {
464
687
  try {
465
- memorySubs = await runMemorySource(input, limit, threshold, warnings);
688
+ memorySubs = await runMemorySource(
689
+ input,
690
+ limit,
691
+ threshold,
692
+ warnings,
693
+ reasoningStrategy,
694
+ reasoningInfo
695
+ );
466
696
  } catch (error) {
467
697
  logger.warn?.("[memory-store] memory source failed:", error);
468
698
  warnings.push({
@@ -541,121 +771,155 @@ function createUnifiedSearch(deps = {}) {
541
771
  limit,
542
772
  strategy: mergeStrategy
543
773
  });
544
- return {
774
+ const output = {
545
775
  query: input.query,
546
776
  sources,
547
777
  results: merged,
548
778
  count: merged.length,
549
779
  warnings
550
780
  };
781
+ if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
782
+ output.reasoning = reasoningInfo;
783
+ }
784
+ return output;
551
785
  }
552
- async function runMemorySource(input, limit, threshold, warnings) {
786
+ async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
787
+ if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
788
+ warnings.push({
789
+ source: "memory",
790
+ code: "memory_iterative_planner_not_configured",
791
+ message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
792
+ });
793
+ if (reasoningInfo) reasoningInfo.degraded = true;
794
+ }
795
+ if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
796
+ warnings.push({
797
+ source: "memory",
798
+ code: "memory_query_rewrite_not_configured",
799
+ message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
800
+ });
801
+ if (reasoningInfo) reasoningInfo.degraded = true;
802
+ }
803
+ if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
804
+ const planner = deps.reasoning.iterativePlanner;
805
+ const result = await planner.plan({
806
+ query: input.query,
807
+ dateFrom: input.dateFrom,
808
+ dateTo: input.dateTo,
809
+ executor: {
810
+ search: async (request) => {
811
+ const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
812
+ const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
813
+ let semanticHits = [];
814
+ if (typeof deps.embedQuery === "function") {
815
+ try {
816
+ const semanticQuery = keywords2.join(" ");
817
+ const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
818
+ semanticHits = await runSemanticSearchForEmbedding(
819
+ deps,
820
+ input,
821
+ queryEmbedding,
822
+ limit,
823
+ threshold,
824
+ logger
825
+ );
826
+ } catch (error) {
827
+ logger.warn?.(
828
+ "[memory-store] Iterative semantic search failed; using lexical results only:",
829
+ error
830
+ );
831
+ }
832
+ }
833
+ const candidates = filterByDateRange(
834
+ mergeByMaxScore([
835
+ lexicalHits.map(toIterativeRecallCandidate),
836
+ semanticHits.map(toIterativeRecallCandidate)
837
+ ]),
838
+ request.dateFrom,
839
+ request.dateTo
840
+ );
841
+ return { candidates };
842
+ }
843
+ }
844
+ });
845
+ if (reasoningInfo) {
846
+ reasoningInfo.iterations = result.stats.iterations;
847
+ reasoningInfo.evidenceCount = result.evidence.length;
848
+ if (planner.lastDegraded?.()) {
849
+ reasoningInfo.degraded = true;
850
+ }
851
+ }
852
+ const semantic2 = result.evidence.map((candidate) => ({
853
+ type: "memory",
854
+ id: candidate.id,
855
+ content: candidate.content,
856
+ similarity: candidate.similarity,
857
+ metadata: { ...candidate.metadata, reasoning: "iterative" }
858
+ }));
859
+ return { semantic: semantic2, lexical: [] };
860
+ }
553
861
  if (typeof deps.embedQuery !== "function") {
554
862
  warnings.push({
555
863
  source: "memory",
556
864
  code: "memory_lexical_search_fallback",
557
865
  message: "Semantic search not configured, using keyword search as fallback"
558
866
  });
559
- const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
560
- const keywords = deriveLexicalKeywords(input.query);
561
- let lexical2 = [];
562
- if (keywords.length > 0) {
563
- try {
564
- const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
565
- const results = await Promise.all(
566
- filters2.map(
567
- (botId) => lexicalSearchRawMessages2({
568
- userId: input.userId,
569
- keywords,
570
- limit: Math.ceil(limit / filters2.length),
571
- botId
572
- })
573
- )
574
- );
575
- lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
576
- } catch (error) {
577
- logger.warn?.("[memory-store] Default lexical search failed:", error);
578
- }
579
- }
867
+ const keywords2 = deriveLexicalKeywords(input.query);
868
+ const lexical2 = filterByDateRange(
869
+ await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
870
+ input.dateFrom,
871
+ input.dateTo
872
+ );
580
873
  return { semantic: [], lexical: lexical2 };
581
874
  }
582
- const queryEmbedding = await deps.embedQuery({
583
- userId: input.userId,
584
- query: input.query,
585
- authToken: input.authToken
586
- });
587
- const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
588
875
  let semantic = [];
589
- if (isRawMessageChromaEnabled()) {
876
+ const embedQuery = deps.embedQuery;
877
+ if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
590
878
  try {
591
- semantic = (await Promise.all(
592
- filters.map((filter) => {
593
- const botId = "botId" in filter ? filter.botId : void 0;
594
- return searchRawMessagesWithChroma({
595
- userId: input.userId,
596
- queryEmbedding,
597
- limit,
598
- threshold,
599
- botId
600
- });
601
- })
602
- )).flat().map(toMemoryResult);
603
- logger.log?.("[memory-store] Raw message semantic search completed", {
604
- backend: "chroma",
605
- dimensions: queryEmbedding.length,
606
- count: semantic.length
879
+ const variants = await deps.reasoning.queryRewriter.rewrite({
880
+ query: input.query,
881
+ userId: input.userId,
882
+ authToken: input.authToken
607
883
  });
608
- } catch (error) {
609
- logger.warn?.(
610
- "[memory-store] Chroma raw message search failed; falling back to database search:",
611
- error
884
+ if (reasoningInfo) {
885
+ reasoningInfo.rewrittenQueries = variants;
886
+ if (deps.reasoning.queryRewriter.lastDegraded?.()) {
887
+ reasoningInfo.degraded = true;
888
+ }
889
+ }
890
+ const embeddings = await Promise.all(
891
+ variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
892
+ );
893
+ const lists = await Promise.all(
894
+ embeddings.map(
895
+ (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
896
+ )
612
897
  );
898
+ semantic = mergeByMaxScore(lists);
899
+ } catch (error) {
900
+ logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
901
+ warnings.push({
902
+ source: "memory",
903
+ code: "memory_query_rewrite_failed",
904
+ message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
905
+ });
906
+ if (reasoningInfo) reasoningInfo.degraded = true;
613
907
  }
614
- } else if (typeof deps.searchRawMessagesAnn === "function") {
615
- semantic = (await Promise.all(
616
- filters.map(
617
- (filter) => deps.searchRawMessagesAnn({
618
- userId: input.userId,
619
- queryEmbedding,
620
- limit,
621
- threshold,
622
- botId: "botId" in filter ? filter.botId : void 0
623
- })
624
- )
625
- )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
626
908
  }
627
909
  if (semantic.length === 0) {
628
- try {
629
- const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
630
- const manager2 = await getRawMessageManager2();
631
- if (typeof manager2.searchMessagesSemantically === "function") {
632
- const results = await manager2.searchMessagesSemantically({
633
- userId: input.userId,
634
- queryEmbedding,
635
- limit,
636
- threshold
637
- });
638
- semantic = results.map((r) => ({
639
- type: "memory",
640
- id: r.id,
641
- content: r.content,
642
- similarity: r.similarity,
643
- metadata: r.metadata ?? {}
644
- }));
645
- }
646
- } catch (error) {
647
- logger.warn?.("[memory-store] SQLite semantic search failed:", error);
648
- }
910
+ const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
911
+ semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
649
912
  }
650
913
  let lexical = [];
651
- if (typeof deps.searchRawMessagesLexical === "function") {
652
- const keywords = deriveLexicalKeywords(input.query);
653
- if (keywords.length > 0) {
914
+ const keywords = deriveLexicalKeywords(input.query);
915
+ if (keywords.length > 0) {
916
+ if (typeof deps.searchRawMessagesLexical === "function") {
654
917
  try {
655
918
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
919
+ const searchRawMessagesLexical = deps.searchRawMessagesLexical;
656
920
  lexical = (await Promise.all(
657
921
  lexFilters.map(
658
- (botId) => deps.searchRawMessagesLexical({
922
+ (botId) => searchRawMessagesLexical({
659
923
  userId: input.userId,
660
924
  keywords,
661
925
  limit,
@@ -674,20 +938,24 @@ function createUnifiedSearch(deps = {}) {
674
938
  message: error.message ?? "memory_lexical_search_failed"
675
939
  });
676
940
  }
941
+ } else if (input.mergeStrategy === "rrf") {
942
+ warnings.push({
943
+ source: "memory",
944
+ code: "memory_lexical_search_not_configured",
945
+ message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
946
+ });
677
947
  }
678
- } else if (input.mergeStrategy === "rrf") {
679
- warnings.push({
680
- source: "memory",
681
- code: "memory_lexical_search_not_configured",
682
- message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
683
- });
684
948
  }
685
- return { semantic, lexical };
949
+ return {
950
+ semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
951
+ lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
952
+ };
686
953
  }
687
954
  async function searchRawMemorySemantically(input) {
688
955
  const limit = clampUnifiedMemorySearchLimit(input.limit);
689
956
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
690
- const sub = await runMemorySource(input, limit, threshold, []);
957
+ const reasoningStrategy = resolveReasoningStrategy(input);
958
+ const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
691
959
  return mergeAcrossSources({
692
960
  memorySubs: sub,
693
961
  insightHits: [],
@@ -1065,6 +1333,9 @@ async function buildUnified(args) {
1065
1333
  } else if (args.memoryBackend === "chroma") {
1066
1334
  if (!args.chromaUrl)
1067
1335
  throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
1336
+ if (!aiRag) {
1337
+ throw new Error("ai-rag modules not loaded");
1338
+ }
1068
1339
  const store = new aiRag.ChromaVectorStore({
1069
1340
  url: args.chromaUrl,
1070
1341
  collectionName: "opencontext_raw_messages"
@@ -1103,6 +1374,9 @@ async function buildUnified(args) {
1103
1374
  throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
1104
1375
  if (!unified.embedQuery)
1105
1376
  throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
1377
+ if (!aiRag) {
1378
+ throw new Error("ai-rag modules not loaded");
1379
+ }
1106
1380
  const store = new aiRag.ChromaVectorStore({
1107
1381
  url: args.chromaUrl,
1108
1382
  collectionName: args.insightsCollection
@@ -1120,6 +1394,9 @@ async function buildUnified(args) {
1120
1394
  throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
1121
1395
  if (!unified.embedQuery)
1122
1396
  throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
1397
+ if (!aiRag) {
1398
+ throw new Error("ai-rag modules not loaded");
1399
+ }
1123
1400
  const store = new aiRag.ChromaVectorStore({
1124
1401
  url: args.chromaUrl,
1125
1402
  collectionName: args.knowledgeCollection