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