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