@melandlabs/memory-store 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
5
  import { dirname } from 'path';
5
6
  import { getOpenContextPath } from '@melandlabs/env-config';
6
- import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
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
  }
@@ -376,6 +379,416 @@ function isRawMemorySemanticResult(result) {
376
379
  const item = result;
377
380
  return typeof item.id === "string" && typeof item.content === "string" && typeof item.similarity === "number" && Boolean(item.metadata) && typeof item.metadata === "object";
378
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
+ }
379
792
 
380
793
  // src/search/unified-search.ts
381
794
  function deriveLexicalKeywords(query) {
@@ -447,7 +860,7 @@ async function embedQueryVariant(embedQuery, input, query) {
447
860
  authToken: input.authToken
448
861
  });
449
862
  }
450
- async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
863
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers = []) {
451
864
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
452
865
  let semantic = [];
453
866
  if (isRawMessageChromaEnabled()) {
@@ -477,6 +890,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
477
890
  }
478
891
  } else if (typeof deps.searchRawMessagesAnn === "function") {
479
892
  const searchRawMessagesAnn = deps.searchRawMessagesAnn;
893
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
480
894
  semantic = (await Promise.all(
481
895
  filters.map(
482
896
  (filter) => searchRawMessagesAnn({
@@ -484,7 +898,9 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
484
898
  queryEmbedding,
485
899
  limit,
486
900
  threshold,
487
- 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 } : {}
488
904
  })
489
905
  )
490
906
  )).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
@@ -494,11 +910,14 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
494
910
  const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
495
911
  const manager2 = await getRawMessageManager2();
496
912
  if (typeof manager2.searchMessagesSemantically === "function") {
913
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
497
914
  const results = await manager2.searchMessagesSemantically({
498
915
  userId: input.userId,
499
916
  queryEmbedding,
500
917
  limit,
501
- threshold
918
+ threshold,
919
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
920
+ ...factTypes ? { factTypes } : {}
502
921
  });
503
922
  semantic = results.map((r) => ({
504
923
  type: "memory",
@@ -534,7 +953,7 @@ function toIterativeRecallCandidate(hit) {
534
953
  metadata: hit.metadata
535
954
  };
536
955
  }
537
- async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
956
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers = []) {
538
957
  if (keywords.length === 0) {
539
958
  return [];
540
959
  }
@@ -542,13 +961,16 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
542
961
  try {
543
962
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
544
963
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
964
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
545
965
  return (await Promise.all(
546
966
  filters.map(
547
967
  (botId) => searchRawMessagesLexical({
548
968
  userId: input.userId,
549
969
  keywords,
550
970
  limit: Math.ceil(limit / filters.length),
551
- botId
971
+ botId,
972
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
973
+ ...factTypes ? { factTypes } : {}
552
974
  })
553
975
  )
554
976
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -562,13 +984,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
562
984
  try {
563
985
  const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
564
986
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
987
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
565
988
  return (await Promise.all(
566
989
  filters.map(
567
990
  (botId) => lexicalSearchRawMessages2({
568
991
  userId: input.userId,
569
992
  keywords,
570
993
  limit: Math.ceil(limit / filters.length),
571
- botId
994
+ botId,
995
+ ...factTypes ? { factTypes } : {}
572
996
  })
573
997
  )
574
998
  )).flat().filter(Boolean).map(
@@ -581,6 +1005,42 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger)
581
1005
  }
582
1006
  return [];
583
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
+ }
584
1044
  function createUnifiedSearch(deps = {}) {
585
1045
  const logger = deps.logger ?? console;
586
1046
  function resolveReasoningStrategy(input) {
@@ -590,130 +1050,7 @@ function createUnifiedSearch(deps = {}) {
590
1050
  }
591
1051
  return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
592
1052
  }
593
- async function searchUnifiedMemory(input) {
594
- const query = input.query.trim();
595
- const sources = normalizeUnifiedMemorySearchSources(input.sources);
596
- const limit = clampUnifiedMemorySearchLimit(input.limit);
597
- const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
598
- const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
599
- const reasoningStrategy = resolveReasoningStrategy(input);
600
- const warnings = [];
601
- if (!query) {
602
- return {
603
- query,
604
- sources,
605
- results: [],
606
- count: 0,
607
- warnings
608
- };
609
- }
610
- let memorySubs;
611
- const reasoningInfo = { strategy: reasoningStrategy };
612
- if (input.dateFrom || input.dateTo) {
613
- reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
614
- }
615
- if (sources.includes("memory")) {
616
- if (isRawMessageStorageAvailable()) {
617
- try {
618
- memorySubs = await runMemorySource(
619
- input,
620
- limit,
621
- threshold,
622
- warnings,
623
- reasoningStrategy,
624
- reasoningInfo
625
- );
626
- } catch (error) {
627
- logger.warn?.("[memory-store] memory source failed:", error);
628
- warnings.push({
629
- source: "memory",
630
- code: "memory_search_failed",
631
- message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
632
- });
633
- }
634
- } else {
635
- warnings.push({
636
- source: "memory",
637
- code: "raw_message_storage_unavailable",
638
- message: "Raw memory storage is not available in this environment."
639
- });
640
- }
641
- }
642
- const insightHits = [];
643
- if (sources.includes("insights") && typeof deps.searchInsights === "function") {
644
- try {
645
- const hits = await deps.searchInsights({
646
- userId: input.userId,
647
- query: input.query,
648
- limit,
649
- threshold,
650
- botIds: input.botIds,
651
- includeArchived: input.includeArchivedInsights,
652
- authToken: input.authToken
653
- });
654
- for (const hit of hits) {
655
- insightHits.push({
656
- type: "insight",
657
- id: hit.id,
658
- content: hit.content,
659
- similarity: hit.similarity,
660
- metadata: hit.metadata
661
- });
662
- }
663
- } catch (error) {
664
- logger.warn?.("[memory-store] insights source failed:", error);
665
- warnings.push({
666
- source: "insights",
667
- code: "insights_search_failed",
668
- message: error.message ?? "insights_search_failed"
669
- });
670
- }
671
- } else if (sources.includes("insights")) ;
672
- const knowledgeHits = [];
673
- if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
674
- try {
675
- const hits = await deps.searchKnowledge({
676
- userId: input.userId,
677
- query: input.query,
678
- options: {
679
- limit,
680
- threshold,
681
- documentIds: input.documentIds
682
- },
683
- authToken: input.authToken
684
- });
685
- for (const hit of hits) {
686
- knowledgeHits.push(toKnowledgeResult(hit));
687
- }
688
- } catch (error) {
689
- logger.warn?.("[memory-store] knowledge source failed:", error);
690
- warnings.push({
691
- source: "knowledge",
692
- code: "knowledge_search_failed",
693
- message: error.message ?? "knowledge_search_failed"
694
- });
695
- }
696
- } else if (sources.includes("knowledge")) ;
697
- const merged = mergeAcrossSources({
698
- memorySubs,
699
- insightHits,
700
- knowledgeHits,
701
- limit,
702
- strategy: mergeStrategy
703
- });
704
- const output = {
705
- query: input.query,
706
- sources,
707
- results: merged,
708
- count: merged.length,
709
- warnings
710
- };
711
- if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
712
- output.reasoning = reasoningInfo;
713
- }
714
- return output;
715
- }
716
- async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
1053
+ async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
717
1054
  if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
718
1055
  warnings.push({
719
1056
  source: "memory",
@@ -739,7 +1076,14 @@ function createUnifiedSearch(deps = {}) {
739
1076
  executor: {
740
1077
  search: async (request) => {
741
1078
  const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
742
- 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
+ );
743
1087
  let semanticHits = [];
744
1088
  if (typeof deps.embedQuery === "function") {
745
1089
  try {
@@ -751,7 +1095,8 @@ function createUnifiedSearch(deps = {}) {
751
1095
  queryEmbedding,
752
1096
  limit,
753
1097
  threshold,
754
- logger
1098
+ logger,
1099
+ peerPeers
755
1100
  );
756
1101
  } catch (error) {
757
1102
  logger.warn?.(
@@ -796,7 +1141,7 @@ function createUnifiedSearch(deps = {}) {
796
1141
  });
797
1142
  const keywords2 = deriveLexicalKeywords(input.query);
798
1143
  const lexical2 = filterByDateRange(
799
- await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
1144
+ await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger, peerPeers),
800
1145
  input.dateFrom,
801
1146
  input.dateTo
802
1147
  );
@@ -822,7 +1167,7 @@ function createUnifiedSearch(deps = {}) {
822
1167
  );
823
1168
  const lists = await Promise.all(
824
1169
  embeddings.map(
825
- (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
1170
+ (embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger, peerPeers)
826
1171
  )
827
1172
  );
828
1173
  semantic = mergeByMaxScore(lists);
@@ -838,7 +1183,15 @@ function createUnifiedSearch(deps = {}) {
838
1183
  }
839
1184
  if (semantic.length === 0) {
840
1185
  const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
841
- 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
+ );
842
1195
  }
843
1196
  let lexical = [];
844
1197
  const keywords = deriveLexicalKeywords(input.query);
@@ -847,13 +1200,16 @@ function createUnifiedSearch(deps = {}) {
847
1200
  try {
848
1201
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
849
1202
  const searchRawMessagesLexical = deps.searchRawMessagesLexical;
1203
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
850
1204
  lexical = (await Promise.all(
851
1205
  lexFilters.map(
852
1206
  (botId) => searchRawMessagesLexical({
853
1207
  userId: input.userId,
854
1208
  keywords,
855
1209
  limit,
856
- botId
1210
+ botId,
1211
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {},
1212
+ ...factTypes ? { factTypes } : {}
857
1213
  })
858
1214
  )
859
1215
  )).flat().filter(isRawMemorySemanticResult).map((hit) => ({
@@ -881,20 +1237,272 @@ function createUnifiedSearch(deps = {}) {
881
1237
  lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
882
1238
  };
883
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
+ }
884
1377
  async function searchRawMemorySemantically(input) {
885
1378
  const limit = clampUnifiedMemorySearchLimit(input.limit);
886
1379
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
887
1380
  const reasoningStrategy = resolveReasoningStrategy(input);
1381
+ const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1382
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1383
+ );
888
1384
  const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
889
1385
  return mergeAcrossSources({
890
1386
  memorySubs: sub,
891
1387
  insightHits: [],
892
1388
  knowledgeHits: [],
893
1389
  limit,
894
- 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
895
1495
  });
1496
+ base.answer = answer;
1497
+ base.warnings = [...base.warnings, ...synthWarnings];
1498
+ return base;
896
1499
  }
897
- return { searchUnifiedMemory, searchRawMemorySemantically };
1500
+ return {
1501
+ search,
1502
+ searchUnifiedMemory,
1503
+ searchRawMemorySemantically,
1504
+ consolidate: (input) => applyReflectedPlan(deps, {}, input)
1505
+ };
898
1506
  }
899
1507
  function mergeAcrossSources(input) {
900
1508
  const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
@@ -925,11 +1533,185 @@ function mergeAcrossSources(input) {
925
1533
  }
926
1534
  return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
927
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
+ }
928
1692
 
929
1693
  // src/index.ts
930
1694
  init_raw_message_store();
1695
+ init_sqlite_raw_message_store();
931
1696
  init_raw_message_store();
932
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
+ }
933
1715
 
934
1716
  // src/search/query-rewriter.ts
935
1717
  var SYSTEM_PROMPT = `You are a memory retrieval assistant.
@@ -1285,19 +2067,173 @@ function createIdentityIterativePlanner() {
1285
2067
  };
1286
2068
  }
1287
2069
 
1288
- // 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
+ }
1289
2194
  async function createMemoryStore(config = {}) {
1290
2195
  const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
1291
2196
  const raw = createRawMessageStore({ env: config.env, dbPath });
1292
- const search = createUnifiedSearch(config.unified);
2197
+ const search = createUnifiedSearch({ ...config.unified, logger: config.logger });
1293
2198
  configureRawMessageStore(config);
1294
- 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 = {
1295
2214
  raw,
1296
- search,
2215
+ // Flat search verb — `search({ synthesize: true })` opts into the
2216
+ // read-only LLM synthesis path.
2217
+ search: search.search,
2218
+ vsa,
1297
2219
  getRawMessageManager,
1298
2220
  searchUnifiedMemory: search.searchUnifiedMemory,
1299
- 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
+ }
1300
2235
  };
2236
+ return store;
1301
2237
  }
1302
2238
 
1303
- 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 };