@graphorin/memory 0.5.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.
Files changed (287) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +401 -0
  4. package/dist/conflict/index.d.ts +3 -0
  5. package/dist/conflict/index.js +7 -0
  6. package/dist/conflict/locale-packs/en.js +262 -0
  7. package/dist/conflict/locale-packs/en.js.map +1 -0
  8. package/dist/conflict/locale-packs/index.js +4 -0
  9. package/dist/conflict/locale-packs/types.d.ts +103 -0
  10. package/dist/conflict/locale-packs/types.d.ts.map +1 -0
  11. package/dist/conflict/locale-packs/types.js +55 -0
  12. package/dist/conflict/locale-packs/types.js.map +1 -0
  13. package/dist/conflict/pipeline.d.ts +35 -0
  14. package/dist/conflict/pipeline.d.ts.map +1 -0
  15. package/dist/conflict/pipeline.js +257 -0
  16. package/dist/conflict/pipeline.js.map +1 -0
  17. package/dist/conflict/stages/helpers.js +66 -0
  18. package/dist/conflict/stages/helpers.js.map +1 -0
  19. package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
  20. package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
  21. package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
  22. package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
  23. package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
  24. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
  25. package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
  26. package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
  27. package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
  28. package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
  29. package/dist/conflict/types.d.ts +222 -0
  30. package/dist/conflict/types.d.ts.map +1 -0
  31. package/dist/conflict/types.js +50 -0
  32. package/dist/conflict/types.js.map +1 -0
  33. package/dist/consolidator/budget.d.ts +1 -0
  34. package/dist/consolidator/budget.js +214 -0
  35. package/dist/consolidator/budget.js.map +1 -0
  36. package/dist/consolidator/decay.d.ts +35 -0
  37. package/dist/consolidator/decay.d.ts.map +1 -0
  38. package/dist/consolidator/decay.js +91 -0
  39. package/dist/consolidator/decay.js.map +1 -0
  40. package/dist/consolidator/dlq.js +58 -0
  41. package/dist/consolidator/dlq.js.map +1 -0
  42. package/dist/consolidator/errors.d.ts +54 -0
  43. package/dist/consolidator/errors.d.ts.map +1 -0
  44. package/dist/consolidator/errors.js +67 -0
  45. package/dist/consolidator/errors.js.map +1 -0
  46. package/dist/consolidator/idempotency.d.ts +1 -0
  47. package/dist/consolidator/idempotency.js +22 -0
  48. package/dist/consolidator/idempotency.js.map +1 -0
  49. package/dist/consolidator/index.d.ts +34 -0
  50. package/dist/consolidator/index.d.ts.map +1 -0
  51. package/dist/consolidator/index.js +163 -0
  52. package/dist/consolidator/index.js.map +1 -0
  53. package/dist/consolidator/lock.d.ts +2 -0
  54. package/dist/consolidator/lock.js +89 -0
  55. package/dist/consolidator/lock.js.map +1 -0
  56. package/dist/consolidator/noise-filter.d.ts +2 -0
  57. package/dist/consolidator/noise-filter.js +173 -0
  58. package/dist/consolidator/noise-filter.js.map +1 -0
  59. package/dist/consolidator/phases/deep.d.ts +4 -0
  60. package/dist/consolidator/phases/deep.js +198 -0
  61. package/dist/consolidator/phases/deep.js.map +1 -0
  62. package/dist/consolidator/phases/induce.d.ts +154 -0
  63. package/dist/consolidator/phases/induce.d.ts.map +1 -0
  64. package/dist/consolidator/phases/induce.js +253 -0
  65. package/dist/consolidator/phases/induce.js.map +1 -0
  66. package/dist/consolidator/phases/light.d.ts +4 -0
  67. package/dist/consolidator/phases/light.js +99 -0
  68. package/dist/consolidator/phases/light.js.map +1 -0
  69. package/dist/consolidator/phases/reflect.js +287 -0
  70. package/dist/consolidator/phases/reflect.js.map +1 -0
  71. package/dist/consolidator/phases/standard.d.ts +8 -0
  72. package/dist/consolidator/phases/standard.js +499 -0
  73. package/dist/consolidator/phases/standard.js.map +1 -0
  74. package/dist/consolidator/reconcile.js +192 -0
  75. package/dist/consolidator/reconcile.js.map +1 -0
  76. package/dist/consolidator/runtime.d.ts +62 -0
  77. package/dist/consolidator/runtime.d.ts.map +1 -0
  78. package/dist/consolidator/runtime.js +580 -0
  79. package/dist/consolidator/runtime.js.map +1 -0
  80. package/dist/consolidator/scheduler.d.ts +111 -0
  81. package/dist/consolidator/scheduler.d.ts.map +1 -0
  82. package/dist/consolidator/scheduler.js +71 -0
  83. package/dist/consolidator/scheduler.js.map +1 -0
  84. package/dist/consolidator/triggers.d.ts +40 -0
  85. package/dist/consolidator/triggers.d.ts.map +1 -0
  86. package/dist/consolidator/triggers.js +94 -0
  87. package/dist/consolidator/triggers.js.map +1 -0
  88. package/dist/consolidator/types.d.ts +397 -0
  89. package/dist/consolidator/types.d.ts.map +1 -0
  90. package/dist/consolidator/types.js +117 -0
  91. package/dist/consolidator/types.js.map +1 -0
  92. package/dist/context-engine/annotations.d.ts +118 -0
  93. package/dist/context-engine/annotations.d.ts.map +1 -0
  94. package/dist/context-engine/annotations.js +102 -0
  95. package/dist/context-engine/annotations.js.map +1 -0
  96. package/dist/context-engine/auto-recall.d.ts +52 -0
  97. package/dist/context-engine/auto-recall.d.ts.map +1 -0
  98. package/dist/context-engine/auto-recall.js +41 -0
  99. package/dist/context-engine/auto-recall.js.map +1 -0
  100. package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
  101. package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
  102. package/dist/context-engine/compaction/clear-tool-results.js +97 -0
  103. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
  104. package/dist/context-engine/compaction/compactor.d.ts +52 -0
  105. package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
  106. package/dist/context-engine/compaction/compactor.js +196 -0
  107. package/dist/context-engine/compaction/compactor.js.map +1 -0
  108. package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
  109. package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
  110. package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
  111. package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
  112. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
  113. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
  114. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
  115. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
  116. package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
  117. package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
  118. package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
  119. package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
  120. package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
  121. package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
  122. package/dist/context-engine/compaction/index.d.ts +9 -0
  123. package/dist/context-engine/compaction/index.js +9 -0
  124. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
  125. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
  126. package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
  127. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
  128. package/dist/context-engine/compaction/thresholds.d.ts +68 -0
  129. package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
  130. package/dist/context-engine/compaction/thresholds.js +63 -0
  131. package/dist/context-engine/compaction/thresholds.js.map +1 -0
  132. package/dist/context-engine/compaction/types.d.ts +202 -0
  133. package/dist/context-engine/compaction/types.d.ts.map +1 -0
  134. package/dist/context-engine/engine.d.ts +258 -0
  135. package/dist/context-engine/engine.d.ts.map +1 -0
  136. package/dist/context-engine/engine.js +424 -0
  137. package/dist/context-engine/engine.js.map +1 -0
  138. package/dist/context-engine/index.d.ts +68 -0
  139. package/dist/context-engine/index.d.ts.map +1 -0
  140. package/dist/context-engine/index.js +24 -0
  141. package/dist/context-engine/locale-packs/en.d.ts +14 -0
  142. package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
  143. package/dist/context-engine/locale-packs/en.js +87 -0
  144. package/dist/context-engine/locale-packs/en.js.map +1 -0
  145. package/dist/context-engine/locale-packs/index.js +4 -0
  146. package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
  147. package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
  148. package/dist/context-engine/locale-packs/resolver.js +119 -0
  149. package/dist/context-engine/locale-packs/resolver.js.map +1 -0
  150. package/dist/context-engine/locale-packs/types.d.ts +119 -0
  151. package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
  152. package/dist/context-engine/locale-packs/types.js +24 -0
  153. package/dist/context-engine/locale-packs/types.js.map +1 -0
  154. package/dist/context-engine/metadata.d.ts +40 -0
  155. package/dist/context-engine/metadata.d.ts.map +1 -0
  156. package/dist/context-engine/metadata.js +66 -0
  157. package/dist/context-engine/metadata.js.map +1 -0
  158. package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
  159. package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
  160. package/dist/context-engine/preambles/inbound-en.js +23 -0
  161. package/dist/context-engine/preambles/inbound-en.js.map +1 -0
  162. package/dist/context-engine/privacy-filter.d.ts +108 -0
  163. package/dist/context-engine/privacy-filter.d.ts.map +1 -0
  164. package/dist/context-engine/privacy-filter.js +113 -0
  165. package/dist/context-engine/privacy-filter.js.map +1 -0
  166. package/dist/context-engine/templates/base-en.d.ts +19 -0
  167. package/dist/context-engine/templates/base-en.d.ts.map +1 -0
  168. package/dist/context-engine/templates/base-en.js +22 -0
  169. package/dist/context-engine/templates/base-en.js.map +1 -0
  170. package/dist/context-engine/templates/composer.d.ts +55 -0
  171. package/dist/context-engine/templates/composer.d.ts.map +1 -0
  172. package/dist/context-engine/templates/composer.js +51 -0
  173. package/dist/context-engine/templates/composer.js.map +1 -0
  174. package/dist/context-engine/token-budget.d.ts +99 -0
  175. package/dist/context-engine/token-budget.d.ts.map +1 -0
  176. package/dist/context-engine/token-budget.js +211 -0
  177. package/dist/context-engine/token-budget.js.map +1 -0
  178. package/dist/context-engine/token-counter.d.ts +54 -0
  179. package/dist/context-engine/token-counter.d.ts.map +1 -0
  180. package/dist/context-engine/token-counter.js +81 -0
  181. package/dist/context-engine/token-counter.js.map +1 -0
  182. package/dist/errors/index.d.ts +171 -0
  183. package/dist/errors/index.d.ts.map +1 -0
  184. package/dist/errors/index.js +206 -0
  185. package/dist/errors/index.js.map +1 -0
  186. package/dist/facade.d.ts +278 -0
  187. package/dist/facade.d.ts.map +1 -0
  188. package/dist/facade.js +265 -0
  189. package/dist/facade.js.map +1 -0
  190. package/dist/graph/entity-resolver.d.ts +135 -0
  191. package/dist/graph/entity-resolver.d.ts.map +1 -0
  192. package/dist/graph/entity-resolver.js +202 -0
  193. package/dist/graph/entity-resolver.js.map +1 -0
  194. package/dist/graph/index.d.ts +1 -0
  195. package/dist/index.d.ts +103 -0
  196. package/dist/index.d.ts.map +1 -0
  197. package/dist/index.js +95 -0
  198. package/dist/index.js.map +1 -0
  199. package/dist/internal/contextualize.d.ts +14 -0
  200. package/dist/internal/contextualize.d.ts.map +1 -0
  201. package/dist/internal/contextualize.js +110 -0
  202. package/dist/internal/contextualize.js.map +1 -0
  203. package/dist/internal/embedder-binding.js +50 -0
  204. package/dist/internal/embedder-binding.js.map +1 -0
  205. package/dist/internal/id.js +24 -0
  206. package/dist/internal/id.js.map +1 -0
  207. package/dist/internal/injection-heuristics.js +64 -0
  208. package/dist/internal/injection-heuristics.js.map +1 -0
  209. package/dist/internal/spans.js +35 -0
  210. package/dist/internal/spans.js.map +1 -0
  211. package/dist/internal/storage-adapter.d.ts +742 -0
  212. package/dist/internal/storage-adapter.d.ts.map +1 -0
  213. package/dist/migration/embedder-migration.d.ts +124 -0
  214. package/dist/migration/embedder-migration.d.ts.map +1 -0
  215. package/dist/migration/embedder-migration.js +109 -0
  216. package/dist/migration/embedder-migration.js.map +1 -0
  217. package/dist/migration/index.d.ts +2 -0
  218. package/dist/migration/index.js +3 -0
  219. package/dist/search/explain.d.ts +74 -0
  220. package/dist/search/explain.d.ts.map +1 -0
  221. package/dist/search/explain.js +50 -0
  222. package/dist/search/explain.js.map +1 -0
  223. package/dist/search/index.d.ts +6 -0
  224. package/dist/search/index.js +6 -0
  225. package/dist/search/iterative.d.ts +223 -0
  226. package/dist/search/iterative.d.ts.map +1 -0
  227. package/dist/search/iterative.js +330 -0
  228. package/dist/search/iterative.js.map +1 -0
  229. package/dist/search/query-transform.d.ts +112 -0
  230. package/dist/search/query-transform.d.ts.map +1 -0
  231. package/dist/search/query-transform.js +184 -0
  232. package/dist/search/query-transform.js.map +1 -0
  233. package/dist/search/rrf.d.ts +95 -0
  234. package/dist/search/rrf.d.ts.map +1 -0
  235. package/dist/search/rrf.js +166 -0
  236. package/dist/search/rrf.js.map +1 -0
  237. package/dist/search/types.d.ts +50 -0
  238. package/dist/search/types.d.ts.map +1 -0
  239. package/dist/tiers/episodic-memory.d.ts +142 -0
  240. package/dist/tiers/episodic-memory.d.ts.map +1 -0
  241. package/dist/tiers/episodic-memory.js +254 -0
  242. package/dist/tiers/episodic-memory.js.map +1 -0
  243. package/dist/tiers/index.d.ts +8 -0
  244. package/dist/tiers/index.js +9 -0
  245. package/dist/tiers/insight-memory.d.ts +71 -0
  246. package/dist/tiers/insight-memory.d.ts.map +1 -0
  247. package/dist/tiers/insight-memory.js +133 -0
  248. package/dist/tiers/insight-memory.js.map +1 -0
  249. package/dist/tiers/procedural-memory.d.ts +147 -0
  250. package/dist/tiers/procedural-memory.d.ts.map +1 -0
  251. package/dist/tiers/procedural-memory.js +267 -0
  252. package/dist/tiers/procedural-memory.js.map +1 -0
  253. package/dist/tiers/semantic-memory.d.ts +461 -0
  254. package/dist/tiers/semantic-memory.d.ts.map +1 -0
  255. package/dist/tiers/semantic-memory.js +631 -0
  256. package/dist/tiers/semantic-memory.js.map +1 -0
  257. package/dist/tiers/session-memory.d.ts +117 -0
  258. package/dist/tiers/session-memory.d.ts.map +1 -0
  259. package/dist/tiers/session-memory.js +163 -0
  260. package/dist/tiers/session-memory.js.map +1 -0
  261. package/dist/tiers/shared-memory.d.ts +29 -0
  262. package/dist/tiers/shared-memory.d.ts.map +1 -0
  263. package/dist/tiers/shared-memory.js +58 -0
  264. package/dist/tiers/shared-memory.js.map +1 -0
  265. package/dist/tiers/working-memory.d.ts +129 -0
  266. package/dist/tiers/working-memory.d.ts.map +1 -0
  267. package/dist/tiers/working-memory.js +229 -0
  268. package/dist/tiers/working-memory.js.map +1 -0
  269. package/dist/tools/block-tools.d.ts +98 -0
  270. package/dist/tools/block-tools.d.ts.map +1 -0
  271. package/dist/tools/block-tools.js +114 -0
  272. package/dist/tools/block-tools.js.map +1 -0
  273. package/dist/tools/fact-tools.d.ts +294 -0
  274. package/dist/tools/fact-tools.d.ts.map +1 -0
  275. package/dist/tools/fact-tools.js +293 -0
  276. package/dist/tools/fact-tools.js.map +1 -0
  277. package/dist/tools/index.d.ts +35 -0
  278. package/dist/tools/index.d.ts.map +1 -0
  279. package/dist/tools/index.js +36 -0
  280. package/dist/tools/index.js.map +1 -0
  281. package/dist/tools/recall-tools.d.ts +209 -0
  282. package/dist/tools/recall-tools.d.ts.map +1 -0
  283. package/dist/tools/recall-tools.js +170 -0
  284. package/dist/tools/recall-tools.js.map +1 -0
  285. package/dist/tools/types.d.ts +40 -0
  286. package/dist/tools/types.d.ts.map +1 -0
  287. package/package.json +119 -0
@@ -0,0 +1,631 @@
1
+ import { withMemorySpan } from "../internal/spans.js";
2
+ import { QuarantinePromotionRefusedError } from "../errors/index.js";
3
+ import { contextualize } from "../internal/contextualize.js";
4
+ import { newMemoryId } from "../internal/id.js";
5
+ import { DEFAULT_MAX_ITERATIONS, runIterativeRetrieval } from "../search/iterative.js";
6
+ import { WeightedRRFReranker, fuseRrf, fuseWeighted } from "../search/rrf.js";
7
+ import { detectMemoryInjection } from "../internal/injection-heuristics.js";
8
+ import { explainRecall } from "../search/explain.js";
9
+ import { md5 } from "@graphorin/core";
10
+
11
+ //#region src/tiers/semantic-memory.ts
12
+ /**
13
+ * `SemanticMemory` — long-term factual store. Hybrid (vector + FTS5)
14
+ * search merges the two ranked lists through the configured
15
+ * {@link ReRanker} (default {@link RRFReranker} with `k = 60`).
16
+ *
17
+ * Phase 10a wrote facts straight through with MD5 deduplication;
18
+ * Phase 10b routes every `remember(...)` call through the multi-stage
19
+ * conflict resolution pipeline (DEC-117 / ADR-018 ext / RB-02). The
20
+ * pipeline can be disabled per-call (`pipeline: 'off'`) or per-`Memory`
21
+ * instance (`createMemory({ conflictPipeline: { mode: 'off' } })`).
22
+ *
23
+ * @stable
24
+ */
25
+ var SemanticMemory = class {
26
+ #store;
27
+ #tracer;
28
+ #embedder;
29
+ #embedderIdProvider;
30
+ #pipeline;
31
+ #contextualMode;
32
+ #queryTransformer;
33
+ #entityResolver;
34
+ #grader;
35
+ #iterativeMaxIterations;
36
+ #reranker;
37
+ constructor(args) {
38
+ this.#store = args.store;
39
+ this.#tracer = args.tracer;
40
+ this.#embedder = args.embedder;
41
+ this.#embedderIdProvider = args.embedderIdProvider;
42
+ this.#reranker = args.reranker;
43
+ this.#pipeline = args.conflictPipeline ?? null;
44
+ this.#contextualMode = args.contextualRetrieval ?? "late-chunk";
45
+ this.#queryTransformer = args.queryTransformer ?? null;
46
+ this.#entityResolver = args.entityResolver ?? null;
47
+ this.#grader = args.grader ?? null;
48
+ this.#iterativeMaxIterations = args.iterativeMaxIterations ?? DEFAULT_MAX_ITERATIONS;
49
+ }
50
+ /** Replace the active reranker. Returns the previous instance. */
51
+ setReranker(reranker) {
52
+ const previous = this.#reranker;
53
+ this.#reranker = reranker;
54
+ return previous;
55
+ }
56
+ /** Currently active reranker. */
57
+ reranker() {
58
+ return this.#reranker;
59
+ }
60
+ /**
61
+ * Persist a fact. Returns the stored record. Phase 10b routes every
62
+ * call through the multi-stage conflict resolution pipeline; the
63
+ * legacy straight-through path is reachable per-call via
64
+ * `{ pipeline: 'off' }` (operators may disable the pipeline globally
65
+ * via `createMemory({ conflictPipeline: { mode: 'off' } })`).
66
+ */
67
+ async remember(scope, input, options = {}) {
68
+ const { fact } = await this.rememberWithDecision(scope, input, options);
69
+ return fact;
70
+ }
71
+ /**
72
+ * Like {@link remember} but returns the pipeline `decision` alongside
73
+ * the stored fact. Useful for callers that need to distinguish
74
+ * silent dedups (`decision.kind === 'dedup'`) from fresh inserts.
75
+ *
76
+ * @stable
77
+ */
78
+ async rememberWithDecision(scope, input, options = {}) {
79
+ return withMemorySpan(this.#tracer, "memory.write.semantic", scope, {}, async (span) => {
80
+ const now = (/* @__PURE__ */ new Date()).toISOString();
81
+ const text = input.text;
82
+ const provenance = input.provenance;
83
+ const synthesized = provenance === "extraction" || provenance === "reflection" || provenance === "induction";
84
+ const injection = detectMemoryInjection(text);
85
+ const autoPromote = options.autoPromoteSynthesized === true;
86
+ const status = injection.flagged ? "quarantined" : synthesized && !autoPromote ? "quarantined" : "active";
87
+ const quarantineReason = status === "quarantined" ? injection.flagged ? "injection" : "synthesized" : void 0;
88
+ const fact = {
89
+ id: newMemoryId("fact"),
90
+ kind: "semantic",
91
+ userId: scope.userId,
92
+ ...scope.sessionId !== void 0 ? { sessionId: scope.sessionId } : {},
93
+ ...scope.agentId !== void 0 ? { agentId: scope.agentId } : {},
94
+ sensitivity: input.sensitivity ?? "internal",
95
+ text,
96
+ ...input.subject !== void 0 ? { subject: input.subject } : {},
97
+ ...input.predicate !== void 0 ? { predicate: input.predicate } : {},
98
+ ...input.object !== void 0 ? { object: input.object } : {},
99
+ ...input.confidence !== void 0 ? { confidence: input.confidence } : {},
100
+ ...typeof input.importance === "number" && Number.isFinite(input.importance) ? { importance: Math.min(1, Math.max(0, input.importance)) } : {},
101
+ ...input.tags !== void 0 ? { tags: Object.freeze([...input.tags]) } : {},
102
+ ...input.validFrom !== void 0 ? { validFrom: input.validFrom } : { validFrom: now },
103
+ ...input.validTo !== void 0 ? { validTo: input.validTo } : {},
104
+ ...input.supersedes !== void 0 ? { supersedes: input.supersedes } : {},
105
+ ...provenance !== void 0 ? { provenance } : {},
106
+ status,
107
+ createdAt: now,
108
+ updatedAt: now
109
+ };
110
+ const embedderId = this.#embedderIdProvider();
111
+ const embedder = this.#embedder;
112
+ const indexText = this.#resolveIndexText(input, fact.text, options.indexText);
113
+ const pipelineEnabled = this.#pipeline !== null && (options.pipeline ?? "on") === "on";
114
+ const decision = pipelineEnabled && this.#pipeline !== null ? await this.#pipeline.run({
115
+ store: this.#store,
116
+ tracer: this.#tracer,
117
+ embedder,
118
+ embedderId,
119
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
120
+ }, fact) : {
121
+ kind: "admit",
122
+ stage: "exact-dedup",
123
+ reason: "pipeline-skipped"
124
+ };
125
+ span.setAttributes({
126
+ "memory.semantic.text_length": text.length,
127
+ "memory.semantic.hash": md5(text),
128
+ "memory.semantic.pipeline.decision": decision.kind,
129
+ "memory.semantic.pipeline.stage": decision.stage,
130
+ "memory.semantic.pipeline.enabled": pipelineEnabled,
131
+ "memory.semantic.status": status,
132
+ "memory.semantic.contextualized": indexText !== text,
133
+ ...provenance !== void 0 ? { "memory.semantic.provenance": provenance } : {},
134
+ "memory.semantic.injection_flagged": injection.flagged,
135
+ ...injection.flagged ? { "memory.semantic.injection_markers": injection.markers.join(",") } : {}
136
+ });
137
+ const reasonField = quarantineReason !== void 0 ? { quarantineReason } : {};
138
+ switch (decision.kind) {
139
+ case "admit":
140
+ case "pending":
141
+ await this.#commitFact(fact, embedder, embedderId, indexText);
142
+ await this.#linkEntities(scope, fact, options.signal);
143
+ return {
144
+ fact,
145
+ decision,
146
+ ...reasonField
147
+ };
148
+ case "dedup": return {
149
+ fact: await this.#fetchExisting(decision.existingId) ?? fact,
150
+ decision
151
+ };
152
+ case "supersede":
153
+ await this.#commitFact(fact, embedder, embedderId, indexText);
154
+ await this.#store.semantic.supersede(decision.existingId, fact, decision.reason);
155
+ await this.#linkEntities(scope, fact, options.signal);
156
+ return {
157
+ fact,
158
+ decision,
159
+ ...reasonField
160
+ };
161
+ }
162
+ });
163
+ }
164
+ /**
165
+ * Resolve the contextual-retrieval index text (P1-3). A caller
166
+ * override (the consolidator's `'llm'` mode) takes precedence; then
167
+ * the instance mode decides. Late-chunk derives the situating context
168
+ * from the *author-supplied* signals on `input` (so an extraction's
169
+ * subject/predicate/object survive even though the persisted `Fact`
170
+ * drops them) and never from framework-defaulted fields, so a plain
171
+ * `remember({ text })` write returns the canonical text unchanged.
172
+ */
173
+ #resolveIndexText(input, canonicalText, override) {
174
+ if (override !== void 0) return override;
175
+ if (this.#contextualMode === "off") return canonicalText;
176
+ return contextualize({
177
+ text: canonicalText,
178
+ ...input.subject !== void 0 ? { subject: input.subject } : {},
179
+ ...input.predicate !== void 0 ? { predicate: input.predicate } : {},
180
+ ...input.object !== void 0 ? { object: input.object } : {},
181
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
182
+ ...input.validFrom !== void 0 ? { validFrom: input.validFrom } : {}
183
+ });
184
+ }
185
+ async #commitFact(fact, embedder, embedderId, indexText) {
186
+ const adapterSupportsEmbeddedWrite = typeof this.#store.semantic.rememberWithEmbedding === "function";
187
+ const contextualized = indexText !== fact.text;
188
+ if (embedder !== null && embedderId !== null && adapterSupportsEmbeddedWrite) {
189
+ const [vector] = await embedder.embed([indexText], { taskType: "passage" });
190
+ if (vector !== void 0 && this.#store.semantic.rememberWithEmbedding !== void 0) {
191
+ await this.#store.semantic.rememberWithEmbedding(fact, {
192
+ embedding: {
193
+ embedderId,
194
+ vector
195
+ },
196
+ ...contextualized ? { indexText } : {}
197
+ });
198
+ return;
199
+ }
200
+ }
201
+ if (contextualized && adapterSupportsEmbeddedWrite && this.#store.semantic.rememberWithEmbedding !== void 0) {
202
+ await this.#store.semantic.rememberWithEmbedding(fact, { indexText });
203
+ return;
204
+ }
205
+ await this.#store.semantic.remember(fact);
206
+ }
207
+ async #fetchExisting(factId) {
208
+ if (typeof this.#store.semantic.get !== "function") return null;
209
+ return this.#store.semantic.get(factId);
210
+ }
211
+ /**
212
+ * P2-1: resolve the fact's subject / object to canonical entities and
213
+ * link them so one-hop expansion can traverse this fact. A no-op when
214
+ * no resolver is configured (the default) or the fact has no s/p/o, and
215
+ * resilient — a resolution failure never breaks the write that just
216
+ * committed.
217
+ */
218
+ async #linkEntities(scope, fact, signal) {
219
+ if (this.#entityResolver === null) return;
220
+ if (fact.subject === void 0 && fact.object === void 0) return;
221
+ try {
222
+ await this.#entityResolver.linkFact(scope, fact, { ...signal !== void 0 ? { signal } : {} });
223
+ } catch {}
224
+ }
225
+ /** Hybrid (vector + FTS5) search merged through the configured reranker. */
226
+ async search(scope, query, opts = {}) {
227
+ return withMemorySpan(this.#tracer, "memory.search.semantic", scope, { "memory.search.query_length": query.length }, async (span) => {
228
+ const candidateTopK = opts.candidateTopK ?? 60;
229
+ const finalTopK = opts.topK ?? 10;
230
+ const weighted = opts.fusion !== void 0 && opts.fusion.strategy === "weighted" ? opts.fusion : null;
231
+ const wFts = weighted?.weights.fts ?? 1;
232
+ const wVector = weighted?.weights.vector ?? 1;
233
+ const queries = await this.#expandQueries(query, opts);
234
+ const lists = [];
235
+ const listWeights = [];
236
+ const listLabels = [];
237
+ let primaryFtsCount = 0;
238
+ let primaryVectorCount = 0;
239
+ let isPrimary = true;
240
+ for (const q of queries) {
241
+ const ftsHits = await this.#store.semantic.search(scope, {
242
+ query: q,
243
+ topK: candidateTopK,
244
+ ...opts.asOf !== void 0 ? { asOf: opts.asOf } : {},
245
+ ...opts.tags !== void 0 && opts.tags.length > 0 ? { tags: opts.tags } : {},
246
+ ...opts.includeQuarantined === true ? { includeQuarantined: true } : {},
247
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
248
+ });
249
+ lists.push(ftsHits);
250
+ listWeights.push(wFts);
251
+ listLabels.push(`fts_${queries.indexOf(q)}`);
252
+ const vectorHits = await this.#tryVectorSearch(scope, q, candidateTopK, opts.asOf, opts.includeQuarantined);
253
+ if (vectorHits.length > 0) {
254
+ lists.push(vectorHits);
255
+ listWeights.push(wVector);
256
+ listLabels.push(`vector_${queries.indexOf(q)}`);
257
+ }
258
+ if (isPrimary) {
259
+ primaryFtsCount = ftsHits.length;
260
+ primaryVectorCount = vectorHits.length;
261
+ isPrimary = false;
262
+ }
263
+ }
264
+ const hydeHits = await this.#tryHyde(scope, query, opts, candidateTopK);
265
+ if (hydeHits.length > 0) {
266
+ lists.push(hydeHits);
267
+ listWeights.push(wVector);
268
+ listLabels.push("hyde");
269
+ }
270
+ const graphHits = await this.#tryExpandHops(scope, lists, opts, candidateTopK);
271
+ if (graphHits.length > 0) {
272
+ lists.push(graphHits);
273
+ listWeights.push(1);
274
+ listLabels.push("graph");
275
+ }
276
+ const reranker = weighted !== null ? new WeightedRRFReranker({
277
+ weights: listWeights,
278
+ ...weighted.k !== void 0 ? { k: weighted.k } : {}
279
+ }) : this.#reranker;
280
+ const fusedTopK = opts.decay !== void 0 ? Math.max(finalTopK, lists.reduce((n, l) => n + l.length, 0)) : finalTopK;
281
+ const fused = await reranker.rerank(query, lists, {
282
+ topK: fusedTopK,
283
+ labels: listLabels,
284
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
285
+ });
286
+ const decayed = await this.#applyDecay(scope, fused, opts.decay);
287
+ const ranked = (opts.tags !== void 0 && opts.tags.length > 0 ? decayed.filter((h) => {
288
+ const recordTags = h.record.tags;
289
+ if (recordTags === void 0 || recordTags.length === 0) return false;
290
+ return opts.tags?.some((t) => recordTags.includes(t)) === true;
291
+ }) : decayed).slice(0, finalTopK);
292
+ if (ranked.length > 0 && typeof this.#store.semantic.markAccessed === "function") try {
293
+ await this.#store.semantic.markAccessed(ranked.map((h) => h.record.id));
294
+ } catch {}
295
+ const explanation = explainRecall(ranked, {
296
+ query,
297
+ rerankerId: reranker.id
298
+ });
299
+ span.setAttributes({
300
+ "memory.search.semantic.fts_count": primaryFtsCount,
301
+ "memory.search.semantic.vector_count": primaryVectorCount,
302
+ "memory.search.semantic.final_count": ranked.length,
303
+ "memory.search.semantic.reranker_id": reranker.id,
304
+ "memory.search.semantic.decay_applied": opts.decay !== void 0,
305
+ "memory.search.semantic.query_count": queries.length,
306
+ "memory.search.semantic.hyde_applied": hydeHits.length > 0,
307
+ "memory.search.semantic.expand_hops": opts.expandHops ?? 0,
308
+ "memory.search.semantic.graph_count": graphHits.length,
309
+ "memory.search.semantic.explain": JSON.stringify(explanation.results),
310
+ ...opts.asOf !== void 0 ? { "memory.search.semantic.as_of": opts.asOf } : {},
311
+ ...opts.includeQuarantined === true ? { "memory.search.semantic.include_quarantined": true } : {}
312
+ });
313
+ return ranked;
314
+ });
315
+ }
316
+ /**
317
+ * Gated, iterative ("deep") recall for hard queries (P2-4). A cheap
318
+ * local heuristic ({@link assessQueryDifficulty}) decides whether the
319
+ * query is even a loop candidate; simple lookups take exactly one
320
+ * {@link search} pass and make no provider call. For a query judged
321
+ * hard *and* with a grader configured
322
+ * (`createMemory({ iterativeRetrieval })`), the retrieved set is graded
323
+ * for sufficiency and, when weak, the query is reformulated and
324
+ * retrieved again — **widening to one-hop graph expansion**
325
+ * (`expandHops: 1`) on each reformulation pass — up to `maxIterations`
326
+ * (hard-capped at 5). When still insufficient it returns
327
+ * `abstained: true` so the caller can decline to answer instead of
328
+ * confabulating.
329
+ *
330
+ * Without a grader (the offline default) this degrades to a single,
331
+ * difficulty-gated `search` and never calls a provider.
332
+ *
333
+ * @stable
334
+ */
335
+ async searchIterative(scope, query, opts = {}) {
336
+ return withMemorySpan(this.#tracer, "memory.search.semantic", scope, { "memory.search.query_length": query.length }, async (span) => {
337
+ const result = await runIterativeRetrieval(query, {
338
+ retrieve: (q, widen, signal) => this.search(scope, q, {
339
+ ...opts,
340
+ ...widen ? { expandHops: 1 } : {},
341
+ ...signal !== void 0 ? { signal } : {}
342
+ }),
343
+ snippetOf: (hit) => hit.record.text,
344
+ idOf: (hit) => hit.record.id,
345
+ grader: this.#grader,
346
+ fuse: (lists) => fuseRrf(lists, 60)
347
+ }, {
348
+ maxIterations: opts.maxIterations ?? this.#iterativeMaxIterations,
349
+ maxResults: opts.topK ?? 10,
350
+ ...opts.forceHard !== void 0 ? { forceHard: opts.forceHard } : {},
351
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
352
+ });
353
+ span.setAttributes({
354
+ "memory.search.semantic.iterative.gate_hard": result.gateHard,
355
+ "memory.search.semantic.iterative.iterations": result.iterations,
356
+ "memory.search.semantic.iterative.sufficient": result.sufficient,
357
+ "memory.search.semantic.iterative.abstained": result.abstained,
358
+ "memory.search.semantic.iterative.query_count": result.queries.length,
359
+ "memory.search.semantic.iterative.final_count": result.hits.length
360
+ });
361
+ return result;
362
+ });
363
+ }
364
+ /**
365
+ * Raw vector KNN neighbours for the consolidator's reconcile
366
+ * pre-filter (P0-3). Unlike {@link search} this skips FTS, reranking,
367
+ * and decay so the cosine scores survive intact (the conflict-pipeline
368
+ * zone thresholds are calibrated against them), and it **includes
369
+ * quarantined facts** so prior synthesized memories are visible to
370
+ * reconciliation. Returns `[]` when no embedder / vector adapter is
371
+ * configured — the consolidator then treats every candidate as a
372
+ * fresh `add`, degrading gracefully to the pre-reconcile behaviour.
373
+ *
374
+ * @stable
375
+ */
376
+ async neighbors(scope, text, opts = {}) {
377
+ return this.#tryVectorSearch(scope, text, opts.topK ?? 10, void 0, true);
378
+ }
379
+ /** Lookup a single fact by id. Returns `null` for soft-deleted / missing. */
380
+ async get(scope, factId) {
381
+ return withMemorySpan(this.#tracer, "memory.read.semantic", scope, { "memory.semantic.fact_id": factId }, async (span) => {
382
+ if (typeof this.#store.semantic.get !== "function") throw new TypeError("[graphorin/memory] SemanticMemory.get(...) requires a storage adapter that implements `semantic.get(id)`. The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via SemanticMemoryStoreExt.");
383
+ const fact = await this.#store.semantic.get(factId);
384
+ span.setAttributes({ "memory.read.semantic.found": fact !== null });
385
+ return fact;
386
+ });
387
+ }
388
+ /**
389
+ * Return the full bi-temporal supersede chain that `factId` belongs
390
+ * to, oldest → newest, including superseded / soft-deleted rows so
391
+ * callers can answer "how did this fact change over time". Requires
392
+ * a storage adapter that implements
393
+ * `SemanticMemoryStoreExt.historyOf(...)` — the default
394
+ * `@graphorin/store-sqlite` adapter wires this through. P0-2.
395
+ *
396
+ * @stable
397
+ */
398
+ async history(scope, factId) {
399
+ return withMemorySpan(this.#tracer, "memory.read.semantic", scope, {
400
+ "memory.semantic.action": "history",
401
+ "memory.semantic.fact_id": factId
402
+ }, async (span) => {
403
+ if (typeof this.#store.semantic.historyOf !== "function") throw new TypeError("[graphorin/memory] SemanticMemory.history(...) requires a storage adapter that implements `semantic.historyOf(scope, id)`. The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via SemanticMemoryStoreExt.");
404
+ const chain = await this.#store.semantic.historyOf(scope, factId);
405
+ span.setAttributes({ "memory.read.semantic.history_length": chain.length });
406
+ return chain;
407
+ });
408
+ }
409
+ /**
410
+ * Promote a quarantined fact to `active` (P1-4). The validation path
411
+ * that admits a synthesized memory into action-driving recall once a
412
+ * human (or trusted non-agent caller) has reviewed it. Writes a
413
+ * `memory_history` audit row. Requires a storage adapter that
414
+ * implements `SemanticMemoryStoreExt.setStatus(...)` — the default
415
+ * `@graphorin/store-sqlite` adapter wires this through.
416
+ *
417
+ * MRET-3 / MST-1: promotion of a fact whose text still trips the
418
+ * offline injection heuristics is **refused** with
419
+ * {@link QuarantinePromotionRefusedError} — the model-facing
420
+ * `fact_validate` tool calls this with no `force`, so a poisoned
421
+ * memory can never be promoted by the agent itself (the one-turn
422
+ * `fact_remember(poison)` → `fact_validate(id)` chain is closed). An
423
+ * operator can override after review by passing `{ force: true }`
424
+ * from a trusted (non-agent) context. Synthesized-but-clean writes
425
+ * (consolidator / reflection) promote normally.
426
+ *
427
+ * @stable
428
+ */
429
+ async validate(scope, factId, reason, options) {
430
+ await withMemorySpan(this.#tracer, "memory.write.semantic", scope, {
431
+ "memory.semantic.action": "validate",
432
+ "memory.semantic.fact_id": factId
433
+ }, async (span) => {
434
+ if (typeof this.#store.semantic.setStatus !== "function") throw new TypeError("[graphorin/memory] SemanticMemory.validate(...) requires a storage adapter that implements `semantic.setStatus(id, status)`. The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via SemanticMemoryStoreExt.");
435
+ const force = options?.force === true;
436
+ const existing = await this.#fetchExisting(factId);
437
+ if (existing !== null && !force) {
438
+ const injection = detectMemoryInjection(existing.text);
439
+ if (injection.flagged) {
440
+ span.setAttributes({
441
+ "memory.semantic.validate.refused": true,
442
+ "memory.semantic.injection_markers": injection.markers.join(",")
443
+ });
444
+ throw new QuarantinePromotionRefusedError(factId, injection.markers);
445
+ }
446
+ }
447
+ span.setAttributes({ "memory.semantic.validate.forced": force });
448
+ await this.#store.semantic.setStatus(factId, "active", reason);
449
+ });
450
+ }
451
+ /** Mark `oldId` superseded by a new fact. Returns the new record. */
452
+ async supersede(scope, oldId, newInput, reason) {
453
+ return withMemorySpan(this.#tracer, "memory.write.semantic", scope, { "memory.semantic.action": "supersede" }, async () => {
454
+ const newFact = await this.remember(scope, {
455
+ ...newInput,
456
+ supersedes: oldId
457
+ }, { pipeline: "off" });
458
+ await this.#store.semantic.supersede(oldId, newFact, reason);
459
+ return {
460
+ old: oldId,
461
+ new: newFact
462
+ };
463
+ });
464
+ }
465
+ /** Soft-delete a fact (kept for replay; never hard-deleted). */
466
+ async forget(scope, factId, reason) {
467
+ await withMemorySpan(this.#tracer, "memory.write.semantic", scope, {
468
+ "memory.semantic.action": "forget",
469
+ "memory.semantic.fact_id": factId
470
+ }, async () => {
471
+ await this.#store.semantic.forget(factId, reason);
472
+ });
473
+ }
474
+ /**
475
+ * Hard-delete a fact (GDPR path). Distinct from {@link forget}: the
476
+ * record is removed from storage entirely instead of soft-archived.
477
+ * Requires a storage adapter that implements
478
+ * `SemanticMemoryStoreExt.purge(...)` — the default
479
+ * `@graphorin/store-sqlite` adapter wires this through.
480
+ */
481
+ async purge(scope, factId, reason) {
482
+ await withMemorySpan(this.#tracer, "memory.write.semantic", scope, {
483
+ "memory.semantic.action": "purge",
484
+ "memory.semantic.fact_id": factId
485
+ }, async () => {
486
+ if (typeof this.#store.semantic.purge !== "function") throw new TypeError("[graphorin/memory] SemanticMemory.purge(...) requires a storage adapter that implements `semantic.purge(id)`. For storage adapters without GDPR-grade hard-delete, prefer `SemanticMemory.forget(...)`.");
487
+ await this.#store.semantic.purge(factId, reason);
488
+ });
489
+ }
490
+ /** Fuse multiple ranked lists outside of a `search()` call. */
491
+ async fuse(query, lists, options = {}) {
492
+ return this.#reranker.rerank(query, lists, options);
493
+ }
494
+ /** Pure-fusion helper — exposed for callers that already collected results. */
495
+ static fuseRrf(lists, k = 60) {
496
+ return fuseRrf(lists, k);
497
+ }
498
+ /**
499
+ * Pure weighted-fusion helper (X-2) — like {@link SemanticMemory.fuseRrf}
500
+ * but scales each list `i`'s reciprocal-rank contribution by
501
+ * `weights[i]`. A missing / invalid entry defaults to `1`, so equal or
502
+ * absent weights reproduce RRF.
503
+ */
504
+ static fuseWeighted(lists, weights, k = 60) {
505
+ return fuseWeighted(lists, weights, k);
506
+ }
507
+ /**
508
+ * Multi-query expansion (P2-3). Returns the original query followed by
509
+ * up to `multiQuery - 1` deduped reworded variants. A no-op (`[query]`)
510
+ * when `multiQuery` is unset / `<= 1` or no transformer is configured —
511
+ * so the default path makes no provider call. A transformer failure
512
+ * degrades to `[query]` rather than breaking recall.
513
+ */
514
+ async #expandQueries(query, opts) {
515
+ const n = opts.multiQuery;
516
+ if (n === void 0 || n <= 1 || this.#queryTransformer === null) return [query];
517
+ try {
518
+ const variants = await this.#queryTransformer.expand(query, n - 1, { ...opts.signal !== void 0 ? { signal: opts.signal } : {} });
519
+ const seen = new Set([query.trim().toLowerCase()]);
520
+ const out = [query];
521
+ for (const variant of variants) {
522
+ const trimmed = variant.trim();
523
+ const key = trimmed.toLowerCase();
524
+ if (trimmed.length === 0 || seen.has(key)) continue;
525
+ seen.add(key);
526
+ out.push(trimmed);
527
+ if (out.length >= n) break;
528
+ }
529
+ return out;
530
+ } catch {
531
+ return [query];
532
+ }
533
+ }
534
+ /**
535
+ * HyDE retrieval (P2-3). Generates a hypothetical answer, embeds it,
536
+ * and returns its vector neighbours to fuse into the result. A no-op
537
+ * (`[]`) unless `hyde` is set, a transformer is configured, **and** an
538
+ * embedder + vector surface exist — the embedder guard is checked
539
+ * *first* so a missing embedder skips the LLM call entirely rather than
540
+ * generating a passage that can never be embedded.
541
+ */
542
+ async #tryHyde(scope, query, opts, topK) {
543
+ if (opts.hyde !== true || this.#queryTransformer === null) return [];
544
+ if (this.#embedder === null || this.#embedderIdProvider() === null || typeof this.#store.semantic.searchVector !== "function") return [];
545
+ try {
546
+ const pseudo = await this.#queryTransformer.hypothetical(query, { ...opts.signal !== void 0 ? { signal: opts.signal } : {} });
547
+ if (pseudo === null || pseudo.trim().length === 0) return [];
548
+ return this.#tryVectorSearch(scope, pseudo, topK, opts.asOf, opts.includeQuarantined);
549
+ } catch {
550
+ return [];
551
+ }
552
+ }
553
+ /**
554
+ * One-hop graph expansion (P2-1). Seeds on the unique fact ids already
555
+ * gathered into `lists` and fuses in facts sharing a canonical entity,
556
+ * via the adapter's recursive-CTE `graph.expandOneHop`. A no-op (`[]`)
557
+ * unless `expandHops >= 1` and the adapter exposes `graph` — and
558
+ * resilient: a traversal error degrades to no expansion rather than
559
+ * breaking recall. Neighbours carry a `graph` signal so explanations
560
+ * (X-3) can attribute a hit to the hop.
561
+ */
562
+ async #tryExpandHops(scope, lists, opts, limit) {
563
+ const hops = opts.expandHops ?? 0;
564
+ const graphStore = this.#store.graph;
565
+ if (hops < 1 || graphStore === void 0 || typeof graphStore.expandOneHop !== "function") return [];
566
+ const seedIds = [];
567
+ const seen = /* @__PURE__ */ new Set();
568
+ for (const list of lists) for (const hit of list) if (!seen.has(hit.record.id)) {
569
+ seen.add(hit.record.id);
570
+ seedIds.push(hit.record.id);
571
+ }
572
+ if (seedIds.length === 0) return [];
573
+ try {
574
+ return (await graphStore.expandOneHop(scope, seedIds, {
575
+ maxHops: hops,
576
+ limit,
577
+ ...opts.includeQuarantined === true ? { includeQuarantined: true } : {},
578
+ ...opts.asOf !== void 0 ? { asOf: opts.asOf } : {}
579
+ })).map((fact) => ({
580
+ record: fact,
581
+ score: 1,
582
+ signals: Object.freeze({ graph: 1 })
583
+ }));
584
+ } catch {
585
+ return [];
586
+ }
587
+ }
588
+ async #tryVectorSearch(scope, query, topK, asOf, includeQuarantined) {
589
+ const embedderId = this.#embedderIdProvider();
590
+ if (this.#embedder === null || embedderId === null || typeof this.#store.semantic.searchVector !== "function") return [];
591
+ const [vector] = await this.#embedder.embed([query], { taskType: "query" });
592
+ if (vector === void 0) return [];
593
+ return this.#store.semantic.searchVector(scope, vector, embedderId, topK, asOf, includeQuarantined);
594
+ }
595
+ async #applyDecay(scope, hits, decay) {
596
+ if (decay === void 0 || hits.length === 0) return hits;
597
+ const semantic = this.#store.semantic;
598
+ let rows;
599
+ if (typeof semantic.listDecaySignals === "function") rows = await semantic.listDecaySignals(hits.map((h) => h.record.id));
600
+ else if (typeof semantic.listForDecay === "function") rows = await semantic.listForDecay(scope, 1e3);
601
+ else return hits;
602
+ if (rows.length === 0) return hits;
603
+ const signals = new Map(rows.map((row) => [row.id, {
604
+ strength: row.strength,
605
+ lastAccessedAt: row.lastAccessedAt,
606
+ createdAt: row.createdAt
607
+ }]));
608
+ const now = (decay.now ?? Date.now)();
609
+ const tauMs = Math.max(1, decay.tauDays) * 24 * 60 * 60 * 1e3;
610
+ const sorted = [...hits.map((hit) => {
611
+ const sig = signals.get(hit.record.id);
612
+ if (sig === void 0) return hit;
613
+ const reference = sig.lastAccessedAt ?? sig.createdAt;
614
+ const elapsed = Math.max(0, now - reference);
615
+ const retention = Math.exp(-elapsed / (tauMs * Math.max(.5, sig.strength)));
616
+ return {
617
+ ...hit,
618
+ score: hit.score * retention,
619
+ signals: Object.freeze({
620
+ ...hit.signals ?? {},
621
+ decay: retention
622
+ })
623
+ };
624
+ })].sort((a, b) => b.score - a.score);
625
+ return Object.freeze(sorted);
626
+ }
627
+ };
628
+
629
+ //#endregion
630
+ export { SemanticMemory };
631
+ //# sourceMappingURL=semantic-memory.js.map