@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.
package/dist/index.js CHANGED
@@ -1,9 +1,14 @@
1
+ import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
1
2
  import '@melandlabs/ai/memory';
2
3
  import { ChromaVectorStore } from '@melandlabs/rag';
3
4
  import { mkdirSync } from 'fs';
4
- import { homedir } from 'os';
5
- import { dirname, join } from 'path';
6
- import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
5
+ import { dirname } from 'path';
6
+ import { getOpenContextPath } from '@melandlabs/env-config';
7
+ import { SQLiteRawMessageManager, getSQLiteVsaStore } from '@melandlabs/sqlite';
8
+ export { closeSQLiteVsaStore, getSQLiteVsaStore } from '@melandlabs/sqlite';
9
+ import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
10
+ import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
11
+ import { createRawMessageMemoryGraphStore } from '@melandlabs/indexeddb';
7
12
 
8
13
  var __defProp = Object.defineProperty;
9
14
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -14,8 +19,6 @@ var __export = (target, all) => {
14
19
  for (var name in all)
15
20
  __defProp(target, name, { get: all[name], enumerable: true });
16
21
  };
17
-
18
- // src/contracts.ts
19
22
  var init_contracts = __esm({
20
23
  "src/contracts.ts"() {
21
24
  }
@@ -117,7 +120,7 @@ __export(sqlite_raw_message_store_exports, {
117
120
  function resolveSQLiteRawMessageDbPath(dbPath) {
118
121
  if (dbPath && dbPath.length > 0) return dbPath;
119
122
  const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
120
- return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
123
+ return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
121
124
  }
122
125
  function isSQLiteRawMessageStorageAvailable(_env) {
123
126
  return true;
@@ -151,12 +154,11 @@ async function lexicalSearchRawMessages(input) {
151
154
  }
152
155
  return mgr.lexicalSearchMessages(input);
153
156
  }
154
- var manager, DEFAULT_SQLITE_PATH;
157
+ var manager;
155
158
  var init_sqlite_raw_message_store = __esm({
156
159
  "src/storage/sqlite-raw-message-store.ts"() {
157
160
  init_chroma_memory_index();
158
161
  manager = null;
159
- DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
160
162
  }
161
163
  });
162
164
 
@@ -377,6 +379,416 @@ function isRawMemorySemanticResult(result) {
377
379
  const item = result;
378
380
  return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
379
381
  }
382
+ async function resolveScopePeer(input) {
383
+ const peers = input.peerFilter ?? [];
384
+ if (peers.length === 0) {
385
+ return { peers: [], warnings: [] };
386
+ }
387
+ if (typeof input.scopeCheck !== "function") {
388
+ return { peers, warnings: [] };
389
+ }
390
+ const retained = [];
391
+ const dropped = [];
392
+ for (const peer of peers) {
393
+ const ok = await input.scopeCheck({ userId: input.userId, peers: [peer] });
394
+ if (ok) {
395
+ retained.push(peer);
396
+ } else {
397
+ dropped.push(peer);
398
+ }
399
+ }
400
+ if (dropped.length === 0) {
401
+ return { peers, warnings: [] };
402
+ }
403
+ if (retained.length === 0) {
404
+ return {
405
+ peers: [],
406
+ warnings: [
407
+ {
408
+ source: "memory",
409
+ code: "peer_filter_outside_user_scope",
410
+ message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored; falling back to userId scope`
411
+ }
412
+ ]
413
+ };
414
+ }
415
+ return {
416
+ peers: retained,
417
+ warnings: [
418
+ {
419
+ source: "memory",
420
+ code: "peer_filter_outside_user_scope",
421
+ message: `${dropped.map((peer) => `${peer.kind}:${peer.id}`).join(",")} ignored because they fall outside userId ${input.userId}`
422
+ }
423
+ ]
424
+ };
425
+ }
426
+
427
+ // src/search/apply-reflect.ts
428
+ var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
429
+ function reflectTierToEvidenceTier(source) {
430
+ switch (source) {
431
+ case "raw":
432
+ return "short";
433
+ case "summary":
434
+ return "long";
435
+ default:
436
+ return "mid";
437
+ }
438
+ }
439
+ function toMemoryEvidenceRecord(input, fallbackUserId) {
440
+ const tier = reflectTierToEvidenceTier(input.source);
441
+ const timestamp = input.timestamp ?? Date.now();
442
+ return {
443
+ id: input.id,
444
+ userId: fallbackUserId,
445
+ timestamp,
446
+ tier,
447
+ text: input.snippet,
448
+ metadata: {
449
+ source: input.source,
450
+ score: input.score,
451
+ ...input.timestamp !== void 0 ? { originalTimestamp: input.timestamp } : {}
452
+ }
453
+ };
454
+ }
455
+ async function applyReflectedPlan(deps, store, input, _logger) {
456
+ const query = input.query.trim();
457
+ const warnings = [];
458
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
459
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
460
+ const tiers = input.tiers && input.tiers.length > 0 ? input.tiers : DEFAULT_TIERS;
461
+ const searchInput = {
462
+ userId: input.userId,
463
+ query,
464
+ botIds: input.botIds,
465
+ dateFrom: input.dateFrom,
466
+ dateTo: input.dateTo,
467
+ tiers,
468
+ limit,
469
+ threshold,
470
+ authToken: input.authToken,
471
+ peerFilter: input.peerFilter,
472
+ synthesize: true
473
+ };
474
+ const search = createUnifiedSearch(deps);
475
+ const result = await search.search(searchInput);
476
+ warnings.push(...result.warnings);
477
+ const evidence = result.evidence.map((item) => ({
478
+ id: item.id,
479
+ source: item.source ?? "raw",
480
+ snippet: item.snippet,
481
+ score: item.score,
482
+ timestamp: item.timestamp
483
+ }));
484
+ const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
485
+ const applyInput = {
486
+ userId: input.userId,
487
+ ownerScope: input.ownerScope,
488
+ records,
489
+ plan: input.plan,
490
+ dryRun: input.dryRun,
491
+ expectedVersion: input.expectedVersion,
492
+ getClusterKey: input.getClusterKey ?? defaultClusterKey,
493
+ llmReview: deps.reasoning?.complete ? {
494
+ complete: deps.reasoning.complete,
495
+ maxTokens: input.llmPlanReview?.maxTokens
496
+ } : void 0,
497
+ graphStore: input.graphStore ?? store.graphStore,
498
+ storage: input.storage ?? store.storage
499
+ };
500
+ const applyOutput = await applyReflectedConsolidationPlan(applyInput);
501
+ for (const warning of applyOutput.warnings) {
502
+ warnings.push({
503
+ source: "memory",
504
+ code: warning.code,
505
+ message: warning.message
506
+ });
507
+ }
508
+ const summaryAnswer = makePlanSummary(applyOutput.plan);
509
+ return {
510
+ answer: result.answer ? `${result.answer}
511
+
512
+ Plan summary:
513
+ ${summaryAnswer}` : summaryAnswer,
514
+ plan: applyOutput.plan,
515
+ persistenceResult: applyOutput.persistenceResult,
516
+ deprecationCounts: applyOutput.deprecationCounts,
517
+ warnings,
518
+ applied: applyOutput.applied,
519
+ evidence
520
+ };
521
+ }
522
+ function defaultClusterKey(record) {
523
+ return record.text ?? record.id;
524
+ }
525
+ function makePlanSummary(plan) {
526
+ const lines = [];
527
+ const counts = {
528
+ preserve: plan.actions.preserve.length,
529
+ observe: plan.actions.observe.length,
530
+ decay: plan.actions.decay.length,
531
+ deprecate: plan.actions.deprecate.length
532
+ };
533
+ lines.push(
534
+ `- preserve=${counts.preserve} observe=${counts.observe} decay=${counts.decay} deprecate=${counts.deprecate}`
535
+ );
536
+ for (const entry of plan.actions.deprecate.slice(0, 5)) {
537
+ lines.push(
538
+ ` deprecate ${entry.recordIds.length} record(s) from cluster ${entry.clusterKey} -> ${entry.supersededBySummaryId ?? entry.winningClusterKey}`
539
+ );
540
+ }
541
+ return lines.join("\n");
542
+ }
543
+
544
+ // src/search/reranker.ts
545
+ async function applyReranker(reranker, query, results) {
546
+ if (!reranker || results.length === 0) {
547
+ return results;
548
+ }
549
+ const scores = await reranker.rerank({
550
+ query,
551
+ candidates: results.map((result) => ({
552
+ id: result.id,
553
+ content: result.content,
554
+ metadata: result.metadata
555
+ }))
556
+ });
557
+ if (scores.length === 0) {
558
+ return results;
559
+ }
560
+ const byId = new Map(results.map((result) => [result.id, result]));
561
+ const reordered = [];
562
+ const seen = /* @__PURE__ */ new Set();
563
+ for (const { id, score } of scores) {
564
+ const hit = byId.get(id);
565
+ if (!hit || seen.has(id)) {
566
+ continue;
567
+ }
568
+ seen.add(id);
569
+ reordered.push({ ...hit, metadata: { ...hit.metadata, rerankerScore: score } });
570
+ }
571
+ for (const hit of results) {
572
+ if (!seen.has(hit.id)) {
573
+ reordered.push(hit);
574
+ }
575
+ }
576
+ return reordered;
577
+ }
578
+
579
+ // src/search/gather-evidence.ts
580
+ var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
581
+ var MAX_EVIDENCE_CHARS = 600;
582
+ function toSummaryResult(row) {
583
+ const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
584
+ const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
585
+ return {
586
+ type: "memory",
587
+ id: row.summaryId,
588
+ content: row.summaryText,
589
+ similarity: 1,
590
+ metadata: {
591
+ tier: "summary",
592
+ summaryTier: row.summaryTier,
593
+ keywords: row.keywords,
594
+ startTimestamp: start,
595
+ endTimestamp: end
596
+ }
597
+ };
598
+ }
599
+ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
600
+ if (typeof deps.searchSummaries !== "function") {
601
+ return { hits: [], warnings: [] };
602
+ }
603
+ const keywords = input.query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
604
+ try {
605
+ const rows = await deps.searchSummaries({
606
+ userId: input.userId,
607
+ query: input.query,
608
+ keywords,
609
+ limit,
610
+ threshold,
611
+ authToken: input.authToken,
612
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
613
+ });
614
+ const hits = rows.map(toSummaryResult);
615
+ return { hits, warnings: [] };
616
+ } catch (error) {
617
+ logger.warn?.("[memory-store] gather-evidence: summary search failed:", error);
618
+ return {
619
+ hits: [],
620
+ warnings: [
621
+ {
622
+ source: "memory",
623
+ code: "reflect_summaries_failed",
624
+ message: error.message ?? "reflect_summaries_failed"
625
+ }
626
+ ]
627
+ };
628
+ }
629
+ }
630
+ function truncate(input, max = MAX_EVIDENCE_CHARS) {
631
+ if (input.length <= max) {
632
+ return input;
633
+ }
634
+ return `${input.slice(0, max - 1)}\u2026`;
635
+ }
636
+ function buildSynthesisPrompt(input) {
637
+ const grouped = /* @__PURE__ */ new Map();
638
+ for (const item of input.evidence) {
639
+ const bucket = grouped.get(item.source) ?? [];
640
+ bucket.push(item);
641
+ grouped.set(item.source, bucket);
642
+ }
643
+ const sections = [];
644
+ for (const tier of DEFAULT_TIERS2) {
645
+ const items = grouped.get(tier) ?? [];
646
+ if (items.length === 0) {
647
+ continue;
648
+ }
649
+ const lines = items.map(
650
+ (item, index) => ` [${index + 1}] (${item.id}, score=${item.score.toFixed(4)}) ${item.snippet}`
651
+ );
652
+ sections.push(`## ${tier}
653
+ ${lines.join("\n")}`);
654
+ }
655
+ const schemaDirective = input.responseSchema ? `
656
+ Respond with a JSON object that conforms to the following schema:
657
+ ${JSON.stringify(input.responseSchema, null, 2)}
658
+ 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.";
659
+ if (sections.length === 0) {
660
+ return `You are a reflection assistant.
661
+
662
+ Question: ${input.query}
663
+
664
+ No evidence was found across any tier. State that explicitly.${schemaDirective}`;
665
+ }
666
+ return `You are a reflection assistant. Synthesise the user's question using only the evidence below. Do not invent facts.
667
+
668
+ Question: ${input.query}
669
+
670
+ Evidence (grouped by tier):
671
+ ${sections.join("\n\n")}${schemaDirective}`;
672
+ }
673
+ function extractJsonPayload(text) {
674
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
675
+ if (fenced?.[1]) {
676
+ return fenced[1];
677
+ }
678
+ const start = text.indexOf("{");
679
+ if (start === -1) {
680
+ return void 0;
681
+ }
682
+ let depth = 0;
683
+ let inString = false;
684
+ let escaped = false;
685
+ for (let i = start; i < text.length; i += 1) {
686
+ const ch = text[i];
687
+ if (escaped) {
688
+ escaped = false;
689
+ continue;
690
+ }
691
+ if (inString) {
692
+ if (ch === "\\") {
693
+ escaped = true;
694
+ } else if (ch === '"') {
695
+ inString = false;
696
+ }
697
+ continue;
698
+ }
699
+ if (ch === '"') {
700
+ inString = true;
701
+ } else if (ch === "{") {
702
+ depth += 1;
703
+ } else if (ch === "}") {
704
+ depth -= 1;
705
+ if (depth === 0) {
706
+ return text.slice(start, i + 1);
707
+ }
708
+ }
709
+ }
710
+ return void 0;
711
+ }
712
+ function coerceAnswer(text, schema) {
713
+ if (!schema) {
714
+ return text;
715
+ }
716
+ const payload = extractJsonPayload(text);
717
+ if (!payload) {
718
+ return text;
719
+ }
720
+ try {
721
+ const parsed = JSON.parse(payload);
722
+ if (parsed && typeof parsed === "object") {
723
+ const answer = parsed.answer;
724
+ if (typeof answer === "string") {
725
+ return answer;
726
+ }
727
+ }
728
+ } catch {
729
+ }
730
+ return text;
731
+ }
732
+ async function synthesizeAnswer(input) {
733
+ const { query, hits, responseSchema, deps, logger } = input;
734
+ const evidence = hits.map((hit) => ({
735
+ id: hit.id,
736
+ source: mapHitToTier(hit),
737
+ snippet: truncate(hit.content),
738
+ score: hit.similarity
739
+ }));
740
+ const warnings = [];
741
+ const complete = deps.reasoning?.complete;
742
+ if (!complete) {
743
+ warnings.push({
744
+ source: "memory",
745
+ code: "reflect_llm_not_configured",
746
+ message: "No `reasoning.complete` callback is configured; search() returned evidence without an LLM synthesis."
747
+ });
748
+ return { answer: "", warnings };
749
+ }
750
+ const prompt = buildSynthesisPrompt({ query, evidence, responseSchema });
751
+ try {
752
+ const raw = await complete(prompt);
753
+ const answer = coerceAnswer(raw, responseSchema);
754
+ logger.log?.("[memory-store] search synthesis completed", {
755
+ evidenceCount: evidence.length,
756
+ answerChars: answer.length
757
+ });
758
+ return { answer, warnings };
759
+ } catch (error) {
760
+ logger.warn?.("[memory-store] search synthesis failed:", error);
761
+ warnings.push({
762
+ source: "memory",
763
+ code: "reflect_llm_failed",
764
+ message: error.message ?? "reflect_llm_failed"
765
+ });
766
+ return { answer: "", warnings };
767
+ }
768
+ }
769
+ function mapHitToTier(hit) {
770
+ const tierMarker = hit.metadata?.tier;
771
+ if (tierMarker === "summary") {
772
+ return "summary";
773
+ }
774
+ if (hit.type === "memory") {
775
+ return "raw";
776
+ }
777
+ if (hit.type === "insight") {
778
+ return "insight";
779
+ }
780
+ if (hit.type === "knowledge") {
781
+ return "knowledge";
782
+ }
783
+ return hit.type;
784
+ }
785
+ async function resolveSearchScopePeers(input) {
786
+ return resolveScopePeer({
787
+ userId: input.userId,
788
+ peerFilter: input.peerFilter,
789
+ scopeCheck: input.deps.peerScopeCheck
790
+ });
791
+ }
380
792
 
381
793
  // src/search/unified-search.ts
382
794
  function deriveLexicalKeywords(query) {
@@ -448,7 +860,7 @@ async function embedQueryVariant(embedQuery, input, query) {
448
860
  authToken: input.authToken
449
861
  });
450
862
  }
451
- async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
863
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
452
864
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
453
865
  let semantic = [];
454
866
  if (isRawMessageChromaEnabled()) {
@@ -478,6 +890,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
478
890
  }
479
891
  } else if (typeof deps.searchRawMessagesAnn === "function") {
480
892
  const searchRawMessagesAnn = deps.searchRawMessagesAnn;
893
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
481
894
  semantic = (await Promise.all(
482
895
  filters.map(
483
896
  (filter) => searchRawMessagesAnn({
@@ -485,7 +898,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
485
898
  queryEmbedding,
486
899
  limit,
487
900
  threshold,
488
- botId: "botId" in filter ? filter.botId : void 0
901
+ botId: "botId" in filter ? filter.botId : void 0,
902
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
903
+ ...factTypes ? { factTypes } : {}
489
904
  })
490
905
  )
491
906
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
@@ -495,11 +910,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
495
910
  const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
496
911
  const manager2 = await getRawMessageManager2();
497
912
  if (typeof manager2.searchMessagesSemantically === "function") {
913
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
498
914
  const results = await manager2.searchMessagesSemantically({
499
915
  userId: input.userId,
500
916
  queryEmbedding,
501
917
  limit,
502
- threshold
918
+ threshold,
919
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
920
+ ...factTypes ? { factTypes } : {}
503
921
  });
504
922
  semantic = results.map((r) => ({
505
923
  type: "memory",
@@ -535,7 +953,7 @@ function toIterativeRecallCandidate(hit) {
535
953
  metadata: hit.metadata
536
954
  };
537
955
  }
538
- async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
956
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
539
957
  if (keywords.length === 0) {
540
958
  return [];
541
959
  }
@@ -543,13 +961,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
543
961
  try {
544
962
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
545
963
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
964
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
546
965
  return (await Promise.all(
547
966
  filters.map(
548
967
  (botId) => searchRawMessagesLexical({
549
968
  userId: input.userId,
550
969
  keywords,
551
970
  limit: Math.ceil(limit / filters.length),
552
- botId
971
+ botId,
972
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
973
+ ...factTypes ? { factTypes } : {}
553
974
  })
554
975
  )
555
976
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -563,13 +984,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
563
984
  try {
564
985
  const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
565
986
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
987
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
566
988
  return (await Promise.all(
567
989
  filters.map(
568
990
  (botId) => lexicalSearchRawMessages2({
569
991
  userId: input.userId,
570
992
  keywords,
571
993
  limit: Math.ceil(limit / filters.length),
572
- botId
994
+ botId,
995
+ ...factTypes ? { factTypes } : {}
573
996
  })
574
997
  )
575
998
  )).flat().filter(Boolean).map(
@@ -582,6 +1005,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
582
1005
  }
583
1006
  return [];
584
1007
  }
1008
+ function searchInputToUnified(input) {
1009
+ return {
1010
+ userId: input.userId,
1011
+ query: input.query,
1012
+ sources: input.sources ? [...input.sources] : void 0,
1013
+ limit: input.limit,
1014
+ threshold: input.threshold,
1015
+ authToken: input.authToken,
1016
+ includeArchivedInsights: input.includeArchivedInsights,
1017
+ botIds: input.botIds,
1018
+ documentIds: input.documentIds,
1019
+ asOf: input.asOf,
1020
+ dateFrom: input.dateFrom,
1021
+ dateTo: input.dateTo,
1022
+ mergeStrategy: input.mergeStrategy,
1023
+ peerFilter: input.peerFilter,
1024
+ reasoningStrategy: input.reasoningStrategy,
1025
+ factTypes: input.factTypes
1026
+ };
1027
+ }
1028
+ function mapHitToEvidenceSource(hit) {
1029
+ const tierMarker = hit.metadata?.tier;
1030
+ if (tierMarker === "summary") {
1031
+ return "summary";
1032
+ }
1033
+ if (hit.type === "memory") {
1034
+ return "raw";
1035
+ }
1036
+ if (hit.type === "insight") {
1037
+ return "insight";
1038
+ }
1039
+ if (hit.type === "knowledge") {
1040
+ return "knowledge";
1041
+ }
1042
+ return hit.type;
1043
+ }
585
1044
  function createUnifiedSearch(deps = {}) {
586
1045
  const logger = deps.logger ?? console;
587
1046
  function resolveReasoningStrategy(input) {
@@ -591,130 +1050,7 @@ function createUnifiedSearch(deps = {}) {
591
1050
  }
592
1051
  return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
593
1052
  }
594
- async function searchUnifiedMemory(input) {
595
- const query = input.query.trim();
596
- const sources = normalizeUnifiedMemorySearchSources(input.sources);
597
- const limit = clampUnifiedMemorySearchLimit(input.limit);
598
- const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
599
- const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
600
- const reasoningStrategy = resolveReasoningStrategy(input);
601
- const warnings = [];
602
- if (!query) {
603
- return {
604
- query,
605
- sources,
606
- results: [],
607
- count: 0,
608
- warnings
609
- };
610
- }
611
- let memorySubs;
612
- const reasoningInfo = { strategy: reasoningStrategy };
613
- if (input.dateFrom || input.dateTo) {
614
- reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
615
- }
616
- if (sources.includes("memory")) {
617
- if (isRawMessageStorageAvailable()) {
618
- try {
619
- memorySubs = await runMemorySource(
620
- input,
621
- limit,
622
- threshold,
623
- warnings,
624
- reasoningStrategy,
625
- reasoningInfo
626
- );
627
- } catch (error) {
628
- logger.warn?.("[memory-store] memory source failed:", error);
629
- warnings.push({
630
- source: "memory",
631
- code: "memory_search_failed",
632
- message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
633
- });
634
- }
635
- } else {
636
- warnings.push({
637
- source: "memory",
638
- code: "raw_message_storage_unavailable",
639
- message: "Raw memory storage is not available in this environment."
640
- });
641
- }
642
- }
643
- const insightHits = [];
644
- if (sources.includes("insights") && typeof deps.searchInsights === "function") {
645
- try {
646
- const hits = await deps.searchInsights({
647
- userId: input.userId,
648
- query: input.query,
649
- limit,
650
- threshold,
651
- botIds: input.botIds,
652
- includeArchived: input.includeArchivedInsights,
653
- authToken: input.authToken
654
- });
655
- for (const hit of hits) {
656
- insightHits.push({
657
- type: "insight",
658
- id: hit.id,
659
- content: hit.content,
660
- similarity: hit.similarity,
661
- metadata: hit.metadata
662
- });
663
- }
664
- } catch (error) {
665
- logger.warn?.("[memory-store] insights source failed:", error);
666
- warnings.push({
667
- source: "insights",
668
- code: "insights_search_failed",
669
- message: error.message ?? "insights_search_failed"
670
- });
671
- }
672
- } else if (sources.includes("insights")) ;
673
- const knowledgeHits = [];
674
- if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
675
- try {
676
- const hits = await deps.searchKnowledge({
677
- userId: input.userId,
678
- query: input.query,
679
- options: {
680
- limit,
681
- threshold,
682
- documentIds: input.documentIds
683
- },
684
- authToken: input.authToken
685
- });
686
- for (const hit of hits) {
687
- knowledgeHits.push(toKnowledgeResult(hit));
688
- }
689
- } catch (error) {
690
- logger.warn?.("[memory-store] knowledge source failed:", error);
691
- warnings.push({
692
- source: "knowledge",
693
- code: "knowledge_search_failed",
694
- message: error.message ?? "knowledge_search_failed"
695
- });
696
- }
697
- } else if (sources.includes("knowledge")) ;
698
- const merged = mergeAcrossSources({
699
- memorySubs,
700
- insightHits,
701
- knowledgeHits,
702
- limit,
703
- strategy: mergeStrategy
704
- });
705
- const output = {
706
- query: input.query,
707
- sources,
708
- results: merged,
709
- count: merged.length,
710
- warnings
711
- };
712
- if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
713
- output.reasoning = reasoningInfo;
714
- }
715
- return output;
716
- }
717
- async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
1053
+ async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
718
1054
  if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
719
1055
  warnings.push({
720
1056
  source: "memory",
@@ -740,7 +1076,14 @@ function createUnifiedSearch(deps = {}) {
740
1076
  executor: {
741
1077
  search: async (request) => {
742
1078
  const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
743
- const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
1079
+ const lexicalHits = await runLexicalSearchForKeywords(
1080
+ deps,
1081
+ input,
1082
+ keywords2,
1083
+ limit,
1084
+ logger,
1085
+ peerPeers
1086
+ );
744
1087
  let semanticHits = [];
745
1088
  if (typeof deps.embedQuery === "function") {
746
1089
  try {
@@ -752,7 +1095,8 @@ function createUnifiedSearch(deps = {}) {
752
1095
  queryEmbedding,
753
1096
  limit,
754
1097
  threshold,
755
- logger
1098
+ logger,
1099
+ peerPeers
756
1100
  );
757
1101
  } catch (error) {
758
1102
  logger.warn?.(
@@ -797,7 +1141,7 @@ function createUnifiedSearch(deps = {}) {
797
1141
  });
798
1142
  const keywords2 = deriveLexicalKeywords(input.query);
799
1143
  const lexical2 = filterByDateRange(
800
- await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
1144
+ await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
801
1145
  input.dateFrom,
802
1146
  input.dateTo
803
1147
  );
@@ -823,7 +1167,7 @@ function createUnifiedSearch(deps = {}) {
823
1167
  );
824
1168
  const lists = await Promise.all(
825
1169
  embeddings.map(
826
- (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
1170
+ (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
827
1171
  )
828
1172
  );
829
1173
  semantic = mergeByMaxScore(lists);
@@ -839,7 +1183,15 @@ function createUnifiedSearch(deps = {}) {
839
1183
  }
840
1184
  if (semantic.length === 0) {
841
1185
  const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
842
- semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
1186
+ semantic = await runSemanticSearchForEmbedding(
1187
+ deps,
1188
+ input,
1189
+ queryEmbedding,
1190
+ limit,
1191
+ threshold,
1192
+ logger,
1193
+ peerPeers
1194
+ );
843
1195
  }
844
1196
  let lexical = [];
845
1197
  const keywords = deriveLexicalKeywords(input.query);
@@ -848,13 +1200,16 @@ function createUnifiedSearch(deps = {}) {
848
1200
  try {
849
1201
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
850
1202
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
1203
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
851
1204
  lexical = (await Promise.all(
852
1205
  lexFilters.map(
853
1206
  (botId) => searchRawMessagesLexical({
854
1207
  userId: input.userId,
855
1208
  keywords,
856
1209
  limit,
857
- botId
1210
+ botId,
1211
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
1212
+ ...factTypes ? { factTypes } : {}
858
1213
  })
859
1214
  )
860
1215
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -882,20 +1237,272 @@ function createUnifiedSearch(deps = {}) {
882
1237
  lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
883
1238
  };
884
1239
  }
1240
+ async function searchUnifiedMemory(input) {
1241
+ const query = input.query.trim();
1242
+ const sources = normalizeUnifiedMemorySearchSources(input.sources);
1243
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
1244
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
1245
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1246
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1247
+ );
1248
+ const reasoningStrategy = resolveReasoningStrategy(input);
1249
+ const warnings = [];
1250
+ const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
1251
+ userId: input.userId,
1252
+ peerFilter: input.peerFilter,
1253
+ scopeCheck: deps.peerScopeCheck
1254
+ }) : { peers: [], warnings: [] };
1255
+ warnings.push(...peerScope.warnings);
1256
+ const peerPeers = peerScope.peers;
1257
+ if (!query) {
1258
+ return {
1259
+ query,
1260
+ sources,
1261
+ results: [],
1262
+ count: 0,
1263
+ warnings
1264
+ };
1265
+ }
1266
+ let memorySubs;
1267
+ const reasoningInfo = { strategy: reasoningStrategy };
1268
+ if (input.dateFrom || input.dateTo) {
1269
+ reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
1270
+ }
1271
+ if (sources.includes("memory")) {
1272
+ const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
1273
+ if (isRawMessageStorageAvailable() || hasRawProviders) {
1274
+ try {
1275
+ memorySubs = await runMemorySource(
1276
+ input,
1277
+ limit,
1278
+ threshold,
1279
+ warnings,
1280
+ reasoningStrategy,
1281
+ reasoningInfo,
1282
+ peerPeers
1283
+ );
1284
+ } catch (error) {
1285
+ logger.warn?.("[memory-store] memory source failed:", error);
1286
+ warnings.push({
1287
+ source: "memory",
1288
+ code: "memory_search_failed",
1289
+ message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
1290
+ });
1291
+ }
1292
+ } else {
1293
+ warnings.push({
1294
+ source: "memory",
1295
+ code: "raw_message_storage_unavailable",
1296
+ message: "Raw memory storage is not available in this environment."
1297
+ });
1298
+ }
1299
+ }
1300
+ const insightHits = [];
1301
+ if (sources.includes("insights") && typeof deps.searchInsights === "function") {
1302
+ try {
1303
+ const hits = await deps.searchInsights({
1304
+ userId: input.userId,
1305
+ query: input.query,
1306
+ limit,
1307
+ threshold,
1308
+ botIds: input.botIds,
1309
+ includeArchived: input.includeArchivedInsights,
1310
+ authToken: input.authToken,
1311
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
1312
+ });
1313
+ for (const hit of hits) {
1314
+ insightHits.push({
1315
+ type: "insight",
1316
+ id: hit.id,
1317
+ content: hit.content,
1318
+ similarity: hit.similarity,
1319
+ metadata: hit.metadata
1320
+ });
1321
+ }
1322
+ } catch (error) {
1323
+ logger.warn?.("[memory-store] insights source failed:", error);
1324
+ warnings.push({
1325
+ source: "insights",
1326
+ code: "insights_search_failed",
1327
+ message: error.message ?? "insights_search_failed"
1328
+ });
1329
+ }
1330
+ } else if (sources.includes("insights")) ;
1331
+ const knowledgeHits = [];
1332
+ if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
1333
+ try {
1334
+ const hits = await deps.searchKnowledge({
1335
+ userId: input.userId,
1336
+ query: input.query,
1337
+ options: {
1338
+ limit,
1339
+ threshold,
1340
+ documentIds: input.documentIds
1341
+ },
1342
+ authToken: input.authToken,
1343
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
1344
+ });
1345
+ for (const hit of hits) {
1346
+ knowledgeHits.push(toKnowledgeResult(hit));
1347
+ }
1348
+ } catch (error) {
1349
+ logger.warn?.("[memory-store] knowledge source failed:", error);
1350
+ warnings.push({
1351
+ source: "knowledge",
1352
+ code: "knowledge_search_failed",
1353
+ message: error.message ?? "knowledge_search_failed"
1354
+ });
1355
+ }
1356
+ } else if (sources.includes("knowledge")) ;
1357
+ const merged = mergeAcrossSources({
1358
+ memorySubs,
1359
+ insightHits,
1360
+ knowledgeHits,
1361
+ limit,
1362
+ strategy: mergeStrategy
1363
+ });
1364
+ const ranked = await applyReranker(deps.reranker, input.query, merged);
1365
+ const output = {
1366
+ query: input.query,
1367
+ sources,
1368
+ results: ranked,
1369
+ count: ranked.length,
1370
+ warnings
1371
+ };
1372
+ if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
1373
+ output.reasoning = reasoningInfo;
1374
+ }
1375
+ return output;
1376
+ }
885
1377
  async function searchRawMemorySemantically(input) {
886
1378
  const limit = clampUnifiedMemorySearchLimit(input.limit);
887
1379
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
888
1380
  const reasoningStrategy = resolveReasoningStrategy(input);
1381
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1382
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1383
+ );
889
1384
  const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
890
1385
  return mergeAcrossSources({
891
1386
  memorySubs: sub,
892
1387
  insightHits: [],
893
1388
  knowledgeHits: [],
894
1389
  limit,
895
- strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
1390
+ strategy: mergeStrategy
1391
+ });
1392
+ }
1393
+ async function search(input) {
1394
+ const query = input.query.trim();
1395
+ const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
1396
+ const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
1397
+ const peerScope = await resolveSearchScopePeers({
1398
+ deps,
1399
+ userId: input.userId,
1400
+ peerFilter: input.peerFilter
1401
+ });
1402
+ if (!query) {
1403
+ const empty = {
1404
+ query,
1405
+ sources: input.sources ? [...input.sources] : [],
1406
+ results: [],
1407
+ evidence: [],
1408
+ count: 0,
1409
+ warnings: peerScope.warnings,
1410
+ answer: ""
1411
+ };
1412
+ return empty;
1413
+ }
1414
+ const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
1415
+ const wantsSummaryTier = tiers.includes("summary");
1416
+ const wantsRawTier = tiers.includes("raw");
1417
+ const wantsInsightTier = tiers.includes("insight");
1418
+ const wantsKnowledgeTier = tiers.includes("knowledge");
1419
+ const warnings = [...peerScope.warnings];
1420
+ const limit = clampUnifiedMemorySearchLimit(input.limit);
1421
+ const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
1422
+ let unifiedHits = [];
1423
+ let sources = input.sources ? [...input.sources] : [];
1424
+ let unifiedReasoning;
1425
+ if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
1426
+ const inferredSources = sources.length > 0 ? sources : [
1427
+ ...wantsRawTier ? ["memory"] : [],
1428
+ ...wantsInsightTier ? ["insights"] : [],
1429
+ ...wantsKnowledgeTier ? ["knowledge"] : []
1430
+ ];
1431
+ const unified = await searchUnifiedMemory({
1432
+ ...searchInputToUnified(input),
1433
+ sources: inferredSources.length > 0 ? inferredSources : void 0
1434
+ });
1435
+ unifiedHits = unified.results;
1436
+ sources = unified.sources;
1437
+ unifiedReasoning = unified.reasoning;
1438
+ warnings.push(...unified.warnings);
1439
+ }
1440
+ let summaryHits = [];
1441
+ if (wantsSummaryTier && typeof deps.searchSummaries === "function") {
1442
+ const summaryBucket = await gatherSummaries(
1443
+ deps,
1444
+ { userId: input.userId, query: input.query, authToken: input.authToken },
1445
+ limit,
1446
+ threshold,
1447
+ logger,
1448
+ peerScope.peers
1449
+ );
1450
+ warnings.push(...summaryBucket.warnings);
1451
+ summaryHits = summaryBucket.hits;
1452
+ }
1453
+ const tierLists = [];
1454
+ if (unifiedHits.length > 0) {
1455
+ tierLists.push({ name: "memory", hits: unifiedHits });
1456
+ }
1457
+ if (summaryHits.length > 0) {
1458
+ tierLists.push({ name: "summary", hits: summaryHits });
1459
+ }
1460
+ let hits;
1461
+ if (tierLists.length === 0) {
1462
+ hits = [];
1463
+ } else if (tierLists.length === 1) {
1464
+ hits = tierLists[0].hits.slice(0, limit);
1465
+ } else {
1466
+ hits = mergeUnifiedMemorySearchResultsRrf(tierLists, limit);
1467
+ }
1468
+ const evidence = hits.map((hit) => ({
1469
+ id: hit.id,
1470
+ source: mapHitToEvidenceSource(hit),
1471
+ snippet: hit.content,
1472
+ score: hit.similarity,
1473
+ timestamp: getCandidateTimestamp(hit.metadata)
1474
+ }));
1475
+ const base = {
1476
+ query,
1477
+ sources,
1478
+ results: hits,
1479
+ evidence,
1480
+ count: hits.length,
1481
+ warnings
1482
+ };
1483
+ if (unifiedReasoning) {
1484
+ base.reasoning = unifiedReasoning;
1485
+ }
1486
+ if (!wantsSynthesis) {
1487
+ return base;
1488
+ }
1489
+ const { answer, warnings: synthWarnings } = await synthesizeAnswer({
1490
+ query,
1491
+ hits,
1492
+ responseSchema: synthesisSchema,
1493
+ deps,
1494
+ logger
896
1495
  });
1496
+ base.answer = answer;
1497
+ base.warnings = [...base.warnings, ...synthWarnings];
1498
+ return base;
897
1499
  }
898
- return { searchUnifiedMemory, searchRawMemorySemantically };
1500
+ return {
1501
+ search,
1502
+ searchUnifiedMemory,
1503
+ searchRawMemorySemantically,
1504
+ consolidate: (input) => applyReflectedPlan(deps, {}, input)
1505
+ };
899
1506
  }
900
1507
  function mergeAcrossSources(input) {
901
1508
  const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
@@ -926,11 +1533,185 @@ function mergeAcrossSources(input) {
926
1533
  }
927
1534
  return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
928
1535
  }
1536
+ var DEFAULT_MAX_FACTS = 1e3;
1537
+ var LOW_CONFIDENCE_THRESHOLD = 0.1;
1538
+ function toHRR(data) {
1539
+ const arr = data instanceof Float32Array ? data : new Float32Array(data);
1540
+ return { dim: arr.length, data: arr };
1541
+ }
1542
+ function generateFactId() {
1543
+ const stamp = Date.now().toString(36);
1544
+ const rand = Math.random().toString(36).slice(2, 10);
1545
+ return `vsa-${stamp}-${rand}`;
1546
+ }
1547
+ function createVsaRecall(storage) {
1548
+ return {
1549
+ async storeFact(input) {
1550
+ const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
1551
+ const fillerVector = vsaNormalizeVector(input.fillerVector, "fillerVector");
1552
+ vsaAssertSameDim(roleVector, fillerVector, "storeFact");
1553
+ const dim = input.dim ?? roleVector.length;
1554
+ if (roleVector.length !== dim) {
1555
+ throw new Error(`storeFact: roleVector.length (${roleVector.length}) must equal dim (${dim})`);
1556
+ }
1557
+ const now = Date.now();
1558
+ const fact = {
1559
+ factId: input.factId ?? generateFactId(),
1560
+ userId: input.userId,
1561
+ roleLabel: input.roleLabel,
1562
+ fillerLabel: input.fillerLabel,
1563
+ roleVector,
1564
+ fillerVector,
1565
+ dim,
1566
+ scopeTag: input.scopeTag ?? "default",
1567
+ botId: input.botId,
1568
+ createdAt: now
1569
+ };
1570
+ await storage.storeFact(fact);
1571
+ return { factId: fact.factId, createdAt: now };
1572
+ },
1573
+ async recall(input) {
1574
+ const roleVector = vsaNormalizeVector(input.roleVector, "roleVector");
1575
+ const scopeTag = input.scopeTag ?? "default";
1576
+ const maxFacts = input.maxFacts ?? DEFAULT_MAX_FACTS;
1577
+ if (input.vocabulary.length === 0) {
1578
+ throw new Error("vsaRecall: vocabulary must contain at least one entry");
1579
+ }
1580
+ const facts = await storage.queryFacts({
1581
+ userId: input.userId,
1582
+ scopeTag,
1583
+ botId: input.botId,
1584
+ includeDeprecated: false,
1585
+ limit: maxFacts
1586
+ });
1587
+ const warnings = [];
1588
+ if (facts.length === 0) {
1589
+ warnings.push({
1590
+ code: "vsa_no_facts",
1591
+ message: `No VSA facts for userId=${input.userId} scopeTag=${scopeTag}`
1592
+ });
1593
+ return {
1594
+ fillerLabel: "",
1595
+ score: 0,
1596
+ allScores: [],
1597
+ factCount: 0,
1598
+ warnings
1599
+ };
1600
+ }
1601
+ const refDim = facts[0].dim;
1602
+ const dimMatchedFacts = facts.filter((fact) => {
1603
+ if (fact.dim === refDim) return true;
1604
+ warnings.push({
1605
+ code: "vsa_dim_mismatch",
1606
+ message: `Fact ${fact.factId} has dim=${fact.dim}, expected ${refDim}; skipping`
1607
+ });
1608
+ return false;
1609
+ });
1610
+ if (roleVector.length !== refDim) {
1611
+ throw new Error(
1612
+ `vsaRecall: roleVector dim (${roleVector.length}) must match stored fact dim (${refDim})`
1613
+ );
1614
+ }
1615
+ if (dimMatchedFacts.length === 0) {
1616
+ warnings.push({
1617
+ code: "vsa_no_facts",
1618
+ message: `All stored facts for userId=${input.userId} scopeTag=${scopeTag} had a non-matching dim`
1619
+ });
1620
+ return {
1621
+ fillerLabel: "",
1622
+ score: 0,
1623
+ allScores: [],
1624
+ factCount: 0,
1625
+ warnings
1626
+ };
1627
+ }
1628
+ const memoryVec = superpose(
1629
+ dimMatchedFacts.map((fact) => bind(toHRR(fact.roleVector), toHRR(fact.fillerVector)))
1630
+ );
1631
+ const candidate = unbind(memoryVec, toHRR(roleVector));
1632
+ const vocabVecs = input.vocabulary.map(
1633
+ (entry) => toHRR(vsaNormalizeVector(entry.vector, `vocabulary[${entry.label}].vector`))
1634
+ );
1635
+ const bestVec = cleanup(candidate, vocabVecs);
1636
+ const bestIdx = vocabVecs.indexOf(bestVec);
1637
+ const allScores = input.vocabulary.map((entry, idx) => ({
1638
+ label: entry.label,
1639
+ score: cosineSimilarity(candidate, vocabVecs[idx])
1640
+ }));
1641
+ allScores.sort((a, b) => b.score - a.score);
1642
+ const fillerLabel = bestIdx >= 0 ? input.vocabulary[bestIdx].label : "";
1643
+ const score = allScores[0]?.score ?? 0;
1644
+ if (score < LOW_CONFIDENCE_THRESHOLD) {
1645
+ warnings.push({
1646
+ code: "vsa_low_confidence",
1647
+ message: `Best match "${fillerLabel}" score ${score.toFixed(3)} below ${LOW_CONFIDENCE_THRESHOLD}`
1648
+ });
1649
+ }
1650
+ if (facts.length >= maxFacts) {
1651
+ warnings.push({
1652
+ code: "vsa_fact_limit_reached",
1653
+ message: `Loaded ${facts.length} facts (cap=${maxFacts}); recall may degrade with more`
1654
+ });
1655
+ }
1656
+ return {
1657
+ fillerLabel,
1658
+ score,
1659
+ allScores,
1660
+ factCount: facts.length,
1661
+ warnings
1662
+ };
1663
+ },
1664
+ async listFacts(input) {
1665
+ const facts = await storage.queryFacts({
1666
+ userId: input.userId,
1667
+ scopeTag: input.scopeTag,
1668
+ botId: input.botId,
1669
+ includeDeprecated: input.includeDeprecated
1670
+ });
1671
+ return facts.map((fact) => ({
1672
+ factId: fact.factId,
1673
+ userId: fact.userId,
1674
+ roleLabel: fact.roleLabel,
1675
+ fillerLabel: fact.fillerLabel,
1676
+ scopeTag: fact.scopeTag,
1677
+ botId: fact.botId,
1678
+ createdAt: fact.createdAt,
1679
+ deprecatedAt: fact.deprecatedAt
1680
+ }));
1681
+ },
1682
+ async forget(input) {
1683
+ const result = await storage.deprecateFacts({
1684
+ userId: input.userId,
1685
+ factIds: input.factIds,
1686
+ reason: input.reason
1687
+ });
1688
+ return { deprecatedCount: result.deprecatedCount };
1689
+ }
1690
+ };
1691
+ }
929
1692
 
930
1693
  // src/index.ts
931
1694
  init_raw_message_store();
1695
+ init_sqlite_raw_message_store();
932
1696
  init_raw_message_store();
933
1697
  init_postgres_raw_message_factory();
1698
+ function attachMemoryGraphStore(store, input) {
1699
+ const logger = input.logger ?? console;
1700
+ if (typeof input.storage.compareAndSwapGraphLedger !== "function") {
1701
+ logger.warn?.(
1702
+ "[memory-store] attachMemoryGraphStore: storage is missing `compareAndSwapGraphLedger`; no graph store attached."
1703
+ );
1704
+ return store;
1705
+ }
1706
+ const graphStore = createRawMessageMemoryGraphStore({
1707
+ storage: input.storage,
1708
+ ownerScope: input.ownerScope,
1709
+ botId: input.botId,
1710
+ now: input.now
1711
+ });
1712
+ store.graphStore = graphStore;
1713
+ return store;
1714
+ }
934
1715
 
935
1716
  // src/search/query-rewriter.ts
936
1717
  var SYSTEM_PROMPT = `You are a memory retrieval assistant.
@@ -1286,19 +2067,173 @@ function createIdentityIterativePlanner() {
1286
2067
  };
1287
2068
  }
1288
2069
 
1289
- // src/index.ts
2070
+ // src/search/peer-profile.ts
2071
+ var DEFAULT_PROFILE_LIMIT = 20;
2072
+ var DEFAULT_RELATIONSHIP_LIMIT = 5;
2073
+ var DEFAULT_PROFILE_THRESHOLD = 0.1;
2074
+ var DEFAULT_RELATIONSHIP_THRESHOLD = 0.1;
2075
+ function indexKnownPeers(peers) {
2076
+ const map = /* @__PURE__ */ new Map();
2077
+ for (const peer of peers) {
2078
+ map.set(peer.id, peer);
2079
+ }
2080
+ return map;
2081
+ }
2082
+ function resolvePeerFromMap(peerId, known, resolvePeer) {
2083
+ if (resolvePeer) {
2084
+ const explicit = resolvePeer(peerId);
2085
+ if (explicit) return explicit;
2086
+ }
2087
+ return known.get(peerId);
2088
+ }
2089
+ function buildProfilePrompt(peerId) {
2090
+ return `Summarize ${peerId}'s role, interests, and recent activity based on the evidence below.`;
2091
+ }
2092
+ function buildRelationshipPrompt(fromId, toId) {
2093
+ return `How do ${fromId} and ${toId} interact? Describe the relationship.`;
2094
+ }
2095
+ async function collectMentions(postings) {
2096
+ if (!postings) return [];
2097
+ const result = postings();
2098
+ if (Array.isArray(result)) {
2099
+ return result;
2100
+ }
2101
+ return await result;
2102
+ }
2103
+ function collectCoMentions(postings, targetId) {
2104
+ const coCounts = /* @__PURE__ */ new Map();
2105
+ for (const posting of postings) {
2106
+ const mentionIds = posting.mentionedPeers?.map((p) => p.id) ?? posting.mentionedIds ?? [];
2107
+ const involvesTarget = posting.authorId === targetId || mentionIds.includes(targetId) || (posting.mentionedPeers ?? []).some((p) => p.id === targetId);
2108
+ if (!involvesTarget) {
2109
+ continue;
2110
+ }
2111
+ if (posting.authorId !== targetId) {
2112
+ const entry = coCounts.get(posting.authorId) ?? { count: 0, evidenceIds: [] };
2113
+ entry.count += 1;
2114
+ entry.evidenceIds.push(posting.id);
2115
+ coCounts.set(posting.authorId, entry);
2116
+ }
2117
+ for (const otherId of mentionIds) {
2118
+ if (otherId === targetId) continue;
2119
+ const entry = coCounts.get(otherId) ?? { count: 0, evidenceIds: [] };
2120
+ entry.count += 1;
2121
+ entry.evidenceIds.push(posting.id);
2122
+ coCounts.set(otherId, entry);
2123
+ }
2124
+ }
2125
+ return coCounts;
2126
+ }
2127
+ function createPeerProfile(deps) {
2128
+ const { search, userId, resolvePeer, listKnownPeers } = deps;
2129
+ return {
2130
+ getKnownPeers() {
2131
+ return listKnownPeers();
2132
+ },
2133
+ async getProfile(peerId) {
2134
+ const known = indexKnownPeers(listKnownPeers());
2135
+ const peer = resolvePeerFromMap(peerId, known, resolvePeer);
2136
+ if (!peer) {
2137
+ throw new Error(`peer_profile: unknown peerId "${peerId}"`);
2138
+ }
2139
+ const out = await search.search({
2140
+ userId,
2141
+ query: buildProfilePrompt(peer.id),
2142
+ peerFilter: [peer],
2143
+ tiers: ["raw"],
2144
+ limit: DEFAULT_PROFILE_LIMIT,
2145
+ threshold: deps.profileThreshold ?? DEFAULT_PROFILE_THRESHOLD,
2146
+ synthesize: {
2147
+ responseSchema: { answer: "string", confidence: "number" }
2148
+ }
2149
+ });
2150
+ return {
2151
+ peer,
2152
+ answer: out.answer ?? "",
2153
+ evidenceIds: out.evidence.map((item) => item.id),
2154
+ warnings: out.warnings.map((warning) => warning.code)
2155
+ };
2156
+ },
2157
+ async getRelationships(peerId) {
2158
+ const known = indexKnownPeers(listKnownPeers());
2159
+ const peer = resolvePeerFromMap(peerId, known, resolvePeer);
2160
+ if (!peer) {
2161
+ throw new Error(`peer_profile: unknown peerId "${peerId}"`);
2162
+ }
2163
+ const postings = await collectMentions(deps.listPeerMentions);
2164
+ const coCounts = collectCoMentions(postings, peer.id);
2165
+ if (coCounts.size === 0) {
2166
+ return [];
2167
+ }
2168
+ const results = [];
2169
+ for (const [otherId, info] of coCounts) {
2170
+ const other = resolvePeerFromMap(otherId, known, resolvePeer);
2171
+ if (!other) continue;
2172
+ const confirmation = await search.search({
2173
+ userId,
2174
+ query: buildRelationshipPrompt(peer.id, other.id),
2175
+ peerFilter: [peer, other],
2176
+ sources: ["memory"],
2177
+ limit: DEFAULT_RELATIONSHIP_LIMIT,
2178
+ threshold: deps.relationshipThreshold ?? DEFAULT_RELATIONSHIP_THRESHOLD,
2179
+ mergeStrategy: "rrf"
2180
+ });
2181
+ const evidenceIds = confirmation.results.map((hit) => hit.id);
2182
+ results.push({
2183
+ from: peer,
2184
+ to: other,
2185
+ summary: confirmation.results.length > 0 ? `${confirmation.results.length} cross-mention${confirmation.results.length === 1 ? "" : "s"} between ${peer.id} and ${other.id}` : "no cross-mentions found",
2186
+ evidenceIds,
2187
+ strength: info.count
2188
+ });
2189
+ }
2190
+ return results.sort((a, b) => b.strength - a.strength);
2191
+ }
2192
+ };
2193
+ }
1290
2194
  async function createMemoryStore(config = {}) {
1291
2195
  const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
1292
2196
  const raw = createRawMessageStore({ env: config.env, dbPath });
1293
- const search = createUnifiedSearch(config.unified);
2197
+ const search = createUnifiedSearch({ ...config.unified, logger: config.logger });
1294
2198
  configureRawMessageStore(config);
1295
- return {
2199
+ const wiring = {
2200
+ graphStore: config.graphStore,
2201
+ storage: config.storage
2202
+ };
2203
+ const vsaDbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0) ?? resolveSQLiteRawMessageDbPath();
2204
+ const vsaStorage = await getSQLiteVsaStore({
2205
+ dbPath: vsaDbPath
2206
+ });
2207
+ const vsa = createVsaRecall(vsaStorage);
2208
+ const applyConsolidate = (input) => search.consolidate({
2209
+ ...input,
2210
+ graphStore: input.graphStore ?? wiring.graphStore,
2211
+ storage: input.storage ?? wiring.storage
2212
+ });
2213
+ const store = {
1296
2214
  raw,
1297
- search,
2215
+ // Flat search verb — `search({ synthesize: true })` opts into the
2216
+ // read-only LLM synthesis path.
2217
+ search: search.search,
2218
+ vsa,
1298
2219
  getRawMessageManager,
1299
2220
  searchUnifiedMemory: search.searchUnifiedMemory,
1300
- searchRawMemorySemantically: search.searchRawMemorySemantically
2221
+ searchRawMemorySemantically: search.searchRawMemorySemantically,
2222
+ consolidate: applyConsolidate,
2223
+ get graphStore() {
2224
+ return wiring.graphStore;
2225
+ },
2226
+ set graphStore(value) {
2227
+ wiring.graphStore = value;
2228
+ },
2229
+ get storage() {
2230
+ return wiring.storage;
2231
+ },
2232
+ set storage(value) {
2233
+ wiring.storage = value;
2234
+ }
1301
2235
  };
2236
+ return store;
1302
2237
  }
1303
2238
 
1304
- export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
2239
+ export { applyReflectedPlan, attachMemoryGraphStore, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createPeerProfile, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, createVsaRecall, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };