@graphorin/memory 0.6.1 → 0.7.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 (227) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +1 -1
  3. package/dist/conflict/index.js +0 -1
  4. package/dist/conflict/pipeline.js +0 -1
  5. package/dist/conflict/pipeline.js.map +1 -1
  6. package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
  7. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
  8. package/dist/conflict/types.d.ts +1 -1
  9. package/dist/conflict/types.js.map +1 -1
  10. package/dist/consolidator/index.js +2 -0
  11. package/dist/consolidator/index.js.map +1 -1
  12. package/dist/consolidator/lock.js +7 -1
  13. package/dist/consolidator/lock.js.map +1 -1
  14. package/dist/consolidator/phases/deep.js +19 -12
  15. package/dist/consolidator/phases/deep.js.map +1 -1
  16. package/dist/consolidator/phases/induce.d.ts.map +1 -1
  17. package/dist/consolidator/phases/induce.js +2 -3
  18. package/dist/consolidator/phases/induce.js.map +1 -1
  19. package/dist/consolidator/phases/reflect.js +35 -8
  20. package/dist/consolidator/phases/reflect.js.map +1 -1
  21. package/dist/consolidator/phases/standard.js +199 -28
  22. package/dist/consolidator/phases/standard.js.map +1 -1
  23. package/dist/consolidator/reconcile.js +16 -15
  24. package/dist/consolidator/reconcile.js.map +1 -1
  25. package/dist/consolidator/runtime.d.ts.map +1 -1
  26. package/dist/consolidator/runtime.js +129 -47
  27. package/dist/consolidator/runtime.js.map +1 -1
  28. package/dist/consolidator/scheduler.d.ts +17 -3
  29. package/dist/consolidator/scheduler.d.ts.map +1 -1
  30. package/dist/consolidator/scheduler.js.map +1 -1
  31. package/dist/consolidator/types.d.ts +30 -1
  32. package/dist/consolidator/types.d.ts.map +1 -1
  33. package/dist/consolidator/types.js +10 -0
  34. package/dist/consolidator/types.js.map +1 -1
  35. package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
  36. package/dist/context-engine/compaction/clear-tool-results.js +1 -1
  37. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
  38. package/dist/context-engine/compaction/compactor.d.ts +1 -0
  39. package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
  40. package/dist/context-engine/compaction/compactor.js +32 -8
  41. package/dist/context-engine/compaction/compactor.js.map +1 -1
  42. package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
  43. package/dist/context-engine/compaction/index.d.ts +1 -0
  44. package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
  45. package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
  46. package/dist/context-engine/compaction/templates/marker.js +34 -0
  47. package/dist/context-engine/compaction/templates/marker.js.map +1 -0
  48. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
  49. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
  50. package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
  51. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
  52. package/dist/context-engine/engine.d.ts +7 -176
  53. package/dist/context-engine/engine.d.ts.map +1 -1
  54. package/dist/context-engine/engine.js +0 -2
  55. package/dist/context-engine/engine.js.map +1 -1
  56. package/dist/context-engine/index.d.ts +8 -49
  57. package/dist/context-engine/io-types.d.ts +144 -0
  58. package/dist/context-engine/io-types.d.ts.map +1 -0
  59. package/dist/context-engine/locale-packs/types.d.ts +1 -1
  60. package/dist/context-engine/locale-packs/types.js.map +1 -1
  61. package/dist/context-engine/privacy-filter.d.ts +1 -2
  62. package/dist/context-engine/privacy-filter.d.ts.map +1 -1
  63. package/dist/context-engine/privacy-filter.js +1 -2
  64. package/dist/context-engine/privacy-filter.js.map +1 -1
  65. package/dist/errors/index.d.ts +2 -2
  66. package/dist/errors/index.js +2 -2
  67. package/dist/errors/index.js.map +1 -1
  68. package/dist/facade.d.ts +43 -52
  69. package/dist/facade.d.ts.map +1 -1
  70. package/dist/facade.js +5 -3
  71. package/dist/facade.js.map +1 -1
  72. package/dist/graph/entity-resolver.js +1 -1
  73. package/dist/graph/entity-resolver.js.map +1 -1
  74. package/dist/index.d.ts +16 -13
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -5
  77. package/dist/index.js.map +1 -1
  78. package/dist/internal/envelope.js +57 -0
  79. package/dist/internal/envelope.js.map +1 -0
  80. package/dist/internal/injection-heuristics.js +0 -0
  81. package/dist/internal/injection-heuristics.js.map +1 -1
  82. package/dist/internal/llm-json.js +40 -0
  83. package/dist/internal/llm-json.js.map +1 -0
  84. package/dist/internal/storage-adapter.d.ts +23 -10
  85. package/dist/internal/storage-adapter.d.ts.map +1 -1
  86. package/dist/memory-interface.d.ts +131 -0
  87. package/dist/memory-interface.d.ts.map +1 -0
  88. package/dist/package.js +1 -1
  89. package/dist/package.js.map +1 -1
  90. package/dist/search/iterative.d.ts.map +1 -1
  91. package/dist/search/iterative.js +2 -3
  92. package/dist/search/iterative.js.map +1 -1
  93. package/dist/search/query-transform.d.ts.map +1 -1
  94. package/dist/search/query-transform.js +2 -3
  95. package/dist/search/query-transform.js.map +1 -1
  96. package/dist/tiers/episodic-memory.js +3 -3
  97. package/dist/tiers/episodic-memory.js.map +1 -1
  98. package/dist/tiers/index.d.ts +2 -2
  99. package/dist/tiers/insight-memory.js +1 -1
  100. package/dist/tiers/insight-memory.js.map +1 -1
  101. package/dist/tiers/procedural-memory.d.ts +1 -1
  102. package/dist/tiers/procedural-memory.js +2 -2
  103. package/dist/tiers/procedural-memory.js.map +1 -1
  104. package/dist/tiers/semantic-memory.d.ts +58 -4
  105. package/dist/tiers/semantic-memory.d.ts.map +1 -1
  106. package/dist/tiers/semantic-memory.js +103 -20
  107. package/dist/tiers/semantic-memory.js.map +1 -1
  108. package/dist/tools/block-tools.d.ts +19 -57
  109. package/dist/tools/block-tools.d.ts.map +1 -1
  110. package/dist/tools/block-tools.js.map +1 -1
  111. package/dist/tools/fact-tools.d.ts +53 -214
  112. package/dist/tools/fact-tools.d.ts.map +1 -1
  113. package/dist/tools/fact-tools.js +3 -2
  114. package/dist/tools/fact-tools.js.map +1 -1
  115. package/dist/tools/recall-tools.d.ts +34 -175
  116. package/dist/tools/recall-tools.d.ts.map +1 -1
  117. package/dist/tools/recall-tools.js +5 -4
  118. package/dist/tools/recall-tools.js.map +1 -1
  119. package/dist/tools/runbook-tools.d.ts +15 -58
  120. package/dist/tools/runbook-tools.d.ts.map +1 -1
  121. package/dist/tools/runbook-tools.js.map +1 -1
  122. package/dist/tools/types.d.ts +1 -1
  123. package/package.json +18 -17
  124. package/src/conflict/index.ts +29 -0
  125. package/src/conflict/locale-packs/en.ts +142 -0
  126. package/src/conflict/locale-packs/index.ts +17 -0
  127. package/src/conflict/locale-packs/types.ts +138 -0
  128. package/src/conflict/pipeline.ts +371 -0
  129. package/src/conflict/stages/helpers.ts +65 -0
  130. package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
  131. package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
  132. package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
  133. package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
  134. package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
  135. package/src/conflict/types.ts +263 -0
  136. package/src/consolidator/budget.ts +312 -0
  137. package/src/consolidator/decay.ts +246 -0
  138. package/src/consolidator/dlq.ts +61 -0
  139. package/src/consolidator/errors.ts +87 -0
  140. package/src/consolidator/idempotency.ts +27 -0
  141. package/src/consolidator/index.ts +269 -0
  142. package/src/consolidator/lock.ts +141 -0
  143. package/src/consolidator/noise-filter.ts +235 -0
  144. package/src/consolidator/phases/deep.ts +308 -0
  145. package/src/consolidator/phases/induce.ts +423 -0
  146. package/src/consolidator/phases/learned-context.ts +233 -0
  147. package/src/consolidator/phases/light.ts +166 -0
  148. package/src/consolidator/phases/reflect.ts +504 -0
  149. package/src/consolidator/phases/standard.ts +1002 -0
  150. package/src/consolidator/reconcile.ts +273 -0
  151. package/src/consolidator/runtime.ts +1049 -0
  152. package/src/consolidator/scheduler.ts +182 -0
  153. package/src/consolidator/triggers.ts +134 -0
  154. package/src/consolidator/types.ts +622 -0
  155. package/src/context-engine/annotations.ts +161 -0
  156. package/src/context-engine/auto-recall.ts +92 -0
  157. package/src/context-engine/compaction/clear-tool-results.ts +232 -0
  158. package/src/context-engine/compaction/compactor.ts +403 -0
  159. package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
  160. package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
  161. package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
  162. package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
  163. package/src/context-engine/compaction/hooks/types.ts +68 -0
  164. package/src/context-engine/compaction/index.ts +60 -0
  165. package/src/context-engine/compaction/templates/marker.ts +33 -0
  166. package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
  167. package/src/context-engine/compaction/thresholds.ts +107 -0
  168. package/src/context-engine/compaction/types.ts +255 -0
  169. package/src/context-engine/engine.ts +970 -0
  170. package/src/context-engine/index.ts +118 -0
  171. package/src/context-engine/io-types.ts +162 -0
  172. package/src/context-engine/locale-packs/en.ts +107 -0
  173. package/src/context-engine/locale-packs/index.ts +19 -0
  174. package/src/context-engine/locale-packs/resolver.ts +197 -0
  175. package/src/context-engine/locale-packs/types.ts +177 -0
  176. package/src/context-engine/metadata.ts +114 -0
  177. package/src/context-engine/preambles/inbound-en.ts +19 -0
  178. package/src/context-engine/privacy-filter.ts +190 -0
  179. package/src/context-engine/templates/base-en.ts +19 -0
  180. package/src/context-engine/templates/composer.ts +91 -0
  181. package/src/context-engine/token-budget.ts +319 -0
  182. package/src/context-engine/token-counter.ts +157 -0
  183. package/src/errors/index.ts +273 -0
  184. package/src/facade.ts +750 -0
  185. package/src/graph/entity-resolver.ts +364 -0
  186. package/src/graph/index.ts +24 -0
  187. package/src/index.ts +259 -0
  188. package/src/internal/contextualize.ts +173 -0
  189. package/src/internal/embedder-binding.ts +53 -0
  190. package/src/internal/envelope.ts +70 -0
  191. package/src/internal/id.ts +23 -0
  192. package/src/internal/injection-heuristics.ts +0 -0
  193. package/src/internal/llm-json.ts +38 -0
  194. package/src/internal/spans.ts +35 -0
  195. package/src/internal/storage-adapter.ts +963 -0
  196. package/src/memory-interface.ts +158 -0
  197. package/src/migration/embedder-migration.ts +237 -0
  198. package/src/migration/index.ts +13 -0
  199. package/src/search/explain.ts +123 -0
  200. package/src/search/fit-weights.ts +109 -0
  201. package/src/search/graph-ppr.ts +38 -0
  202. package/src/search/index.ts +55 -0
  203. package/src/search/iterative.ts +658 -0
  204. package/src/search/query-transform.ts +285 -0
  205. package/src/search/rrf.ts +225 -0
  206. package/src/search/trust.ts +55 -0
  207. package/src/search/types.ts +50 -0
  208. package/src/tiers/episodic-memory.ts +463 -0
  209. package/src/tiers/index.ts +50 -0
  210. package/src/tiers/insight-memory.ts +208 -0
  211. package/src/tiers/procedural-memory.ts +482 -0
  212. package/src/tiers/semantic-memory.ts +1729 -0
  213. package/src/tiers/session-memory.ts +271 -0
  214. package/src/tiers/shared-memory.ts +62 -0
  215. package/src/tiers/working-memory.ts +368 -0
  216. package/src/tools/block-tools.ts +172 -0
  217. package/src/tools/fact-tools.ts +473 -0
  218. package/src/tools/index.ts +109 -0
  219. package/src/tools/recall-tools.ts +303 -0
  220. package/src/tools/runbook-tools.ts +107 -0
  221. package/src/tools/taint.ts +39 -0
  222. package/src/tools/types.ts +37 -0
  223. package/dist/conflict/locale-packs/index.js +0 -4
  224. package/dist/context-engine/compaction/index.js +0 -10
  225. package/dist/context-engine/index.d.ts.map +0 -1
  226. package/dist/context-engine/index.js +0 -25
  227. package/dist/context-engine/locale-packs/index.js +0 -4
package/src/facade.ts ADDED
@@ -0,0 +1,750 @@
1
+ /**
2
+ * `createMemory()` - the facade that wires every tier sub-module (the
3
+ * seven-tier system) + the eleven (+1 gated) memory tools + the search
4
+ * reranker + the context engine
5
+ * stubs + the consolidator placeholder.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type {
11
+ EmbedderProvider,
12
+ MemoryMetadata,
13
+ Provider,
14
+ SessionScope,
15
+ Tracer,
16
+ } from '@graphorin/core';
17
+ import { NOOP_TRACER } from '@graphorin/core';
18
+ import { type ConflictPipelineOptions, createConflictPipeline } from './conflict/index.js';
19
+ import {
20
+ type Consolidator,
21
+ type ConsolidatorCeilings,
22
+ type ConsolidatorPhase,
23
+ type ConsolidatorTier,
24
+ type ConsolidatorTriggerSpec,
25
+ createConsolidator,
26
+ createConsolidatorPlaceholder,
27
+ createProviderWorkflowInducer,
28
+ type OnBudgetExceed,
29
+ type PhaseListener,
30
+ type SalienceWeights,
31
+ } from './consolidator/index.js';
32
+ import { createContextEngine } from './context-engine/engine.js';
33
+ import type {
34
+ CompileOptions,
35
+ CompileScope,
36
+ ContextEngine,
37
+ ContextEngineConfig,
38
+ MemoryContextBlocks,
39
+ } from './context-engine/index.js';
40
+ import { enLocalePack } from './context-engine/locale-packs/index.js';
41
+ import { resolveLocalePack } from './context-engine/locale-packs/resolver.js';
42
+ import { gatherMemoryMetadata, renderMetadataBlock } from './context-engine/metadata.js';
43
+ import { partition as partitionBySensitivity } from './context-engine/privacy-filter.js';
44
+ import { composeLayer1 } from './context-engine/templates/composer.js';
45
+ import { type EntityResolutionConfig, EntityResolver } from './graph/entity-resolver.js';
46
+ import type { ContextualRetrievalMode } from './internal/contextualize.js';
47
+ import { bindEmbedder } from './internal/embedder-binding.js';
48
+ import type { EmbeddingMetaRegistryLike, MemoryStoreAdapter } from './internal/storage-adapter.js';
49
+ import type { Memory } from './memory-interface.js';
50
+ import { createProviderRetrievalGrader } from './search/iterative.js';
51
+ import { createProviderQueryTransformer } from './search/query-transform.js';
52
+ import { RRFReranker } from './search/rrf.js';
53
+ import type { ReRanker } from './search/types.js';
54
+ import { EpisodicMemory } from './tiers/episodic-memory.js';
55
+ import { InsightMemory } from './tiers/insight-memory.js';
56
+ import { ProceduralMemory } from './tiers/procedural-memory.js';
57
+ import { SemanticMemory, type SemanticSearchDefaults } from './tiers/semantic-memory.js';
58
+ import { SessionMemory } from './tiers/session-memory.js';
59
+ import { SharedMemory } from './tiers/shared-memory.js';
60
+ import { type BlockDefinition, WorkingMemory } from './tiers/working-memory.js';
61
+ import { buildMemoryTools, type ScopeResolver } from './tools/index.js';
62
+
63
+ /**
64
+ * Options accepted by {@link createMemory}.
65
+ *
66
+ * @stable
67
+ */
68
+ export interface CreateMemoryOptions {
69
+ /** Storage adapter (default: `@graphorin/store-sqlite`'s `MemoryStore`). */
70
+ readonly store: MemoryStoreAdapter;
71
+ /** Embedder registry. The default sqlite store exposes one as `sqlite.embeddings`. */
72
+ readonly embeddings: EmbeddingMetaRegistryLike;
73
+ /** Embedder provider (default: none - vector search is disabled). */
74
+ readonly embedder?: EmbedderProvider;
75
+ /** Pre-declared working blocks (idempotent - re-defining is a no-op). */
76
+ readonly workingBlocks?: ReadonlyArray<BlockDefinition>;
77
+ /**
78
+ * Tracer used for every `memory.*` span. Defaults to the no-op
79
+ * tracer from `@graphorin/core` so unit tests do not need to wire
80
+ * the observability stack.
81
+ */
82
+ readonly tracer?: Tracer;
83
+ /** Override the reranker used by `SemanticMemory.search`. */
84
+ readonly reranker?: ReRanker;
85
+ /**
86
+ * Construction-time retrieval defaults (W-086) merged under every
87
+ * `SemanticMemory.search(...)` call, per-call options winning
88
+ * key-by-key. This is how the advanced retrieval stack (multi-query
89
+ * fan-out, HyDE, graph expansion, entity matching, weighted fusion,
90
+ * decay ranking) reaches the model-facing surfaces - `fact_search`,
91
+ * auto-recall and `deep_recall` all funnel through `search()` and
92
+ * inherit these without custom tools. Deliberately excludes the
93
+ * trust-sensitive predicates (`includeQuarantined`,
94
+ * `includeSuperseded`, `trustWeighting`, `owner`) so configuration
95
+ * cannot silently weaken trust gates. The fan-out switches only take
96
+ * effect when their backing dependency is configured (`queryTransform`
97
+ * for `multiQuery`/`hyde`, `graph` for `expandHops`/`entityMatch`) -
98
+ * and they add provider latency and cost to EVERY recall, including
99
+ * auto-recall, so opt in deliberately.
100
+ */
101
+ readonly searchDefaults?: SemanticSearchDefaults;
102
+ /**
103
+ * Contextual-retrieval mode for the write path (P1-3). `'late-chunk'`
104
+ * (default) prepends a deterministic, offline situating context
105
+ * (entities / timeframe / topics, derived from the fact's own
106
+ * structured fields) to the text that is embedded + FTS-indexed, so a
107
+ * terse fact stays findable; the canonical `text` is preserved. `'off'`
108
+ * indexes the bare text. The `'llm'` enrichment is **not** available on
109
+ * the hot path - it is a consolidator-only opt-in configured via
110
+ * `consolidator: { contextualRetrieval: 'llm' }`.
111
+ */
112
+ readonly contextualRetrieval?: 'off' | 'late-chunk';
113
+ /**
114
+ * Query transformation for retrieval (P2-3, opt-in). When supplied,
115
+ * `SemanticMemory.search(..., { multiQuery })` fans the query into
116
+ * reworded variants (multi-query / RAG-Fusion) and `{ hyde }` adds a
117
+ * hypothetical-answer embedding - both via one cheap LLM call on the
118
+ * given provider, fused through the existing RRF reranker. Omitted (the
119
+ * default) ⇒ search stays **offline + single-shot** and the
120
+ * `multiQuery` / `hyde` search options become silent no-ops. Reserve it
121
+ * for retrieval-heavy recall, not every search (it adds provider
122
+ * latency).
123
+ */
124
+ readonly queryTransform?: {
125
+ /** Cheap provider used to rewrite the query / write the HyDE passage. */
126
+ readonly provider: Provider;
127
+ /** Hard ceiling on reworded variants requested per call. Default 5. */
128
+ readonly maxVariants?: number;
129
+ /** Output-token ceiling per transform call. Default 256. */
130
+ readonly maxTokens?: number;
131
+ };
132
+ /**
133
+ * Relation-graph entity resolution (P2-1). When `entityResolution` is
134
+ * `true` **and** the storage adapter exposes a `graph` surface (the
135
+ * default `@graphorin/store-sqlite` does), `remember(...)` resolves a
136
+ * fact's subject / object to canonical entities and links them, so
137
+ * `search(..., { expandHops: 1 })` can traverse relationships. Omitted
138
+ * (the default) ⇒ facts still carry s/p/o but form no entity links and
139
+ * the write path stays offline + unchanged. Dedup is lexical +
140
+ * embedding (offline, via the configured embedder); LLM adjudication of
141
+ * ambiguous merges is a further opt-in that needs `provider`.
142
+ */
143
+ readonly graph?: EntityResolutionConfig & {
144
+ /** Enable entity resolution + linking on write. Default `false`. */
145
+ readonly entityResolution?: boolean;
146
+ /** Provider for opt-in LLM adjudication of ambiguous merges. */
147
+ readonly provider?: Provider;
148
+ };
149
+ /**
150
+ * Agentic / iterative retrieval (P2-4, opt-in). When supplied,
151
+ * `SemanticMemory.searchIterative(...)` and the gated `deep_recall`
152
+ * tool can grade a retrieved set on the given provider and, for queries
153
+ * judged hard, reformulate + retrieve again (widening to one-hop graph
154
+ * expansion) up to `maxIterations`, abstaining instead of confabulating
155
+ * when memory is insufficient. Omitted (the default) ⇒ `searchIterative`
156
+ * stays a single difficulty-gated pass with **no provider call**, and
157
+ * `deep_recall` is **not** registered (the tool surface stays at the
158
+ * canonical eleven). Reserve it for hard multi-hop / temporal recall -
159
+ * it adds provider latency per pass.
160
+ */
161
+ readonly iterativeRetrieval?: {
162
+ /** Cheap provider used to grade retrieved memories + reformulate. */
163
+ readonly provider: Provider;
164
+ /** Default total-pass cap (clamped to `[1, 5]`). Default 3. */
165
+ readonly maxIterations?: number;
166
+ /** Output-token ceiling per grade call. Default 256. */
167
+ readonly maxTokens?: number;
168
+ /**
169
+ * Default difficulty-gate threshold in `[0, 1]` (W-088). The gate's
170
+ * signal lexicon is **English-only**: on non-English deployments the
171
+ * auto-gate never fires, so either lower this threshold or rely on
172
+ * `forceHard` (`deep_recall` already forces the loop). Omitted ⇒ the
173
+ * built-in `0.5`; per-call `difficultyThreshold` overrides it.
174
+ */
175
+ readonly difficultyThreshold?: number;
176
+ };
177
+ /**
178
+ * Opt-in workflow induction (P2-2). When set, `ProceduralMemory.induce(...)`
179
+ * distils a reusable, value-abstracted procedure from a successful agent
180
+ * trajectory and stores it **quarantined** + `provenance: 'induction'`.
181
+ * Omitted (the default) ⇒ `induce(...)` throws
182
+ * `ProcedureInductionNotConfiguredError` and the procedural tier
183
+ * stays pure offline CRUD - no provider call.
184
+ */
185
+ readonly procedureInduction?: {
186
+ /** Provider used to abstract trajectory values into a procedure. */
187
+ readonly provider: Provider;
188
+ /** Output-token ceiling per induction call. Default 512. */
189
+ readonly maxTokens?: number;
190
+ };
191
+ /**
192
+ * Promotion-by-demonstrated-success for quarantined induced
193
+ * procedures (MCON-2 part 4). Fully offline - orthogonal to
194
+ * `procedureInduction` (no provider needed). When configured,
195
+ * `procedural.recordOutcome(scope, id, true)` increments the rule's
196
+ * persistent success counter and promotes it into `activate()` once
197
+ * `afterSuccesses` verified reuses accumulate; the injection gate
198
+ * still refuses flagged texts. Omitted ⇒ outcomes are counted but
199
+ * nothing auto-promotes.
200
+ */
201
+ readonly procedurePromotion?: {
202
+ /** Successful demonstrated reuses required before promotion (≥ 1). */
203
+ readonly afterSuccesses: number;
204
+ };
205
+ /**
206
+ * Register the gated `runbook_search` tool (D3) so the model can look
207
+ * up validated procedures by task description (content recall over
208
+ * procedural memory, returning whole runbooks). Fully offline - the
209
+ * default `@graphorin/store-sqlite` adapter serves it from the rules
210
+ * FTS index (migration 028); adapters without the index degrade to an
211
+ * in-memory lexical scan. Default `false` - the tool surface stays at
212
+ * the canonical eleven.
213
+ */
214
+ readonly runbookSearch?: boolean;
215
+ /**
216
+ * Resolver that produces the live {@link SessionScope} for each
217
+ * memory-tool invocation. Defaults to a closure that throws - the
218
+ * agent runtime overrides it in Phase 12.
219
+ */
220
+ readonly resolveScope?: ScopeResolver;
221
+ /**
222
+ * Consolidator configuration. When omitted, empty, or
223
+ * `enabled: false`, the facade installs the Phase 10a no-op
224
+ * placeholder so consumers can still type their interactions without
225
+ * paying the runtime cost. ANY other setting - including offline
226
+ * knobs like `decayCapacity` or `contextualRetrieval` - implicitly
227
+ * enables the production runtime (MST-4); `enabled: false` together
228
+ * with other settings warns once and keeps the placeholder.
229
+ */
230
+ readonly consolidator?: {
231
+ readonly enabled?: boolean;
232
+ readonly triggers?: ReadonlyArray<ConsolidatorTriggerSpec>;
233
+ readonly tier?: ConsolidatorTier;
234
+ readonly phases?: ReadonlyArray<ConsolidatorPhase>;
235
+ readonly ceilings?: Partial<ConsolidatorCeilings>;
236
+ readonly onExceed?: OnBudgetExceed;
237
+ /**
238
+ * USD pricer for phase LLM usage (memory-consolidation-02) - wire
239
+ * to `@graphorin/pricing` so `maxCostPerDay` can actually trip.
240
+ */
241
+ readonly priceUsage?: (usage: { promptTokens: number; completionTokens: number }) => number;
242
+ /** Provider routed to the standard phase when set (MCON-7); falls back to `provider`. */
243
+ readonly cheapProvider?: Provider | null;
244
+ /** Provider routed to the deep + reflection passes when set (MCON-7). */
245
+ readonly deepProvider?: Provider | null;
246
+ readonly cheapModel?: string | null;
247
+ readonly deepModel?: string | null;
248
+ readonly noiseFilters?: ReadonlyArray<'default' | 'minimal' | 'none'>;
249
+ readonly lockWaitMs?: number;
250
+ readonly decayTauDays?: number;
251
+ readonly decayArchiveThreshold?: number;
252
+ /** Capacity-bounded eviction target for the light phase (X-1). Default unbounded. */
253
+ readonly decayCapacity?: number | null;
254
+ /** Weights for the multi-signal salience score (X-1). */
255
+ readonly salienceWeights?: SalienceWeights;
256
+ readonly maxStandardBatchSize?: number;
257
+ /**
258
+ * Input transcript budget for one standard-phase slice, in
259
+ * characters (W-081). Over-budget batches are half-split before the
260
+ * provider call; a lone over-budget message is tail-truncated.
261
+ * Per-tier default (60k chars ~ 15k tokens; 120k on `full`).
262
+ */
263
+ readonly maxTranscriptChars?: number;
264
+ readonly maxDeepConflictsPerRun?: number;
265
+ readonly dlqMaxRetries?: number;
266
+ readonly dlqBaseBackoffMs?: number;
267
+ readonly dlqMaxBackoffMs?: number;
268
+ /** Auto-form quarantined episodes from processed slices (P1-2). Per-tier default. */
269
+ readonly formEpisodes?: boolean;
270
+ /** Score episode importance via the consolidator LLM (P1-2). Per-tier default. */
271
+ readonly importanceScoring?: boolean;
272
+ /** Opt in to auto-promotion of injection-clean extraction facts (MCON-2). Default off. */
273
+ readonly autoPromoteExtraction?: boolean;
274
+ /** Run the deep-phase reflection pass synthesizing cited insights (P1-1). Per-tier default. */
275
+ readonly reflection?: boolean;
276
+ /** Accumulated-importance threshold at which reflection fires (P1-1). */
277
+ readonly importanceThreshold?: number;
278
+ /** Upper bound on salient questions reflection asks per pass (P1-1). */
279
+ readonly reflectionMaxQuestions?: number;
280
+ /** Override the quarantined-insight queue cap (W-082). Default 100. */
281
+ readonly reflectionMaxQuarantinedInsights?: number;
282
+ /**
283
+ * Contextual retrieval for standard-phase fact writes (P1-3).
284
+ * `'llm'` opts into one budgeted cheap-model call per write to author
285
+ * a situating prefix (consolidator-only); `'late-chunk'` (default)
286
+ * and `'off'` defer to the write-path mode. Per-tier default.
287
+ */
288
+ readonly contextualRetrieval?: ContextualRetrievalMode;
289
+ /**
290
+ * Maintain the learned-context digest block (D3): after each deep
291
+ * phase, one budgeted LLM call rewrites the reserved
292
+ * `learned_context` working block (previous digest + recent
293
+ * episodes / active insights / procedures), so the assembled system
294
+ * prompt carries a compact standing summary. Default `false` at
295
+ * every tier (Wave-D trial).
296
+ */
297
+ readonly learnedContext?: boolean;
298
+ /** Character bound for the learned-context digest (D3). Default `1200`. */
299
+ readonly learnedContextMaxChars?: number;
300
+ readonly defaultScope?: SessionScope;
301
+ readonly provider?: Provider | null;
302
+ /** Override the wall clock - used by tests. */
303
+ readonly now?: () => number;
304
+ /** Stable id seed - used by tests. */
305
+ readonly randomId?: () => string;
306
+ /** Subscribe to phase-finished events. */
307
+ readonly onPhaseFinished?: PhaseListener;
308
+ };
309
+ /**
310
+ * Conflict pipeline configuration (Phase 10b). Default: enabled,
311
+ * English locale pack, thresholds `0.95 / 0.85 / 0.4`. Pass
312
+ * `{ mode: 'off' }` to bypass the pipeline entirely (logs a one-shot
313
+ * WARN per process per the spec).
314
+ */
315
+ readonly conflictPipeline?: ConflictPipelineOptions;
316
+ /**
317
+ * Context engine configuration (Phase 10d). The engine assembles
318
+ * the layered six-layer system prompt; `memory.compile(scope)`
319
+ * delegates to it for the working blocks + rules + metadata
320
+ * fragments. When omitted, a default engine is created (English
321
+ * locale; `'full'` base mode; no auto-recall; conservative
322
+ * `'public-tls'` provider trust).
323
+ */
324
+ readonly contextEngine?: ContextEngineConfig;
325
+ }
326
+
327
+ // The `Memory` interface is co-located with `ContextEngine` in
328
+ // memory-interface.ts (issue #22 - the pair is mutually recursive and
329
+ // keeping it here put a type-only cycle into the module graph).
330
+ // Re-exported so `import type { Memory } from './facade.js'` and the
331
+ // public barrel keep working unchanged.
332
+ export type { Memory } from './memory-interface.js';
333
+
334
+ /**
335
+ * Wire every memory subsystem in one call. Returns the typed
336
+ * `Memory` facade ready to be passed into `createAgent({...})`.
337
+ *
338
+ * @stable
339
+ */
340
+ export function createMemory(options: CreateMemoryOptions): Memory {
341
+ const tracer = options.tracer ?? NOOP_TRACER;
342
+ const reranker: ReRanker = options.reranker ?? new RRFReranker();
343
+ const resolveScope: ScopeResolver =
344
+ options.resolveScope ??
345
+ (() => {
346
+ throw new TypeError(
347
+ '[graphorin/memory] memory tool invoked without a scope resolver. ' +
348
+ 'Pass `resolveScope` to createMemory({...}) - the agent runtime supplies one in Phase 12.',
349
+ );
350
+ });
351
+
352
+ let activeEmbedderId: string | null = null;
353
+ if (options.embedder !== undefined) {
354
+ activeEmbedderId = bindEmbedder(options.embedder, options.embeddings);
355
+ }
356
+ const embedder = options.embedder ?? null;
357
+ const embedderIdProvider = (): string | null => activeEmbedderId;
358
+
359
+ const working = new WorkingMemory({ store: options.store, tracer });
360
+ for (const block of options.workingBlocks ?? []) {
361
+ working.define(block);
362
+ }
363
+ const session = new SessionMemory({ store: options.store, tracer });
364
+ const episodic = new EpisodicMemory({
365
+ store: options.store,
366
+ tracer,
367
+ embedder,
368
+ embedderIdProvider,
369
+ });
370
+ const conflictPipeline = createConflictPipeline(options.conflictPipeline ?? {});
371
+ // P2-3: build the (opt-in) query transformer from the supplied provider.
372
+ // Absent ⇒ `null` ⇒ search stays offline + single-shot.
373
+ const queryTransformer =
374
+ options.queryTransform !== undefined
375
+ ? createProviderQueryTransformer(options.queryTransform.provider, {
376
+ ...(options.queryTransform.maxVariants !== undefined
377
+ ? { maxVariants: options.queryTransform.maxVariants }
378
+ : {}),
379
+ ...(options.queryTransform.maxTokens !== undefined
380
+ ? { maxTokens: options.queryTransform.maxTokens }
381
+ : {}),
382
+ })
383
+ : null;
384
+ // P2-1: build the (opt-in) entity resolver. Requires `graph.entityResolution`
385
+ // *and* a graph-capable adapter; absent ⇒ `null` ⇒ writes carry s/p/o but
386
+ // form no entity links and the write path stays offline + unchanged.
387
+ const graphStore = options.store.graph;
388
+ const entityResolver =
389
+ options.graph?.entityResolution === true && graphStore !== undefined
390
+ ? new EntityResolver({
391
+ store: graphStore,
392
+ embedder,
393
+ embedderId: embedderIdProvider,
394
+ ...(options.graph.provider !== undefined ? { provider: options.graph.provider } : {}),
395
+ config: {
396
+ ...(options.graph.mergeThreshold !== undefined
397
+ ? { mergeThreshold: options.graph.mergeThreshold }
398
+ : {}),
399
+ ...(options.graph.adjudicateThreshold !== undefined
400
+ ? { adjudicateThreshold: options.graph.adjudicateThreshold }
401
+ : {}),
402
+ ...(options.graph.llmAdjudication !== undefined
403
+ ? { llmAdjudication: options.graph.llmAdjudication }
404
+ : {}),
405
+ },
406
+ })
407
+ : null;
408
+ // P2-4: build the (opt-in) retrieval grader. Absent ⇒ `null` ⇒
409
+ // `searchIterative` runs a single difficulty-gated pass (no provider
410
+ // call) and the `deep_recall` tool is not registered.
411
+ const grader =
412
+ options.iterativeRetrieval !== undefined
413
+ ? createProviderRetrievalGrader(options.iterativeRetrieval.provider, {
414
+ ...(options.iterativeRetrieval.maxTokens !== undefined
415
+ ? { maxTokens: options.iterativeRetrieval.maxTokens }
416
+ : {}),
417
+ })
418
+ : null;
419
+ const semantic = new SemanticMemory({
420
+ store: options.store,
421
+ tracer,
422
+ embedder,
423
+ embedderIdProvider,
424
+ reranker,
425
+ conflictPipeline,
426
+ ...(options.contextualRetrieval !== undefined
427
+ ? { contextualRetrieval: options.contextualRetrieval }
428
+ : {}),
429
+ ...(queryTransformer !== null ? { queryTransformer } : {}),
430
+ ...(entityResolver !== null ? { entityResolver } : {}),
431
+ ...(grader !== null ? { grader } : {}),
432
+ ...(options.iterativeRetrieval?.maxIterations !== undefined
433
+ ? { iterativeMaxIterations: options.iterativeRetrieval.maxIterations }
434
+ : {}),
435
+ ...(options.iterativeRetrieval?.difficultyThreshold !== undefined
436
+ ? { iterativeDifficultyThreshold: options.iterativeRetrieval.difficultyThreshold }
437
+ : {}),
438
+ // C5: the same weights drive eviction salience and rank-time trust.
439
+ ...(options.consolidator?.salienceWeights !== undefined
440
+ ? { trustWeights: options.consolidator.salienceWeights }
441
+ : {}),
442
+ // W-086: construction-time retrieval defaults for every search().
443
+ ...(options.searchDefaults !== undefined ? { searchDefaults: options.searchDefaults } : {}),
444
+ });
445
+ // P2-2: build the (opt-in) workflow inducer. Absent ⇒ `null` ⇒
446
+ // `ProceduralMemory.induce(...)` throws and the tier stays offline CRUD.
447
+ // MCON-15: induction spend is recorded into the consolidator budget -
448
+ // the consolidator is constructed AFTER the inducer, so the callback
449
+ // routes through a slot filled below (placeholder ⇒ no-op).
450
+ let consolidatorForSpend: Consolidator | null = null;
451
+ const inducer =
452
+ options.procedureInduction !== undefined
453
+ ? createProviderWorkflowInducer(options.procedureInduction.provider, {
454
+ ...(options.procedureInduction.maxTokens !== undefined
455
+ ? { maxTokens: options.procedureInduction.maxTokens }
456
+ : {}),
457
+ onUsage: (usage) => {
458
+ consolidatorForSpend?.recordExternalSpend(usage.promptTokens + usage.completionTokens);
459
+ },
460
+ })
461
+ : null;
462
+ const procedural = new ProceduralMemory({
463
+ store: options.store,
464
+ tracer,
465
+ ...(inducer !== null ? { inducer } : {}),
466
+ // MCON-2 part 4: opt-in promotion-by-demonstrated-success.
467
+ ...(options.procedurePromotion?.afterSuccesses !== undefined
468
+ ? { promoteAfterSuccesses: options.procedurePromotion.afterSuccesses }
469
+ : {}),
470
+ });
471
+ const shared = new SharedMemory({ store: options.store, tracer });
472
+ const insights = new InsightMemory({ store: options.store, tracer });
473
+
474
+ const tools = buildMemoryTools(
475
+ {
476
+ working,
477
+ session,
478
+ episodic,
479
+ semantic,
480
+ procedural,
481
+ shared,
482
+ resolveScope,
483
+ },
484
+ // P2-4: the gated `deep_recall` tool is registered only when a grader
485
+ // is configured - the offline default stays at exactly eleven tools.
486
+ // D3: `runbook_search` is a second gated appendix, opt-in via
487
+ // `runbookSearch: true`.
488
+ {
489
+ includeDeepRecall: grader !== null,
490
+ includeRunbookSearch: options.runbookSearch === true,
491
+ },
492
+ );
493
+
494
+ const consolidatorOpts = options.consolidator;
495
+ const consolidator: Consolidator = buildConsolidator(
496
+ consolidatorOpts,
497
+ options.store,
498
+ semantic,
499
+ episodic,
500
+ working,
501
+ tracer,
502
+ );
503
+ consolidatorForSpend = consolidator;
504
+ const contextEngineConfig = options.contextEngine ?? {};
505
+ const contextEngine: ContextEngine = createContextEngine(contextEngineConfig);
506
+ const resolved = contextEngine.config();
507
+ const localePack =
508
+ contextEngineConfig.locale === undefined
509
+ ? enLocalePack
510
+ : typeof contextEngineConfig.locale === 'string'
511
+ ? contextEngineConfig.locale === enLocalePack.id
512
+ ? enLocalePack
513
+ : resolveLocalePack({ id: contextEngineConfig.locale })
514
+ : resolveLocalePack(contextEngineConfig.locale);
515
+
516
+ // The facade's `compile(...)` is the static-fragment surface
517
+ // consumed by callers that do not know the active provider yet
518
+ // (the agent runtime calls `memory.contextEngine.assemble(...)`
519
+ // with full provider context when `autoAssembleContext` is
520
+ // enabled). The privacy filter only fires
521
+ // when the caller explicitly passes `providerAcceptsSensitivity`
522
+ // OR the operator opted into the engine's `privacy` block.
523
+ const privacyOptedIn =
524
+ contextEngineConfig.privacy !== undefined &&
525
+ (contextEngineConfig.privacy.providerAcceptsSensitivity !== undefined ||
526
+ contextEngineConfig.privacy.cloudUploadConsent !== undefined ||
527
+ contextEngineConfig.privacy.providerTrust !== undefined);
528
+
529
+ async function compile(
530
+ scope: CompileScope,
531
+ compileOptions: CompileOptions = {},
532
+ ): Promise<MemoryContextBlocks> {
533
+ const out: { -readonly [K in keyof MemoryContextBlocks]: MemoryContextBlocks[K] } = {};
534
+ const blocks = await options.store.working.list(scope);
535
+ // Quarantined (e.g. P2-2-induced) procedures are provisional and must not
536
+ // reach the system prompt - `activate()` already excludes them, and
537
+ // compile() (public `@stable`) must agree or a compile()-based prompt
538
+ // builder ingests unvalidated induction procedures (MST-3).
539
+ const rules = (await options.store.procedural.list(scope)).filter(
540
+ (rule) => rule.status !== 'quarantined',
541
+ );
542
+ const shouldFilter = privacyOptedIn || compileOptions.providerAcceptsSensitivity !== undefined;
543
+ let blocksKept: ReadonlyArray<(typeof blocks)[number]> = blocks;
544
+ let rulesKept: ReadonlyArray<(typeof rules)[number]> = rules;
545
+ if (shouldFilter) {
546
+ const filterContext: Parameters<typeof partitionBySensitivity>[1] = {
547
+ providerTrust: resolved.providerTrust,
548
+ cloudUploadConsent: resolved.cloudUploadConsent,
549
+ defaultSensitivity: resolved.defaultSensitivity,
550
+ };
551
+ if (compileOptions.providerAcceptsSensitivity !== undefined) {
552
+ (
553
+ filterContext as {
554
+ providerAcceptsSensitivity?: ReadonlyArray<'public' | 'internal' | 'secret'>;
555
+ }
556
+ ).providerAcceptsSensitivity = compileOptions.providerAcceptsSensitivity;
557
+ }
558
+ blocksKept = partitionBySensitivity(blocks, filterContext).kept;
559
+ rulesKept = partitionBySensitivity(rules, filterContext).kept;
560
+ }
561
+ const blocksXml = renderWorkingBlocksXml(blocksKept);
562
+ if (blocksXml.length > 0) {
563
+ out.workingBlocks = blocksXml;
564
+ }
565
+ if (rulesKept.length > 0) {
566
+ const lines = ['<memory_rules>'];
567
+ for (const rule of rulesKept) {
568
+ lines.push(` <rule priority="${rule.priority}">${escapeXml(rule.text)}</rule>`);
569
+ }
570
+ lines.push('</memory_rules>');
571
+ out.rules = lines.join('\n');
572
+ }
573
+ if (compileOptions.includeMetadata !== false) {
574
+ const meta = await metadata(scope);
575
+ out.metadata = renderMetadataBlock(meta);
576
+ }
577
+ out.base = composeLayer1(localePack, resolved.memoryBaseMode);
578
+ return Object.freeze(out);
579
+ }
580
+
581
+ async function metadata(scope: SessionScope): Promise<MemoryMetadata> {
582
+ return gatherMemoryMetadata(scope, {
583
+ store: options.store,
584
+ consolidator,
585
+ embedderId: embedderIdProvider,
586
+ localeId: resolved.localeId,
587
+ });
588
+ }
589
+
590
+ return Object.freeze({
591
+ working,
592
+ session,
593
+ episodic,
594
+ semantic,
595
+ procedural,
596
+ shared,
597
+ insights,
598
+ tools,
599
+ consolidator,
600
+ conflictPipeline,
601
+ contextEngine,
602
+ embedder,
603
+ embedderId: embedderIdProvider,
604
+ compile,
605
+ metadata,
606
+ });
607
+ }
608
+
609
+ function renderWorkingBlocksXml(
610
+ blocks: ReadonlyArray<{
611
+ readonly label: string;
612
+ readonly value: string;
613
+ readonly description?: string;
614
+ }>,
615
+ ): string {
616
+ if (blocks.length === 0) return '';
617
+ const lines = ['<memory_blocks>'];
618
+ for (const block of blocks) {
619
+ const description =
620
+ block.description !== undefined ? ` description="${escapeXml(block.description)}"` : '';
621
+ lines.push(` <block label="${escapeXml(block.label)}"${description}>`);
622
+ lines.push(` ${escapeXml(block.value)}`);
623
+ lines.push(' </block>');
624
+ }
625
+ lines.push('</memory_blocks>');
626
+ return lines.join('\n');
627
+ }
628
+
629
+ function escapeXml(value: string): string {
630
+ return value
631
+ .replace(/&/g, '&amp;')
632
+ .replace(/</g, '&lt;')
633
+ .replace(/>/g, '&gt;')
634
+ .replace(/"/g, '&quot;');
635
+ }
636
+
637
+ function buildConsolidator(
638
+ opts: CreateMemoryOptions['consolidator'],
639
+ store: CreateMemoryOptions['store'],
640
+ semantic: SemanticMemory,
641
+ episodic: EpisodicMemory,
642
+ working: WorkingMemory,
643
+ tracer: Tracer,
644
+ ): Consolidator {
645
+ if (opts === undefined) {
646
+ return createConsolidatorPlaceholder();
647
+ }
648
+ const enabled = shouldEnableConsolidator(opts);
649
+ if (!enabled) {
650
+ return createConsolidatorPlaceholder({
651
+ ...(opts.triggers !== undefined ? { triggers: opts.triggers } : {}),
652
+ ...(opts.tier !== undefined ? { tier: opts.tier } : {}),
653
+ });
654
+ }
655
+ const consolidator = createConsolidator({
656
+ store,
657
+ semantic,
658
+ episodic,
659
+ working,
660
+ tracer,
661
+ ...(opts.provider !== undefined ? { provider: opts.provider } : {}),
662
+ ...(opts.now !== undefined ? { now: opts.now } : {}),
663
+ ...(opts.randomId !== undefined ? { randomId: opts.randomId } : {}),
664
+ ...(opts.triggers !== undefined ? { triggers: opts.triggers } : {}),
665
+ ...(opts.tier !== undefined ? { tier: opts.tier } : {}),
666
+ ...(opts.phases !== undefined ? { phases: opts.phases } : {}),
667
+ ...(opts.ceilings !== undefined ? { ceilings: opts.ceilings } : {}),
668
+ ...(opts.onExceed !== undefined ? { onExceed: opts.onExceed } : {}),
669
+ ...(opts.priceUsage !== undefined ? { priceUsage: opts.priceUsage } : {}),
670
+ ...(opts.cheapProvider !== undefined ? { cheapProvider: opts.cheapProvider } : {}),
671
+ ...(opts.deepProvider !== undefined ? { deepProvider: opts.deepProvider } : {}),
672
+ ...(opts.cheapModel !== undefined ? { cheapModel: opts.cheapModel } : {}),
673
+ ...(opts.deepModel !== undefined ? { deepModel: opts.deepModel } : {}),
674
+ ...(opts.noiseFilters !== undefined ? { noiseFilters: opts.noiseFilters } : {}),
675
+ ...(opts.lockWaitMs !== undefined ? { lockWaitMs: opts.lockWaitMs } : {}),
676
+ ...(opts.decayTauDays !== undefined ? { decayTauDays: opts.decayTauDays } : {}),
677
+ ...(opts.decayArchiveThreshold !== undefined
678
+ ? { decayArchiveThreshold: opts.decayArchiveThreshold }
679
+ : {}),
680
+ ...(opts.decayCapacity !== undefined ? { decayCapacity: opts.decayCapacity } : {}),
681
+ ...(opts.salienceWeights !== undefined ? { salienceWeights: opts.salienceWeights } : {}),
682
+ ...(opts.maxStandardBatchSize !== undefined
683
+ ? { maxStandardBatchSize: opts.maxStandardBatchSize }
684
+ : {}),
685
+ ...(opts.maxTranscriptChars !== undefined
686
+ ? { maxTranscriptChars: opts.maxTranscriptChars }
687
+ : {}),
688
+ ...(opts.maxDeepConflictsPerRun !== undefined
689
+ ? { maxDeepConflictsPerRun: opts.maxDeepConflictsPerRun }
690
+ : {}),
691
+ ...(opts.dlqMaxRetries !== undefined ? { dlqMaxRetries: opts.dlqMaxRetries } : {}),
692
+ ...(opts.dlqBaseBackoffMs !== undefined ? { dlqBaseBackoffMs: opts.dlqBaseBackoffMs } : {}),
693
+ ...(opts.dlqMaxBackoffMs !== undefined ? { dlqMaxBackoffMs: opts.dlqMaxBackoffMs } : {}),
694
+ ...(opts.formEpisodes !== undefined ? { formEpisodes: opts.formEpisodes } : {}),
695
+ ...(opts.importanceScoring !== undefined ? { importanceScoring: opts.importanceScoring } : {}),
696
+ ...(opts.autoPromoteExtraction !== undefined
697
+ ? { autoPromoteExtraction: opts.autoPromoteExtraction }
698
+ : {}),
699
+ ...(opts.reflection !== undefined ? { reflection: opts.reflection } : {}),
700
+ ...(opts.importanceThreshold !== undefined
701
+ ? { importanceThreshold: opts.importanceThreshold }
702
+ : {}),
703
+ ...(opts.reflectionMaxQuestions !== undefined
704
+ ? { reflectionMaxQuestions: opts.reflectionMaxQuestions }
705
+ : {}),
706
+ ...(opts.reflectionMaxQuarantinedInsights !== undefined
707
+ ? { reflectionMaxQuarantinedInsights: opts.reflectionMaxQuarantinedInsights }
708
+ : {}),
709
+ ...(opts.contextualRetrieval !== undefined
710
+ ? { contextualRetrieval: opts.contextualRetrieval }
711
+ : {}),
712
+ ...(opts.learnedContext !== undefined ? { learnedContext: opts.learnedContext } : {}),
713
+ ...(opts.learnedContextMaxChars !== undefined
714
+ ? { learnedContextMaxChars: opts.learnedContextMaxChars }
715
+ : {}),
716
+ ...(opts.defaultScope !== undefined ? { defaultScope: opts.defaultScope } : {}),
717
+ });
718
+ if (opts.onPhaseFinished !== undefined) {
719
+ consolidator.onPhaseFinished(opts.onPhaseFinished);
720
+ }
721
+ return consolidator;
722
+ }
723
+
724
+ let consolidatorConfigIgnoredWarned = false;
725
+
726
+ /** @internal - test seam for the one-time disabled-config warning. */
727
+ export function _resetConsolidatorConfigWarningForTesting(): void {
728
+ consolidatorConfigIgnoredWarned = false;
729
+ }
730
+
731
+ function shouldEnableConsolidator(opts: NonNullable<CreateMemoryOptions['consolidator']>): boolean {
732
+ // MST-4: ANY non-empty consolidator config implicitly enables - the old
733
+ // allow-list silently ignored offline knobs (`decayCapacity`,
734
+ // `formEpisodes`, `reflection`, `contextualRetrieval`, …): the caller
735
+ // got a no-op placeholder while believing the feature was on.
736
+ const keys = Object.keys(opts).filter(
737
+ (k) => k !== 'enabled' && (opts as Record<string, unknown>)[k] !== undefined,
738
+ );
739
+ if (opts.enabled === false) {
740
+ if (keys.length > 0 && !consolidatorConfigIgnoredWarned) {
741
+ consolidatorConfigIgnoredWarned = true;
742
+ process.stderr.write(
743
+ `[graphorin/memory] consolidator config (${keys.join(', ')}) was supplied together with enabled: false - the settings are ignored until the consolidator is enabled.\n`,
744
+ );
745
+ }
746
+ return false;
747
+ }
748
+ if (opts.enabled === true) return true;
749
+ return keys.length > 0;
750
+ }