@melandlabs/memory-store 0.3.1 → 1.0.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.
package/dist/http.js CHANGED
@@ -1,11 +1,14 @@
1
+ import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
1
2
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
2
3
  import { ChromaVectorStore } from '@melandlabs/rag';
3
4
  import { mkdirSync } from 'fs';
4
5
  import { dirname } from 'path';
5
6
  import { getOpenContextPath } from '@melandlabs/env-config';
6
- import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
7
+ import { SQLiteRawMessageManager, getSQLiteVsaStore, closeSQLiteVsaStore } from '@melandlabs/sqlite';
7
8
  import { serve } from '@hono/node-server';
8
9
  import { Hono } from 'hono';
10
+ import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
11
+ import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
9
12
 
10
13
  var __defProp = Object.defineProperty;
11
14
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -16,8 +19,6 @@ var __export = (target, all) => {
16
19
  for (var name in all)
17
20
  __defProp(target, name, { get: all[name], enumerable: true });
18
21
  };
19
-
20
- // src/contracts.ts
21
22
  function normalizeTimestampToMs(value) {
22
23
  if (!Number.isFinite(value)) {
23
24
  return 0;
@@ -324,6 +325,41 @@ var init_raw_message_store = __esm({
324
325
  init_chroma_memory_index();
325
326
  init_raw_message_store();
326
327
 
328
+ // src/search/reranker.ts
329
+ async function applyReranker(reranker, query, results) {
330
+ if (!reranker || results.length === 0) {
331
+ return results;
332
+ }
333
+ const scores = await reranker.rerank({
334
+ query,
335
+ candidates: results.map((result) => ({
336
+ id: result.id,
337
+ content: result.content,
338
+ metadata: result.metadata
339
+ }))
340
+ });
341
+ if (scores.length === 0) {
342
+ return results;
343
+ }
344
+ const byId = new Map(results.map((result) => [result.id, result]));
345
+ const reordered = [];
346
+ const seen = /* @__PURE__ */ new Set();
347
+ for (const { id, score } of scores) {
348
+ const hit = byId.get(id);
349
+ if (!hit || seen.has(id)) {
350
+ continue;
351
+ }
352
+ seen.add(id);
353
+ reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
354
+ }
355
+ for (const hit of results) {
356
+ if (!seen.has(hit.id)) {
357
+ reordered.push(hit);
358
+ }
359
+ }
360
+ return reordered;
361
+ }
362
+
327
363
  // src/search/utilities.ts
328
364
  var DEFAULT_LIMIT = 10;
329
365
  var DEFAULT_THRESHOLD = 0.7;
@@ -445,6 +481,264 @@ function isRawMemorySemanticResult(result) {
445
481
  const item = result;
446
482
  return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
447
483
  }
484
+ async function resolveScopePeer(input) {
485
+ const peers = input.peerFilter ?? [];
486
+ if (peers.length === 0) {
487
+ return { peers: [], warnings: [] };
488
+ }
489
+ if (typeof input.scopeCheck !== "function") {
490
+ return { peers, warnings: [] };
491
+ }
492
+ const retained = [];
493
+ const dropped = [];
494
+ for (const peer of peers) {
495
+ const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
496
+ if (ok) {
497
+ retained.push(peer);
498
+ } else {
499
+ dropped.push(peer);
500
+ }
501
+ }
502
+ if (dropped.length === 0) {
503
+ return { peers, warnings: [] };
504
+ }
505
+ if (retained.length === 0) {
506
+ return {
507
+ peers: [],
508
+ warnings: [
509
+ {
510
+ source: "memory",
511
+ code: "peer_filter_outside_user_scope",
512
+ message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
513
+ }
514
+ ]
515
+ };
516
+ }
517
+ return {
518
+ peers: retained,
519
+ warnings: [
520
+ {
521
+ source: "memory",
522
+ code: "peer_filter_outside_user_scope",
523
+ message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
524
+ }
525
+ ]
526
+ };
527
+ }
528
+
529
+ // src/search/gather-evidence.ts
530
+ var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
531
+ var MAX_EVIDENCE_CHARS = 600;
532
+ function toSummaryResult(row) {
533
+ const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
534
+ const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
535
+ return {
536
+ type: "memory",
537
+ id: row.summaryId,
538
+ content: row.summaryText,
539
+ similarity: 1,
540
+ metadata: {
541
+ tier: "summary",
542
+ summaryTier: row.summaryTier,
543
+ keywords: row.keywords,
544
+ startTimestamp: start,
545
+ endTimestamp: end
546
+ }
547
+ };
548
+ }
549
+ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
550
+ if (typeof deps.searchSummaries !== "function") {
551
+ return { hits: [], warnings: [] };
552
+ }
553
+ const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
554
+ try {
555
+ const rows = await deps.searchSummaries({
556
+ userId: input.userId,
557
+ query: input.query,
558
+ keywords,
559
+ limit,
560
+ threshold,
561
+ authToken: input.authToken,
562
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
563
+ });
564
+ const hits = rows.map(toSummaryResult);
565
+ return { hits, warnings: [] };
566
+ } catch (error) {
567
+ logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
568
+ return {
569
+ hits: [],
570
+ warnings: [
571
+ {
572
+ source: "memory",
573
+ code: "reflect_summaries_failed",
574
+ message: error.message ?? "reflect_summaries_failed"
575
+ }
576
+ ]
577
+ };
578
+ }
579
+ }
580
+ function truncate(input, max = MAX_EVIDENCE_CHARS) {
581
+ if (input.length <= max) {
582
+ return input;
583
+ }
584
+ return `${input.slice(0, max - 1)}\u2026`;
585
+ }
586
+ function buildSynthesisPrompt(input) {
587
+ const grouped = /* @__PURE__ */ new Map();
588
+ for (const item of input.evidence) {
589
+ const bucket = grouped.get(item.source) ?? [];
590
+ bucket.push(item);
591
+ grouped.set(item.source, bucket);
592
+ }
593
+ const sections = [];
594
+ for (const tier of DEFAULT_TIERS) {
595
+ const items = grouped.get(tier) ?? [];
596
+ if (items.length === 0) {
597
+ continue;
598
+ }
599
+ const lines = items.map(
600
+ (item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
601
+ );
602
+ sections.push(`## ${tier}
603
+ ${lines.join("\n")}`);
604
+ }
605
+ const schemaDirective = input.responseSchema ? `
606
+ Respond with a JSON object that conforms to the following schema:
607
+ ${JSON.stringify(input.responseSchema, null, 2)}
608
+ Wrap your JSON in a single fenced \`\`\`json code block; do not add any prose outside the code block.` : "\nRespond in plain prose. Be concise; cite evidence by its [n] number.";
609
+ if (sections.length === 0) {
610
+ return `You are a reflection assistant.
611
+
612
+ Question: ${input.query}
613
+
614
+ No evidence was found across any tier. State that explicitly.${schemaDirective}`;
615
+ }
616
+ return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
617
+
618
+ Question: ${input.query}
619
+
620
+ Evidence (grouped by tier):
621
+ ${sections.join("\n\n")}${schemaDirective}`;
622
+ }
623
+ function extractJsonPayload(text) {
624
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
625
+ if (fenced?.[1]) {
626
+ return fenced[1];
627
+ }
628
+ const start = text.indexOf("{");
629
+ if (start === -1) {
630
+ return void 0;
631
+ }
632
+ let depth = 0;
633
+ let inString = false;
634
+ let escaped = false;
635
+ for (let i = start; i < text.length; i += 1) {
636
+ const ch = text[i];
637
+ if (escaped) {
638
+ escaped = false;
639
+ continue;
640
+ }
641
+ if (inString) {
642
+ if (ch === "\\") {
643
+ escaped = true;
644
+ } else if (ch === '"') {
645
+ inString = false;
646
+ }
647
+ continue;
648
+ }
649
+ if (ch === '"') {
650
+ inString = true;
651
+ } else if (ch === "{") {
652
+ depth += 1;
653
+ } else if (ch === "}") {
654
+ depth -= 1;
655
+ if (depth === 0) {
656
+ return text.slice(start, i + 1);
657
+ }
658
+ }
659
+ }
660
+ return void 0;
661
+ }
662
+ function coerceAnswer(text, schema) {
663
+ if (!schema) {
664
+ return text;
665
+ }
666
+ const payload = extractJsonPayload(text);
667
+ if (!payload) {
668
+ return text;
669
+ }
670
+ try {
671
+ const parsed = JSON.parse(payload);
672
+ if (parsed && typeof parsed === "object") {
673
+ const answer = parsed.answer;
674
+ if (typeof answer === "string") {
675
+ return answer;
676
+ }
677
+ }
678
+ } catch {
679
+ }
680
+ return text;
681
+ }
682
+ async function synthesizeAnswer(input) {
683
+ const { query, hits, responseSchema, deps, logger } = input;
684
+ const evidence = hits.map((hit) => ({
685
+ id: hit.id,
686
+ source: mapHitToTier(hit),
687
+ snippet: truncate(hit.content),
688
+ score: hit.similarity
689
+ }));
690
+ const warnings = [];
691
+ const complete = deps.reasoning?.complete;
692
+ if (!complete) {
693
+ warnings.push({
694
+ source: "memory",
695
+ code: "reflect_llm_not_configured",
696
+ message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
697
+ });
698
+ return { answer: "", warnings };
699
+ }
700
+ const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
701
+ try {
702
+ const raw = await complete(prompt);
703
+ const answer = coerceAnswer(raw, responseSchema);
704
+ logger.log?.("[memory-store] search synthesis completed", {
705
+ evidenceCount: evidence.length,
706
+ answerChars: answer.length
707
+ });
708
+ return { answer, warnings };
709
+ } catch (error) {
710
+ logger.warn?.("[memory-store] search synthesis failed:", error);
711
+ warnings.push({
712
+ source: "memory",
713
+ code: "reflect_llm_failed",
714
+ message: error.message ?? "reflect_llm_failed"
715
+ });
716
+ return { answer: "", warnings };
717
+ }
718
+ }
719
+ function mapHitToTier(hit) {
720
+ const tierMarker = hit.metadata?.tier;
721
+ if (tierMarker === "summary") {
722
+ return "summary";
723
+ }
724
+ if (hit.type === "memory") {
725
+ return "raw";
726
+ }
727
+ if (hit.type === "insight") {
728
+ return "insight";
729
+ }
730
+ if (hit.type === "knowledge") {
731
+ return "knowledge";
732
+ }
733
+ return hit.type;
734
+ }
735
+ async function resolveSearchScopePeers(input) {
736
+ return resolveScopePeer({
737
+ userId: input.userId,
738
+ peerFilter: input.peerFilter,
739
+ scopeCheck: input.deps.peerScopeCheck
740
+ });
741
+ }
448
742
 
449
743
  // src/search/unified-search.ts
450
744
  function deriveLexicalKeywords(query) {
@@ -516,7 +810,7 @@ async function embedQueryVariant(embedQuery, input, query) {
516
810
  authToken: input.authToken
517
811
  });
518
812
  }
519
- async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
813
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
520
814
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
521
815
  let semantic = [];
522
816
  if (isRawMessageChromaEnabled()) {
@@ -546,6 +840,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
546
840
  }
547
841
  } else if (typeof deps.searchRawMessagesAnn === "function") {
548
842
  const searchRawMessagesAnn = deps.searchRawMessagesAnn;
843
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
549
844
  semantic = (await Promise.all(
550
845
  filters.map(
551
846
  (filter) => searchRawMessagesAnn({
@@ -553,7 +848,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
553
848
  queryEmbedding,
554
849
  limit,
555
850
  threshold,
556
- botId: "botId" in filter ? filter.botId : void 0
851
+ botId: "botId" in filter ? filter.botId : void 0,
852
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
853
+ ...factTypes ? { factTypes } : {}
557
854
  })
558
855
  )
559
856
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
@@ -563,11 +860,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
563
860
  const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
564
861
  const manager2 = await getRawMessageManager2();
565
862
  if (typeof manager2.searchMessagesSemantically === "function") {
863
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
566
864
  const results = await manager2.searchMessagesSemantically({
567
865
  userId: input.userId,
568
866
  queryEmbedding,
569
867
  limit,
570
- threshold
868
+ threshold,
869
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
870
+ ...factTypes ? { factTypes } : {}
571
871
  });
572
872
  semantic = results.map((r) => ({
573
873
  type: "memory",
@@ -603,7 +903,7 @@ function toIterativeRecallCandidate(hit) {
603
903
  metadata: hit.metadata
604
904
  };
605
905
  }
606
- async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
906
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
607
907
  if (keywords.length === 0) {
608
908
  return [];
609
909
  }
@@ -611,13 +911,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
611
911
  try {
612
912
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
613
913
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
914
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
614
915
  return (await Promise.all(
615
916
  filters.map(
616
917
  (botId) => searchRawMessagesLexical({
617
918
  userId: input.userId,
618
919
  keywords,
619
920
  limit: Math.ceil(limit / filters.length),
620
- botId
921
+ botId,
922
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
923
+ ...factTypes ? { factTypes } : {}
621
924
  })
622
925
  )
623
926
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -631,13 +934,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
631
934
  try {
632
935
  const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
633
936
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
937
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
634
938
  return (await Promise.all(
635
939
  filters.map(
636
940
  (botId) => lexicalSearchRawMessages2({
637
941
  userId: input.userId,
638
942
  keywords,
639
943
  limit: Math.ceil(limit / filters.length),
640
- botId
944
+ botId,
945
+ ...factTypes ? { factTypes } : {}
641
946
  })
642
947
  )
643
948
  )).flat().filter(Boolean).map(
@@ -650,6 +955,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
650
955
  }
651
956
  return [];
652
957
  }
958
+ function searchInputToUnified(input) {
959
+ return {
960
+ userId: input.userId,
961
+ query: input.query,
962
+ sources: input.sources ? [...input.sources] : void 0,
963
+ limit: input.limit,
964
+ threshold: input.threshold,
965
+ authToken: input.authToken,
966
+ includeArchivedInsights: input.includeArchivedInsights,
967
+ botIds: input.botIds,
968
+ documentIds: input.documentIds,
969
+ asOf: input.asOf,
970
+ dateFrom: input.dateFrom,
971
+ dateTo: input.dateTo,
972
+ mergeStrategy: input.mergeStrategy,
973
+ peerFilter: input.peerFilter,
974
+ reasoningStrategy: input.reasoningStrategy,
975
+ factTypes: input.factTypes
976
+ };
977
+ }
978
+ function mapHitToEvidenceSource(hit) {
979
+ const tierMarker = hit.metadata?.tier;
980
+ if (tierMarker === "summary") {
981
+ return "summary";
982
+ }
983
+ if (hit.type === "memory") {
984
+ return "raw";
985
+ }
986
+ if (hit.type === "insight") {
987
+ return "insight";
988
+ }
989
+ if (hit.type === "knowledge") {
990
+ return "knowledge";
991
+ }
992
+ return hit.type;
993
+ }
653
994
  function createUnifiedSearch(deps = {}) {
654
995
  const logger = deps.logger ?? console;
655
996
  function resolveReasoningStrategy(input) {
@@ -659,130 +1000,7 @@ function createUnifiedSearch(deps = {}) {
659
1000
  }
660
1001
  return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
661
1002
  }
662
- async function searchUnifiedMemory(input) {
663
- const query = input.query.trim();
664
- const sources = normalizeUnifiedMemorySearchSources(input.sources);
665
- const limit = clampUnifiedMemorySearchLimit(input.limit);
666
- const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
667
- const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
668
- const reasoningStrategy = resolveReasoningStrategy(input);
669
- const warnings = [];
670
- if (!query) {
671
- return {
672
- query,
673
- sources,
674
- results: [],
675
- count: 0,
676
- warnings
677
- };
678
- }
679
- let memorySubs;
680
- const reasoningInfo = { strategy: reasoningStrategy };
681
- if (input.dateFrom || input.dateTo) {
682
- reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
683
- }
684
- if (sources.includes("memory")) {
685
- if (isRawMessageStorageAvailable()) {
686
- try {
687
- memorySubs = await runMemorySource(
688
- input,
689
- limit,
690
- threshold,
691
- warnings,
692
- reasoningStrategy,
693
- reasoningInfo
694
- );
695
- } catch (error) {
696
- logger.warn?.("[memory-store] memory source failed:", error);
697
- warnings.push({
698
- source: "memory",
699
- code: "memory_search_failed",
700
- message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
701
- });
702
- }
703
- } else {
704
- warnings.push({
705
- source: "memory",
706
- code: "raw_message_storage_unavailable",
707
- message: "Raw memory storage is not available in this environment."
708
- });
709
- }
710
- }
711
- const insightHits = [];
712
- if (sources.includes("insights") && typeof deps.searchInsights === "function") {
713
- try {
714
- const hits = await deps.searchInsights({
715
- userId: input.userId,
716
- query: input.query,
717
- limit,
718
- threshold,
719
- botIds: input.botIds,
720
- includeArchived: input.includeArchivedInsights,
721
- authToken: input.authToken
722
- });
723
- for (const hit of hits) {
724
- insightHits.push({
725
- type: "insight",
726
- id: hit.id,
727
- content: hit.content,
728
- similarity: hit.similarity,
729
- metadata: hit.metadata
730
- });
731
- }
732
- } catch (error) {
733
- logger.warn?.("[memory-store] insights source failed:", error);
734
- warnings.push({
735
- source: "insights",
736
- code: "insights_search_failed",
737
- message: error.message ?? "insights_search_failed"
738
- });
739
- }
740
- } else if (sources.includes("insights")) ;
741
- const knowledgeHits = [];
742
- if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
743
- try {
744
- const hits = await deps.searchKnowledge({
745
- userId: input.userId,
746
- query: input.query,
747
- options: {
748
- limit,
749
- threshold,
750
- documentIds: input.documentIds
751
- },
752
- authToken: input.authToken
753
- });
754
- for (const hit of hits) {
755
- knowledgeHits.push(toKnowledgeResult(hit));
756
- }
757
- } catch (error) {
758
- logger.warn?.("[memory-store] knowledge source failed:", error);
759
- warnings.push({
760
- source: "knowledge",
761
- code: "knowledge_search_failed",
762
- message: error.message ?? "knowledge_search_failed"
763
- });
764
- }
765
- } else if (sources.includes("knowledge")) ;
766
- const merged = mergeAcrossSources({
767
- memorySubs,
768
- insightHits,
769
- knowledgeHits,
770
- limit,
771
- strategy: mergeStrategy
772
- });
773
- const output = {
774
- query: input.query,
775
- sources,
776
- results: merged,
777
- count: merged.length,
778
- warnings
779
- };
780
- if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
781
- output.reasoning = reasoningInfo;
782
- }
783
- return output;
784
- }
785
- async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
1003
+ async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
786
1004
  if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
787
1005
  warnings.push({
788
1006
  source: "memory",
@@ -808,7 +1026,14 @@ function createUnifiedSearch(deps = {}) {
808
1026
  executor: {
809
1027
  search: async (request) => {
810
1028
  const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
811
- const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
1029
+ const lexicalHits = await runLexicalSearchForKeywords(
1030
+ deps,
1031
+ input,
1032
+ keywords2,
1033
+ limit,
1034
+ logger,
1035
+ peerPeers
1036
+ );
812
1037
  let semanticHits = [];
813
1038
  if (typeof deps.embedQuery === "function") {
814
1039
  try {
@@ -820,7 +1045,8 @@ function createUnifiedSearch(deps = {}) {
820
1045
  queryEmbedding,
821
1046
  limit,
822
1047
  threshold,
823
- logger
1048
+ logger,
1049
+ peerPeers
824
1050
  );
825
1051
  } catch (error) {
826
1052
  logger.warn?.(
@@ -865,7 +1091,7 @@ function createUnifiedSearch(deps = {}) {
865
1091
  });
866
1092
  const keywords2 = deriveLexicalKeywords(input.query);
867
1093
  const lexical2 = filterByDateRange(
868
- await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
1094
+ await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
869
1095
  input.dateFrom,
870
1096
  input.dateTo
871
1097
  );
@@ -891,7 +1117,7 @@ function createUnifiedSearch(deps = {}) {
891
1117
  );
892
1118
  const lists = await Promise.all(
893
1119
  embeddings.map(
894
- (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
1120
+ (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
895
1121
  )
896
1122
  );
897
1123
  semantic = mergeByMaxScore(lists);
@@ -907,7 +1133,15 @@ function createUnifiedSearch(deps = {}) {
907
1133
  }
908
1134
  if (semantic.length === 0) {
909
1135
  const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
910
- semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
1136
+ semantic = await runSemanticSearchForEmbedding(
1137
+ deps,
1138
+ input,
1139
+ queryEmbedding,
1140
+ limit,
1141
+ threshold,
1142
+ logger,
1143
+ peerPeers
1144
+ );
911
1145
  }
912
1146
  let lexical = [];
913
1147
  const keywords = deriveLexicalKeywords(input.query);
@@ -916,13 +1150,16 @@ function createUnifiedSearch(deps = {}) {
916
1150
  try {
917
1151
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
918
1152
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
1153
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
919
1154
  lexical = (await Promise.all(
920
1155
  lexFilters.map(
921
1156
  (botId) => searchRawMessagesLexical({
922
1157
  userId: input.userId,
923
1158
  keywords,
924
1159
  limit,
925
- botId
1160
+ botId,
1161
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
1162
+ ...factTypes ? { factTypes } : {}
926
1163
  })
927
1164
  )
928
1165
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -950,20 +1187,272 @@ function createUnifiedSearch(deps = {}) {
950
1187
  lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
951
1188
  };
952
1189
  }
1190
+ async function searchUnifiedMemory(input) {
1191
+ const query = input.query.trim();
1192
+ const sources = normalizeUnifiedMemorySearchSources(input.sources);
1193
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
1194
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
1195
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1196
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1197
+ );
1198
+ const reasoningStrategy = resolveReasoningStrategy(input);
1199
+ const warnings = [];
1200
+ const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
1201
+ userId: input.userId,
1202
+ peerFilter: input.peerFilter,
1203
+ scopeCheck: deps.peerScopeCheck
1204
+ }) : { peers: [], warnings: [] };
1205
+ warnings.push(...peerScope.warnings);
1206
+ const peerPeers = peerScope.peers;
1207
+ if (!query) {
1208
+ return {
1209
+ query,
1210
+ sources,
1211
+ results: [],
1212
+ count: 0,
1213
+ warnings
1214
+ };
1215
+ }
1216
+ let memorySubs;
1217
+ const reasoningInfo = { strategy: reasoningStrategy };
1218
+ if (input.dateFrom || input.dateTo) {
1219
+ reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
1220
+ }
1221
+ if (sources.includes("memory")) {
1222
+ const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
1223
+ if (isRawMessageStorageAvailable() || hasRawProviders) {
1224
+ try {
1225
+ memorySubs = await runMemorySource(
1226
+ input,
1227
+ limit,
1228
+ threshold,
1229
+ warnings,
1230
+ reasoningStrategy,
1231
+ reasoningInfo,
1232
+ peerPeers
1233
+ );
1234
+ } catch (error) {
1235
+ logger.warn?.("[memory-store] memory source failed:", error);
1236
+ warnings.push({
1237
+ source: "memory",
1238
+ code: "memory_search_failed",
1239
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
1240
+ });
1241
+ }
1242
+ } else {
1243
+ warnings.push({
1244
+ source: "memory",
1245
+ code: "raw_message_storage_unavailable",
1246
+ message: "Raw memory storage is not available in this environment."
1247
+ });
1248
+ }
1249
+ }
1250
+ const insightHits = [];
1251
+ if (sources.includes("insights") && typeof deps.searchInsights === "function") {
1252
+ try {
1253
+ const hits = await deps.searchInsights({
1254
+ userId: input.userId,
1255
+ query: input.query,
1256
+ limit,
1257
+ threshold,
1258
+ botIds: input.botIds,
1259
+ includeArchived: input.includeArchivedInsights,
1260
+ authToken: input.authToken,
1261
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
1262
+ });
1263
+ for (const hit of hits) {
1264
+ insightHits.push({
1265
+ type: "insight",
1266
+ id: hit.id,
1267
+ content: hit.content,
1268
+ similarity: hit.similarity,
1269
+ metadata: hit.metadata
1270
+ });
1271
+ }
1272
+ } catch (error) {
1273
+ logger.warn?.("[memory-store] insights source failed:", error);
1274
+ warnings.push({
1275
+ source: "insights",
1276
+ code: "insights_search_failed",
1277
+ message: error.message ?? "insights_search_failed"
1278
+ });
1279
+ }
1280
+ } else if (sources.includes("insights")) ;
1281
+ const knowledgeHits = [];
1282
+ if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
1283
+ try {
1284
+ const hits = await deps.searchKnowledge({
1285
+ userId: input.userId,
1286
+ query: input.query,
1287
+ options: {
1288
+ limit,
1289
+ threshold,
1290
+ documentIds: input.documentIds
1291
+ },
1292
+ authToken: input.authToken,
1293
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
1294
+ });
1295
+ for (const hit of hits) {
1296
+ knowledgeHits.push(toKnowledgeResult(hit));
1297
+ }
1298
+ } catch (error) {
1299
+ logger.warn?.("[memory-store] knowledge source failed:", error);
1300
+ warnings.push({
1301
+ source: "knowledge",
1302
+ code: "knowledge_search_failed",
1303
+ message: error.message ?? "knowledge_search_failed"
1304
+ });
1305
+ }
1306
+ } else if (sources.includes("knowledge")) ;
1307
+ const merged = mergeAcrossSources({
1308
+ memorySubs,
1309
+ insightHits,
1310
+ knowledgeHits,
1311
+ limit,
1312
+ strategy: mergeStrategy
1313
+ });
1314
+ const ranked = await applyReranker(deps.reranker, input.query, merged);
1315
+ const output = {
1316
+ query: input.query,
1317
+ sources,
1318
+ results: ranked,
1319
+ count: ranked.length,
1320
+ warnings
1321
+ };
1322
+ if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
1323
+ output.reasoning = reasoningInfo;
1324
+ }
1325
+ return output;
1326
+ }
953
1327
  async function searchRawMemorySemantically(input) {
954
1328
  const limit = clampUnifiedMemorySearchLimit(input.limit);
955
1329
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
956
1330
  const reasoningStrategy = resolveReasoningStrategy(input);
1331
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1332
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1333
+ );
957
1334
  const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
958
1335
  return mergeAcrossSources({
959
1336
  memorySubs: sub,
960
1337
  insightHits: [],
961
1338
  knowledgeHits: [],
962
1339
  limit,
963
- strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
1340
+ strategy: mergeStrategy
964
1341
  });
965
1342
  }
966
- return { searchUnifiedMemory, searchRawMemorySemantically };
1343
+ async function search(input) {
1344
+ const query = input.query.trim();
1345
+ const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
1346
+ const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
1347
+ const peerScope = await resolveSearchScopePeers({
1348
+ deps,
1349
+ userId: input.userId,
1350
+ peerFilter: input.peerFilter
1351
+ });
1352
+ if (!query) {
1353
+ const empty = {
1354
+ query,
1355
+ sources: input.sources ? [...input.sources] : [],
1356
+ results: [],
1357
+ evidence: [],
1358
+ count: 0,
1359
+ warnings: peerScope.warnings,
1360
+ answer: ""
1361
+ };
1362
+ return empty;
1363
+ }
1364
+ const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
1365
+ const wantsSummaryTier = tiers.includes("summary");
1366
+ const wantsRawTier = tiers.includes("raw");
1367
+ const wantsInsightTier = tiers.includes("insight");
1368
+ const wantsKnowledgeTier = tiers.includes("knowledge");
1369
+ const warnings = [...peerScope.warnings];
1370
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
1371
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
1372
+ let unifiedHits = [];
1373
+ let sources = input.sources ? [...input.sources] : [];
1374
+ let unifiedReasoning;
1375
+ if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
1376
+ const inferredSources = sources.length > 0 ? sources : [
1377
+ ...wantsRawTier ? ["memory"] : [],
1378
+ ...wantsInsightTier ? ["insights"] : [],
1379
+ ...wantsKnowledgeTier ? ["knowledge"] : []
1380
+ ];
1381
+ const unified = await searchUnifiedMemory({
1382
+ ...searchInputToUnified(input),
1383
+ sources: inferredSources.length > 0 ? inferredSources : void 0
1384
+ });
1385
+ unifiedHits = unified.results;
1386
+ sources = unified.sources;
1387
+ unifiedReasoning = unified.reasoning;
1388
+ warnings.push(...unified.warnings);
1389
+ }
1390
+ let summaryHits = [];
1391
+ if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
1392
+ const summaryBucket = await gatherSummaries(
1393
+ deps,
1394
+ { userId: input.userId, query: input.query, authToken: input.authToken },
1395
+ limit,
1396
+ threshold,
1397
+ logger,
1398
+ peerScope.peers
1399
+ );
1400
+ warnings.push(...summaryBucket.warnings);
1401
+ summaryHits = summaryBucket.hits;
1402
+ }
1403
+ const tierLists = [];
1404
+ if (unifiedHits.length > 0) {
1405
+ tierLists.push({ name: "memory", hits: unifiedHits });
1406
+ }
1407
+ if (summaryHits.length > 0) {
1408
+ tierLists.push({ name: "summary", hits: summaryHits });
1409
+ }
1410
+ let hits;
1411
+ if (tierLists.length === 0) {
1412
+ hits = [];
1413
+ } else if (tierLists.length === 1) {
1414
+ hits = tierLists[0].hits.slice(0, limit);
1415
+ } else {
1416
+ hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
1417
+ }
1418
+ const evidence = hits.map((hit) => ({
1419
+ id: hit.id,
1420
+ source: mapHitToEvidenceSource(hit),
1421
+ snippet: hit.content,
1422
+ score: hit.similarity,
1423
+ timestamp: getCandidateTimestamp(hit.metadata)
1424
+ }));
1425
+ const base = {
1426
+ query,
1427
+ sources,
1428
+ results: hits,
1429
+ evidence,
1430
+ count: hits.length,
1431
+ warnings
1432
+ };
1433
+ if (unifiedReasoning) {
1434
+ base.reasoning = unifiedReasoning;
1435
+ }
1436
+ if (!wantsSynthesis) {
1437
+ return base;
1438
+ }
1439
+ const { answer, warnings: synthWarnings } = await synthesizeAnswer({
1440
+ query,
1441
+ hits,
1442
+ responseSchema: synthesisSchema,
1443
+ deps,
1444
+ logger
1445
+ });
1446
+ base.answer = answer;
1447
+ base.warnings = [...base.warnings, ...synthWarnings];
1448
+ return base;
1449
+ }
1450
+ return {
1451
+ search,
1452
+ searchUnifiedMemory,
1453
+ searchRawMemorySemantically,
1454
+ consolidate: (input) => applyReflectedPlan(deps, {}, input)
1455
+ };
967
1456
  }
968
1457
  function mergeAcrossSources(input) {
969
1458
  const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
@@ -995,9 +1484,283 @@ function mergeAcrossSources(input) {
995
1484
  return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
996
1485
  }
997
1486
 
1487
+ // src/search/apply-reflect.ts
1488
+ var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
1489
+ function reflectTierToEvidenceTier(source) {
1490
+ switch (source) {
1491
+ case "raw":
1492
+ return "short";
1493
+ case "summary":
1494
+ return "long";
1495
+ default:
1496
+ return "mid";
1497
+ }
1498
+ }
1499
+ function toMemoryEvidenceRecord(input, fallbackUserId) {
1500
+ const tier = reflectTierToEvidenceTier(input.source);
1501
+ const timestamp = input.timestamp ?? Date.now();
1502
+ return {
1503
+ id: input.id,
1504
+ userId: fallbackUserId,
1505
+ timestamp,
1506
+ tier,
1507
+ text: input.snippet,
1508
+ metadata: {
1509
+ source: input.source,
1510
+ score: input.score,
1511
+ ...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
1512
+ }
1513
+ };
1514
+ }
1515
+ async function applyReflectedPlan(deps, store, input, _logger) {
1516
+ const query = input.query.trim();
1517
+ const warnings = [];
1518
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
1519
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
1520
+ const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS2;
1521
+ const searchInput = {
1522
+ userId: input.userId,
1523
+ query,
1524
+ botIds: input.botIds,
1525
+ dateFrom: input.dateFrom,
1526
+ dateTo: input.dateTo,
1527
+ tiers,
1528
+ limit,
1529
+ threshold,
1530
+ authToken: input.authToken,
1531
+ peerFilter: input.peerFilter,
1532
+ synthesize: true
1533
+ };
1534
+ const search = createUnifiedSearch(deps);
1535
+ const result = await search.search(searchInput);
1536
+ warnings.push(...result.warnings);
1537
+ const evidence = result.evidence.map((item) => ({
1538
+ id: item.id,
1539
+ source: item.source ?? "raw",
1540
+ snippet: item.snippet,
1541
+ score: item.score,
1542
+ timestamp: item.timestamp
1543
+ }));
1544
+ const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
1545
+ const applyInput = {
1546
+ userId: input.userId,
1547
+ ownerScope: input.ownerScope,
1548
+ records,
1549
+ plan: input.plan,
1550
+ dryRun: input.dryRun,
1551
+ expectedVersion: input.expectedVersion,
1552
+ getClusterKey: input.getClusterKey ?? defaultClusterKey,
1553
+ llmReview: deps.reasoning?.complete ? {
1554
+ complete: deps.reasoning.complete,
1555
+ maxTokens: input.llmPlanReview?.maxTokens
1556
+ } : void 0,
1557
+ graphStore: input.graphStore ?? store.graphStore,
1558
+ storage: input.storage ?? store.storage
1559
+ };
1560
+ const applyOutput = await applyReflectedConsolidationPlan(applyInput);
1561
+ for (const warning of applyOutput.warnings) {
1562
+ warnings.push({
1563
+ source: "memory",
1564
+ code: warning.code,
1565
+ message: warning.message
1566
+ });
1567
+ }
1568
+ const summaryAnswer = makePlanSummary(applyOutput.plan);
1569
+ return {
1570
+ answer: result.answer ? `${result.answer}
1571
+
1572
+ Plan summary:
1573
+ ${summaryAnswer}` : summaryAnswer,
1574
+ plan: applyOutput.plan,
1575
+ persistenceResult: applyOutput.persistenceResult,
1576
+ deprecationCounts: applyOutput.deprecationCounts,
1577
+ warnings,
1578
+ applied: applyOutput.applied,
1579
+ evidence
1580
+ };
1581
+ }
1582
+ function defaultClusterKey(record) {
1583
+ return record.text ?? record.id;
1584
+ }
1585
+ function makePlanSummary(plan) {
1586
+ const lines = [];
1587
+ const counts = {
1588
+ preserve: plan.actions.preserve.length,
1589
+ observe: plan.actions.observe.length,
1590
+ decay: plan.actions.decay.length,
1591
+ deprecate: plan.actions.deprecate.length
1592
+ };
1593
+ lines.push(
1594
+ `- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
1595
+ );
1596
+ for (const entry of plan.actions.deprecate.slice(0, 5)) {
1597
+ lines.push(
1598
+ ` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
1599
+ );
1600
+ }
1601
+ return lines.join("\n");
1602
+ }
1603
+ var DEFAULT_MAX_FACTS = 1e3;
1604
+ var LOW_CONFIDENCE_THRESHOLD = 0.1;
1605
+ function toHRR(data) {
1606
+ const arr = data instanceof Float32Array ? data : new Float32Array(data);
1607
+ return { dim: arr.length, data: arr };
1608
+ }
1609
+ function generateFactId() {
1610
+ const stamp = Date.now().toString(36);
1611
+ const rand = Math.random().toString(36).slice(2, 10);
1612
+ return `vsa-${stamp}-${rand}`;
1613
+ }
1614
+ function createVsaRecall(storage) {
1615
+ return {
1616
+ async storeFact(input) {
1617
+ const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
1618
+ const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
1619
+ vsaAssertSameDim(roleVector, fillerVector, "storeFact");
1620
+ const dim = input.dim ?? roleVector.length;
1621
+ if (roleVector.length !== dim) {
1622
+ throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
1623
+ }
1624
+ const now = Date.now();
1625
+ const fact = {
1626
+ factId: input.factId ?? generateFactId(),
1627
+ userId: input.userId,
1628
+ roleLabel: input.roleLabel,
1629
+ fillerLabel: input.fillerLabel,
1630
+ roleVector,
1631
+ fillerVector,
1632
+ dim,
1633
+ scopeTag: input.scopeTag ?? "default",
1634
+ botId: input.botId,
1635
+ createdAt: now
1636
+ };
1637
+ await storage.storeFact(fact);
1638
+ return { factId: fact.factId, createdAt: now };
1639
+ },
1640
+ async recall(input) {
1641
+ const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
1642
+ const scopeTag = input.scopeTag ?? "default";
1643
+ const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
1644
+ if (input.vocabulary.length === 0) {
1645
+ throw new Error("vsaRecall: vocabulary must contain at least one entry");
1646
+ }
1647
+ const facts = await storage.queryFacts({
1648
+ userId: input.userId,
1649
+ scopeTag,
1650
+ botId: input.botId,
1651
+ includeDeprecated: false,
1652
+ limit: maxFacts
1653
+ });
1654
+ const warnings = [];
1655
+ if (facts.length === 0) {
1656
+ warnings.push({
1657
+ code: "vsa_no_facts",
1658
+ message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
1659
+ });
1660
+ return {
1661
+ fillerLabel: "",
1662
+ score: 0,
1663
+ allScores: [],
1664
+ factCount: 0,
1665
+ warnings
1666
+ };
1667
+ }
1668
+ const refDim = facts[0].dim;
1669
+ const dimMatchedFacts = facts.filter((fact) => {
1670
+ if (fact.dim === refDim) return true;
1671
+ warnings.push({
1672
+ code: "vsa_dim_mismatch",
1673
+ message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
1674
+ });
1675
+ return false;
1676
+ });
1677
+ if (roleVector.length !== refDim) {
1678
+ throw new Error(
1679
+ `vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
1680
+ );
1681
+ }
1682
+ if (dimMatchedFacts.length === 0) {
1683
+ warnings.push({
1684
+ code: "vsa_no_facts",
1685
+ message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
1686
+ });
1687
+ return {
1688
+ fillerLabel: "",
1689
+ score: 0,
1690
+ allScores: [],
1691
+ factCount: 0,
1692
+ warnings
1693
+ };
1694
+ }
1695
+ const memoryVec = superpose(
1696
+ dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
1697
+ );
1698
+ const candidate = unbind(memoryVec, toHRR(roleVector));
1699
+ const vocabVecs = input.vocabulary.map(
1700
+ (entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
1701
+ );
1702
+ const bestVec = cleanup(candidate, vocabVecs);
1703
+ const bestIdx = vocabVecs.indexOf(bestVec);
1704
+ const allScores = input.vocabulary.map((entry, idx) => ({
1705
+ label: entry.label,
1706
+ score: cosineSimilarity(candidate, vocabVecs[idx])
1707
+ }));
1708
+ allScores.sort((a, b) => b.score - a.score);
1709
+ const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
1710
+ const score = allScores[0]?.score ?? 0;
1711
+ if (score < LOW_CONFIDENCE_THRESHOLD) {
1712
+ warnings.push({
1713
+ code: "vsa_low_confidence",
1714
+ message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
1715
+ });
1716
+ }
1717
+ if (facts.length >= maxFacts) {
1718
+ warnings.push({
1719
+ code: "vsa_fact_limit_reached",
1720
+ message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
1721
+ });
1722
+ }
1723
+ return {
1724
+ fillerLabel,
1725
+ score,
1726
+ allScores,
1727
+ factCount: facts.length,
1728
+ warnings
1729
+ };
1730
+ },
1731
+ async listFacts(input) {
1732
+ const facts = await storage.queryFacts({
1733
+ userId: input.userId,
1734
+ scopeTag: input.scopeTag,
1735
+ botId: input.botId,
1736
+ includeDeprecated: input.includeDeprecated
1737
+ });
1738
+ return facts.map((fact) => ({
1739
+ factId: fact.factId,
1740
+ userId: fact.userId,
1741
+ roleLabel: fact.roleLabel,
1742
+ fillerLabel: fact.fillerLabel,
1743
+ scopeTag: fact.scopeTag,
1744
+ botId: fact.botId,
1745
+ createdAt: fact.createdAt,
1746
+ deprecatedAt: fact.deprecatedAt
1747
+ }));
1748
+ },
1749
+ async forget(input) {
1750
+ const result = await storage.deprecateFacts({
1751
+ userId: input.userId,
1752
+ factIds: input.factIds,
1753
+ reason: input.reason
1754
+ });
1755
+ return { deprecatedCount: result.deprecatedCount };
1756
+ }
1757
+ };
1758
+ }
1759
+
998
1760
  // src/http.ts
999
1761
  init_chroma_memory_index();
1000
1762
  init_raw_message_store();
1763
+ init_sqlite_raw_message_store();
1001
1764
  async function embedMissingMessages(messages, deps) {
1002
1765
  if (typeof deps.embedQuery !== "function") {
1003
1766
  return messages;
@@ -1049,6 +1812,9 @@ async function startHttpServer(options = {}) {
1049
1812
  return [];
1050
1813
  }
1051
1814
  });
1815
+ const vsaDbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
1816
+ const vsaStorage = await getSQLiteVsaStore({ dbPath: vsaDbPath });
1817
+ const vsa = createVsaRecall(vsaStorage);
1052
1818
  const app = new Hono();
1053
1819
  app.get("/health", (c) => c.json({ ok: true, store: "memory", ts: Date.now() }));
1054
1820
  app.post("/v1/search", async (c) => {
@@ -1058,17 +1824,62 @@ async function startHttpServer(options = {}) {
1058
1824
  if (!userId || !query) {
1059
1825
  return c.json({ error: "userId and query are required" }, 400);
1060
1826
  }
1061
- const result = await search.searchUnifiedMemory({
1827
+ const wantsSynthesis = typeof body.synthesize === "boolean" ? body.synthesize : typeof body.synthesize === "object" && body.synthesize !== null;
1828
+ const responseSchema = typeof body.synthesize === "object" && body.synthesize !== null ? body.synthesize.responseSchema ?? body.responseSchema : body.responseSchema;
1829
+ const input = {
1062
1830
  userId,
1063
1831
  query,
1064
- sources: body.sources,
1065
- limit: body.limit,
1066
- threshold: body.threshold,
1067
- botIds: body.botIds,
1068
- documentIds: body.documentIds,
1069
- includeArchivedInsights: body.includeArchivedInsights,
1070
- authToken: body.authToken
1071
- });
1832
+ sources: Array.isArray(body.sources) ? body.sources : void 0,
1833
+ tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
1834
+ limit: typeof body.limit === "number" ? body.limit : void 0,
1835
+ threshold: typeof body.threshold === "number" ? body.threshold : void 0,
1836
+ botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
1837
+ documentIds: Array.isArray(body.documentIds) ? body.documentIds : void 0,
1838
+ includeArchivedInsights: body.includeArchivedInsights === true,
1839
+ authToken: typeof body.authToken === "string" ? body.authToken : void 0,
1840
+ dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
1841
+ dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
1842
+ ...wantsSynthesis ? {
1843
+ synthesize: {
1844
+ ...responseSchema ? { responseSchema } : {}
1845
+ }
1846
+ } : {}
1847
+ };
1848
+ const result = await search.search(input);
1849
+ return c.json(result);
1850
+ });
1851
+ app.post("/v1/consolidate:apply", async (c) => {
1852
+ const body = await c.req.json().catch(() => ({}));
1853
+ const userId = typeof body.userId === "string" ? body.userId : null;
1854
+ const query = typeof body.query === "string" ? body.query : "";
1855
+ const ownerScope = body.ownerScope ?? {};
1856
+ if (!userId || !query) {
1857
+ return c.json({ error: "userId and query are required" }, 400);
1858
+ }
1859
+ if (typeof ownerScope.userId !== "string") {
1860
+ return c.json({ error: "ownerScope.userId is required" }, 400);
1861
+ }
1862
+ const input = {
1863
+ userId,
1864
+ query,
1865
+ ownerScope: { userId: ownerScope.userId },
1866
+ botIds: Array.isArray(body.botIds) ? body.botIds : void 0,
1867
+ dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
1868
+ dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
1869
+ tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
1870
+ limit: typeof body.limit === "number" ? body.limit : void 0,
1871
+ threshold: typeof body.threshold === "number" ? body.threshold : void 0,
1872
+ dryRun: body.dryRun === true,
1873
+ expectedVersion: typeof body.expectedVersion === "string" ? body.expectedVersion : void 0,
1874
+ authToken: typeof body.authToken === "string" ? body.authToken : void 0,
1875
+ llmPlanReview: body.llmPlanReview && typeof body.llmPlanReview === "object" ? {
1876
+ maxTokens: typeof body.llmPlanReview.maxTokens === "number" ? body.llmPlanReview.maxTokens : void 0
1877
+ } : void 0
1878
+ };
1879
+ const result = await applyReflectedPlan(
1880
+ { ...options.unified ?? {} },
1881
+ { graphStore: options.graphStore, storage: options.storage },
1882
+ input);
1072
1883
  return c.json(result);
1073
1884
  });
1074
1885
  app.post("/v1/raw-messages", async (c) => {
@@ -1134,12 +1945,98 @@ async function startHttpServer(options = {}) {
1134
1945
  if (!row) return c.json({ error: "not found" }, 404);
1135
1946
  return c.json({ message: row });
1136
1947
  });
1948
+ app.post("/v1/vsa/store", async (c) => {
1949
+ const body = await c.req.json().catch(() => ({}));
1950
+ const userId = typeof body.userId === "string" ? body.userId : null;
1951
+ const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
1952
+ const fillerLabel = typeof body.fillerLabel === "string" ? body.fillerLabel : null;
1953
+ if (!userId || !roleLabel || !fillerLabel) {
1954
+ return c.json({ error: "userId, roleLabel, and fillerLabel are required" }, 400);
1955
+ }
1956
+ if (!Array.isArray(body.roleVector) || !Array.isArray(body.fillerVector)) {
1957
+ return c.json({ error: "roleVector[] and fillerVector[] are required" }, 400);
1958
+ }
1959
+ try {
1960
+ const result = await vsa.storeFact({
1961
+ userId,
1962
+ roleLabel,
1963
+ fillerLabel,
1964
+ roleVector: body.roleVector,
1965
+ fillerVector: body.fillerVector,
1966
+ scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
1967
+ botId: typeof body.botId === "string" ? body.botId : void 0,
1968
+ factId: typeof body.factId === "string" ? body.factId : void 0
1969
+ });
1970
+ return c.json(result);
1971
+ } catch (error) {
1972
+ return c.json({ error: error.message ?? "vsa.store failed" }, 400);
1973
+ }
1974
+ });
1975
+ app.post("/v1/vsa/recall", async (c) => {
1976
+ const body = await c.req.json().catch(() => ({}));
1977
+ const userId = typeof body.userId === "string" ? body.userId : null;
1978
+ const roleLabel = typeof body.roleLabel === "string" ? body.roleLabel : null;
1979
+ const roleVector = Array.isArray(body.roleVector) ? body.roleVector : null;
1980
+ const vocabulary = Array.isArray(body.vocabulary) ? body.vocabulary : null;
1981
+ if (!userId || !roleLabel || !roleVector || !vocabulary) {
1982
+ return c.json({ error: "userId, roleLabel, roleVector[], and vocabulary[] are required" }, 400);
1983
+ }
1984
+ try {
1985
+ const result = await vsa.recall({
1986
+ userId,
1987
+ roleLabel,
1988
+ roleVector,
1989
+ vocabulary,
1990
+ scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
1991
+ botId: typeof body.botId === "string" ? body.botId : void 0,
1992
+ maxFacts: typeof body.maxFacts === "number" ? body.maxFacts : void 0
1993
+ });
1994
+ return c.json(result);
1995
+ } catch (error) {
1996
+ return c.json({ error: error.message ?? "vsa.recall failed" }, 400);
1997
+ }
1998
+ });
1999
+ app.post("/v1/vsa/list", async (c) => {
2000
+ const body = await c.req.json().catch(() => ({}));
2001
+ const userId = typeof body.userId === "string" ? body.userId : null;
2002
+ if (!userId) return c.json({ error: "userId is required" }, 400);
2003
+ try {
2004
+ const facts = await vsa.listFacts({
2005
+ userId,
2006
+ scopeTag: typeof body.scopeTag === "string" ? body.scopeTag : void 0,
2007
+ botId: typeof body.botId === "string" ? body.botId : void 0,
2008
+ includeDeprecated: body.includeDeprecated === true
2009
+ });
2010
+ return c.json({ facts });
2011
+ } catch (error) {
2012
+ return c.json({ error: error.message ?? "vsa.list failed" }, 400);
2013
+ }
2014
+ });
2015
+ app.post("/v1/vsa/forget", async (c) => {
2016
+ const body = await c.req.json().catch(() => ({}));
2017
+ const userId = typeof body.userId === "string" ? body.userId : null;
2018
+ const factIds = Array.isArray(body.factIds) ? body.factIds : null;
2019
+ if (!userId || !factIds) {
2020
+ return c.json({ error: "userId and factIds[] are required" }, 400);
2021
+ }
2022
+ try {
2023
+ const result = await vsa.forget({
2024
+ userId,
2025
+ factIds,
2026
+ reason: typeof body.reason === "string" ? body.reason : void 0
2027
+ });
2028
+ return c.json(result);
2029
+ } catch (error) {
2030
+ return c.json({ error: error.message ?? "vsa.forget failed" }, 400);
2031
+ }
2032
+ });
1137
2033
  const server = serve({ fetch: app.fetch, port, hostname: host });
1138
2034
  return {
1139
2035
  url: `http://${host}:${port}`,
1140
2036
  port,
1141
2037
  async stop() {
1142
2038
  await rawStore.close();
2039
+ await closeSQLiteVsaStore();
1143
2040
  server.close();
1144
2041
  }
1145
2042
  };