@remnic/core 9.3.733 → 9.3.735

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 (304) hide show
  1. package/dist/access-boundary.d.ts +13 -11
  2. package/dist/access-boundary.js +9 -9
  3. package/dist/access-cli.js +26 -24
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +13 -11
  6. package/dist/access-http.js +14 -14
  7. package/dist/access-mcp.d.ts +13 -11
  8. package/dist/access-mcp.js +13 -13
  9. package/dist/access-operations-batch.js +11 -11
  10. package/dist/access-operations.d.ts +13 -11
  11. package/dist/access-operations.js +12 -12
  12. package/dist/access-schema.d.ts +4 -4
  13. package/dist/{access-service-Byoe3HsT.d.ts → access-service-CVx7mmvK.d.ts} +4 -4
  14. package/dist/access-service.d.ts +13 -11
  15. package/dist/access-service.js +8 -8
  16. package/dist/access-surface-catalog.d.ts +13 -11
  17. package/dist/action-confidence.d.ts +1 -1
  18. package/dist/active-memory-bridge.d.ts +1 -1
  19. package/dist/active-memory-bridge.js +3 -2
  20. package/dist/active-recall.d.ts +1 -1
  21. package/dist/behavior-learner.d.ts +1 -1
  22. package/dist/behavior-signals.d.ts +1 -1
  23. package/dist/bootstrap.d.ts +12 -10
  24. package/dist/briefing.d.ts +1 -1
  25. package/dist/buffer-surprise-report.d.ts +1 -1
  26. package/dist/buffer.d.ts +1 -1
  27. package/dist/calibration.d.ts +1 -1
  28. package/dist/capabilities.d.ts +196 -2
  29. package/dist/capabilities.js +21 -3
  30. package/dist/{catalog-DM1nk7cM.d.ts → catalog-BVhDEUhT.d.ts} +2 -2
  31. package/dist/causal-behavior.d.ts +1 -1
  32. package/dist/causal-consolidation.d.ts +1 -1
  33. package/dist/causal-trajectory-graph.d.ts +1 -1
  34. package/dist/{chunk-CX77GNMB.js → chunk-2T2N7ISV.js} +6 -3
  35. package/dist/chunk-2T2N7ISV.js.map +1 -0
  36. package/dist/{chunk-HHNSLPX7.js → chunk-5BOQNB2K.js} +10 -9
  37. package/dist/chunk-5BOQNB2K.js.map +1 -0
  38. package/dist/{chunk-QZHF4BWN.js → chunk-AAIHDIMD.js} +6 -6
  39. package/dist/{chunk-H3MB3F62.js → chunk-AB2E5DCN.js} +4 -3
  40. package/dist/chunk-AB2E5DCN.js.map +1 -0
  41. package/dist/{chunk-KSJCXXOU.js → chunk-AE7NTZ75.js} +6 -3
  42. package/dist/chunk-AE7NTZ75.js.map +1 -0
  43. package/dist/{chunk-OTQE66IX.js → chunk-AESC5OQJ.js} +6 -3
  44. package/dist/chunk-AESC5OQJ.js.map +1 -0
  45. package/dist/{chunk-FFUCCKPN.js → chunk-BXPG5AVX.js} +10 -7
  46. package/dist/chunk-BXPG5AVX.js.map +1 -0
  47. package/dist/{chunk-P3VJCTVE.js → chunk-CHJIWG2H.js} +4 -3
  48. package/dist/chunk-CHJIWG2H.js.map +1 -0
  49. package/dist/{chunk-FYWUNVFZ.js → chunk-E4LYJ7PY.js} +6 -6
  50. package/dist/{chunk-NIZQH6AU.js → chunk-FF36YNKY.js} +8 -7
  51. package/dist/chunk-FF36YNKY.js.map +1 -0
  52. package/dist/{chunk-OUITHS6V.js → chunk-FLCYY5JX.js} +801 -711
  53. package/dist/chunk-FLCYY5JX.js.map +1 -0
  54. package/dist/{chunk-PLBIPT6I.js → chunk-FLPKEMSO.js} +7 -4
  55. package/dist/chunk-FLPKEMSO.js.map +1 -0
  56. package/dist/{chunk-6TUWSUUD.js → chunk-G54IDCD3.js} +59 -53
  57. package/dist/chunk-G54IDCD3.js.map +1 -0
  58. package/dist/{chunk-J6BH2OBA.js → chunk-GB2FZOWB.js} +8 -5
  59. package/dist/chunk-GB2FZOWB.js.map +1 -0
  60. package/dist/{chunk-CGPHMNL4.js → chunk-GYICIICQ.js} +10 -6
  61. package/dist/chunk-GYICIICQ.js.map +1 -0
  62. package/dist/{chunk-DKT3X5C3.js → chunk-HFXZIF3V.js} +7 -4
  63. package/dist/chunk-HFXZIF3V.js.map +1 -0
  64. package/dist/{chunk-646B5ERJ.js → chunk-HLPYL6LC.js} +71 -65
  65. package/dist/chunk-HLPYL6LC.js.map +1 -0
  66. package/dist/{chunk-ZVHRSR2R.js → chunk-IPI45Q4S.js} +6 -3
  67. package/dist/chunk-IPI45Q4S.js.map +1 -0
  68. package/dist/{chunk-24D66MMN.js → chunk-IX4L7E5G.js} +7 -4
  69. package/dist/chunk-IX4L7E5G.js.map +1 -0
  70. package/dist/{chunk-SYX73XD7.js → chunk-IXGXALRV.js} +10 -7
  71. package/dist/chunk-IXGXALRV.js.map +1 -0
  72. package/dist/{chunk-ESF7TRYJ.js → chunk-JWDE7OVJ.js} +79 -2
  73. package/dist/chunk-JWDE7OVJ.js.map +1 -0
  74. package/dist/{chunk-GGL34GCW.js → chunk-K3Y2SJTN.js} +7 -4
  75. package/dist/chunk-K3Y2SJTN.js.map +1 -0
  76. package/dist/{chunk-PMAQRUN4.js → chunk-NDKW67ER.js} +5 -2
  77. package/dist/chunk-NDKW67ER.js.map +1 -0
  78. package/dist/{chunk-ZQUAVZNG.js → chunk-NNJKEAP2.js} +7 -4
  79. package/dist/chunk-NNJKEAP2.js.map +1 -0
  80. package/dist/{chunk-UZYLX7M6.js → chunk-OYHYBHHI.js} +9 -6
  81. package/dist/chunk-OYHYBHHI.js.map +1 -0
  82. package/dist/{chunk-NOU77OHX.js → chunk-P6VLG7KE.js} +2 -2
  83. package/dist/{chunk-O4ZIBPIW.js → chunk-QNTLWH3T.js} +3 -3
  84. package/dist/{chunk-LUCARK4W.js → chunk-UO6MPN77.js} +37 -35
  85. package/dist/chunk-UO6MPN77.js.map +1 -0
  86. package/dist/{chunk-HJWQJUKQ.js → chunk-VOEVVIP3.js} +3 -3
  87. package/dist/{chunk-KQYGNGON.js → chunk-ZBZ4XULO.js} +6 -3
  88. package/dist/chunk-ZBZ4XULO.js.map +1 -0
  89. package/dist/{cli-Dt-tuiYH.d.ts → cli-Cx5Mxwk7.d.ts} +3 -3
  90. package/dist/cli.d.ts +14 -12
  91. package/dist/cli.js +20 -20
  92. package/dist/compounding/engine.d.ts +3 -2
  93. package/dist/compounding/engine.js +2 -1
  94. package/dist/compounding/preference-consolidator.d.ts +1 -1
  95. package/dist/compression-optimizer.d.ts +1 -1
  96. package/dist/config.d.ts +1 -1
  97. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  98. package/dist/connectors/codex-materialize.d.ts +1 -1
  99. package/dist/connectors/index.d.ts +1 -1
  100. package/dist/consolidation-provenance-check.d.ts +1 -1
  101. package/dist/consolidation-undo.d.ts +1 -1
  102. package/dist/contradiction/index.d.ts +2 -2
  103. package/dist/contradiction/index.js +2 -1
  104. package/dist/{contradiction-scan-HWGEOUDS.js → contradiction-scan-2B4AE4TI.js} +3 -2
  105. package/dist/conversation-index/backend.d.ts +1 -1
  106. package/dist/conversation-index/chunker.d.ts +1 -1
  107. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  108. package/dist/conversation-index/indexer.d.ts +1 -1
  109. package/dist/conversation-index/search.d.ts +1 -1
  110. package/dist/day-summary.d.ts +1 -1
  111. package/dist/delinearize.d.ts +1 -1
  112. package/dist/direct-answer-wiring.d.ts +1 -1
  113. package/dist/direct-answer.d.ts +1 -1
  114. package/dist/embedding-fallback.d.ts +1 -1
  115. package/dist/embedding-fallback.js +2 -2
  116. package/dist/enrichment/index.d.ts +1 -1
  117. package/dist/entity-retrieval.d.ts +1 -1
  118. package/dist/entity-retrieval.js +2 -1
  119. package/dist/entity-schema.d.ts +1 -1
  120. package/dist/explicit-capture.d.ts +12 -10
  121. package/dist/explicit-capture.js +2 -1
  122. package/dist/extraction-faithfulness.d.ts +1 -1
  123. package/dist/extraction-judge-telemetry.d.ts +1 -1
  124. package/dist/extraction-judge-training.d.ts +1 -1
  125. package/dist/extraction-judge.d.ts +1 -1
  126. package/dist/extraction.d.ts +1 -1
  127. package/dist/extraction.js +3 -3
  128. package/dist/fallback-llm.d.ts +1 -1
  129. package/dist/{first-start-migration-OOR57AYR.js → first-start-migration-2RQYDIZ7.js} +10 -8
  130. package/dist/first-start-migration-2RQYDIZ7.js.map +1 -0
  131. package/dist/graph-dashboard-diff.d.ts +1 -1
  132. package/dist/graph-dashboard-key.d.ts +1 -1
  133. package/dist/graph-dashboard-parser.d.ts +1 -1
  134. package/dist/graph-edge-reinforcement.d.ts +1 -1
  135. package/dist/graph-snapshot.d.ts +1 -1
  136. package/dist/graph.d.ts +1 -1
  137. package/dist/identity-continuity.d.ts +1 -1
  138. package/dist/importance.d.ts +1 -1
  139. package/dist/index.d.ts +12 -12
  140. package/dist/index.js +44 -44
  141. package/dist/intent.d.ts +1 -1
  142. package/dist/lcm/engine.d.ts +1 -1
  143. package/dist/lcm/index.d.ts +1 -1
  144. package/dist/lcm/tools.d.ts +1 -1
  145. package/dist/lifecycle.d.ts +1 -1
  146. package/dist/live-connectors-runner.d.ts +1 -1
  147. package/dist/local-llm.d.ts +1 -1
  148. package/dist/local-llm.js +2 -1
  149. package/dist/local-model-endpoint.d.ts +1 -1
  150. package/dist/maintenance/memory-governance.d.ts +1 -1
  151. package/dist/mcp-memory-inspector-app.d.ts +13 -11
  152. package/dist/memory-action-policy.d.ts +1 -1
  153. package/dist/memory-cache.d.ts +1 -1
  154. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  155. package/dist/memory-projection-store.d.ts +1 -1
  156. package/dist/memory-provenance.d.ts +1 -1
  157. package/dist/memory-worth-outcomes.d.ts +1 -1
  158. package/dist/models-json.d.ts +1 -1
  159. package/dist/namespaces/migrate.d.ts +2 -2
  160. package/dist/namespaces/migrate.js +6 -6
  161. package/dist/namespaces/principal.d.ts +1 -1
  162. package/dist/namespaces/principal.js +2 -1
  163. package/dist/namespaces/search.d.ts +1 -1
  164. package/dist/namespaces/search.js +4 -4
  165. package/dist/namespaces/storage.d.ts +2 -2
  166. package/dist/namespaces/storage.js +2 -1
  167. package/dist/native-knowledge.d.ts +1 -1
  168. package/dist/operator-toolkit.d.ts +1 -1
  169. package/dist/operator-toolkit.js +11 -11
  170. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  171. package/dist/orchestration/compression-guideline-coordinator.js +2 -1
  172. package/dist/orchestration/maintenance.d.ts +2 -2
  173. package/dist/orchestration/maintenance.js +6 -5
  174. package/dist/{orchestrator-B4KjaOtY.d.ts → orchestrator-oF0cceBX.d.ts} +143 -18
  175. package/dist/orchestrator.d.ts +8 -6
  176. package/dist/orchestrator.js +21 -19
  177. package/dist/patterns-cli.d.ts +1 -1
  178. package/dist/policy-runtime.d.ts +1 -1
  179. package/dist/provenance.d.ts +1 -1
  180. package/dist/qmd-recall-cache.d.ts +1 -1
  181. package/dist/qmd.d.ts +1 -1
  182. package/dist/recall-disclosure-escalation.d.ts +1 -1
  183. package/dist/recall-explain-renderer.d.ts +1 -1
  184. package/dist/recall-planner-llm.d.ts +1 -1
  185. package/dist/recall-state.d.ts +1 -1
  186. package/dist/recall-tag-filter.d.ts +1 -1
  187. package/dist/recall-xray-cli.d.ts +1 -1
  188. package/dist/recall-xray-renderer.d.ts +1 -1
  189. package/dist/recall-xray.d.ts +1 -1
  190. package/dist/resolve-auth-token.d.ts +1 -1
  191. package/dist/retrieval-agents.d.ts +1 -1
  192. package/dist/retrieval-tiers.d.ts +1 -1
  193. package/dist/routing/engine.d.ts +1 -1
  194. package/dist/routing/store.d.ts +1 -1
  195. package/dist/schemas.d.ts +2 -2
  196. package/dist/search/embed-helper.d.ts +1 -1
  197. package/dist/search/embed-helper.js +2 -2
  198. package/dist/search/factory.d.ts +1 -1
  199. package/dist/search/factory.js +3 -3
  200. package/dist/search/index.d.ts +1 -1
  201. package/dist/search/index.js +3 -3
  202. package/dist/search/lancedb-backend.d.ts +1 -1
  203. package/dist/search/meilisearch-backend.d.ts +1 -1
  204. package/dist/search/noop-backend.d.ts +1 -1
  205. package/dist/search/orama-backend.d.ts +1 -1
  206. package/dist/search/port.d.ts +1 -1
  207. package/dist/search/remote-backend.d.ts +1 -1
  208. package/dist/{semantic-SLAa_prH.d.ts → semantic-DJR8_DMQ.d.ts} +1 -1
  209. package/dist/{semantic-consolidation-CstTvHPo.d.ts → semantic-consolidation-DKGbufYO.d.ts} +1 -1
  210. package/dist/semantic-consolidation.d.ts +2 -2
  211. package/dist/semantic-rule-verifier.d.ts +1 -1
  212. package/dist/session-observer-bands.d.ts +1 -1
  213. package/dist/session-observer-state.d.ts +1 -1
  214. package/dist/shared-context/manager.d.ts +3 -3
  215. package/dist/signal.d.ts +1 -1
  216. package/dist/storage.d.ts +1 -1
  217. package/dist/summarizer.d.ts +1 -1
  218. package/dist/summarizer.js +3 -2
  219. package/dist/summary-snapshot.d.ts +1 -1
  220. package/dist/temporal-supersession.d.ts +1 -1
  221. package/dist/temporal-validity.d.ts +1 -1
  222. package/dist/threading.d.ts +1 -1
  223. package/dist/tier-migration.d.ts +1 -1
  224. package/dist/tier-routing.d.ts +1 -1
  225. package/dist/{tier-stats-3LYQ3VV5.js → tier-stats-KPDIISG7.js} +8 -4
  226. package/dist/tier-stats-KPDIISG7.js.map +1 -0
  227. package/dist/topics.d.ts +1 -1
  228. package/dist/transcript.d.ts +1 -1
  229. package/dist/transfer/types.d.ts +30 -30
  230. package/dist/trust-score-stage.d.ts +1 -1
  231. package/dist/trust-score.d.ts +1 -1
  232. package/dist/{types-VkxElCHR.d.ts → types-C7UCxpuO.d.ts} +1 -1
  233. package/dist/types.d.ts +1 -1
  234. package/dist/utility-runtime.d.ts +1 -1
  235. package/package.json +2 -2
  236. package/src/access-service.ts +61 -56
  237. package/src/active-memory-bridge.ts +2 -1
  238. package/src/admin/admin-surfaces.ts +2 -1
  239. package/src/capabilities.test.ts +375 -0
  240. package/src/capabilities.ts +403 -0
  241. package/src/cli/creation-ledger-commands.ts +2 -2
  242. package/src/cli/research-status-commands.ts +13 -13
  243. package/src/cli.ts +29 -25
  244. package/src/compounding/engine.ts +4 -3
  245. package/src/contradiction/contradiction-scan.ts +4 -3
  246. package/src/embedding-fallback.ts +5 -2
  247. package/src/entity-retrieval.ts +3 -2
  248. package/src/explicit-capture.ts +2 -1
  249. package/src/extraction.ts +6 -3
  250. package/src/local-llm.ts +4 -3
  251. package/src/maintenance/first-start-migration.ts +8 -5
  252. package/src/maintenance/namespace-maintenance-fanout.ts +2 -1
  253. package/src/maintenance/namespace-planner.ts +4 -3
  254. package/src/maintenance/tier-stats.ts +4 -3
  255. package/src/namespaces/catalog.ts +2 -1
  256. package/src/namespaces/migrate.ts +2 -1
  257. package/src/namespaces/principal.ts +6 -5
  258. package/src/namespaces/scope-profiles.ts +2 -1
  259. package/src/namespaces/search.ts +2 -1
  260. package/src/namespaces/storage.ts +4 -3
  261. package/src/operator-toolkit.ts +27 -26
  262. package/src/orchestration/compression-guideline-coordinator.ts +7 -6
  263. package/src/orchestration/lifecycle-policy-coordinator.ts +504 -0
  264. package/src/orchestration/maintenance.ts +8 -4
  265. package/src/orchestration/semantic-consolidation-coordinator.ts +454 -0
  266. package/src/orchestration/tier-migration-coordinator.ts +5 -4
  267. package/src/orchestrator.ts +106 -800
  268. package/src/scopes/scope-plan.ts +3 -2
  269. package/src/search/embed-helper.ts +5 -2
  270. package/src/search/factory.ts +9 -6
  271. package/src/summarizer.ts +3 -2
  272. package/dist/chunk-24D66MMN.js.map +0 -1
  273. package/dist/chunk-646B5ERJ.js.map +0 -1
  274. package/dist/chunk-6TUWSUUD.js.map +0 -1
  275. package/dist/chunk-CGPHMNL4.js.map +0 -1
  276. package/dist/chunk-CX77GNMB.js.map +0 -1
  277. package/dist/chunk-DKT3X5C3.js.map +0 -1
  278. package/dist/chunk-ESF7TRYJ.js.map +0 -1
  279. package/dist/chunk-FFUCCKPN.js.map +0 -1
  280. package/dist/chunk-GGL34GCW.js.map +0 -1
  281. package/dist/chunk-H3MB3F62.js.map +0 -1
  282. package/dist/chunk-HHNSLPX7.js.map +0 -1
  283. package/dist/chunk-J6BH2OBA.js.map +0 -1
  284. package/dist/chunk-KQYGNGON.js.map +0 -1
  285. package/dist/chunk-KSJCXXOU.js.map +0 -1
  286. package/dist/chunk-LUCARK4W.js.map +0 -1
  287. package/dist/chunk-NIZQH6AU.js.map +0 -1
  288. package/dist/chunk-OTQE66IX.js.map +0 -1
  289. package/dist/chunk-OUITHS6V.js.map +0 -1
  290. package/dist/chunk-P3VJCTVE.js.map +0 -1
  291. package/dist/chunk-PLBIPT6I.js.map +0 -1
  292. package/dist/chunk-PMAQRUN4.js.map +0 -1
  293. package/dist/chunk-SYX73XD7.js.map +0 -1
  294. package/dist/chunk-UZYLX7M6.js.map +0 -1
  295. package/dist/chunk-ZQUAVZNG.js.map +0 -1
  296. package/dist/chunk-ZVHRSR2R.js.map +0 -1
  297. package/dist/first-start-migration-OOR57AYR.js.map +0 -1
  298. package/dist/tier-stats-3LYQ3VV5.js.map +0 -1
  299. /package/dist/{chunk-QZHF4BWN.js.map → chunk-AAIHDIMD.js.map} +0 -0
  300. /package/dist/{chunk-FYWUNVFZ.js.map → chunk-E4LYJ7PY.js.map} +0 -0
  301. /package/dist/{chunk-NOU77OHX.js.map → chunk-P6VLG7KE.js.map} +0 -0
  302. /package/dist/{chunk-O4ZIBPIW.js.map → chunk-QNTLWH3T.js.map} +0 -0
  303. /package/dist/{chunk-HJWQJUKQ.js.map → chunk-VOEVVIP3.js.map} +0 -0
  304. /package/dist/{contradiction-scan-HWGEOUDS.js.map → contradiction-scan-2B4AE4TI.js.map} +0 -0
@@ -101,6 +101,8 @@ import { MaintenanceScheduler } from "./orchestration/maintenance.js";
101
101
  import { TierMigrationCoordinator } from "./orchestration/tier-migration-coordinator.js";
102
102
  import { ExtractionQueueCoordinator } from "./orchestration/extraction-queue-coordinator.js";
103
103
  import { CompressionGuidelineCoordinator } from "./orchestration/compression-guideline-coordinator.js";
104
+ import { SemanticConsolidationCoordinator } from "./orchestration/semantic-consolidation-coordinator.js";
105
+ import { LifecyclePolicyCoordinator } from "./orchestration/lifecycle-policy-coordinator.js";
104
106
  import {
105
107
  runLiveConnectorsOnce,
106
108
  type LiveConnectorsRunSummary,
@@ -266,6 +268,7 @@ import {
266
268
  } from "./trust-zones.js";
267
269
  import { tryDirectAnswer, type DirectAnswerSources } from "./direct-answer-wiring.js";
268
270
  import {
271
+ resolveNamespaceCapabilities,
269
272
  resolveCapabilities,
270
273
  resolveGraphConstructionCapabilities,
271
274
  resolveMemoryLifecycleCapabilities,
@@ -276,7 +279,10 @@ import {
276
279
  type MemoryLifecycleCapabilitySet,
277
280
  type IndexingCapabilitySet,
278
281
  type CreationMemoryCapabilitySet,
279
- } from "./capabilities.js";
282
+ resolveQmdCapabilities,
283
+ resolveIdentityContinuityCapabilities,
284
+ resolveLocalLlmCapabilities,
285
+ resolveSecurityCapabilities, resolveEvalCapabilities, resolveUtilityLearningCapabilities, resolveObjectiveStateCapabilities, resolveCompressionCapabilities} from "./capabilities.js";
280
286
  import { DEFAULT_TAXONOMY } from "./taxonomy/index.js";
281
287
  import {
282
288
  searchHarmonicRetrieval,
@@ -1896,6 +1902,8 @@ export class Orchestrator {
1896
1902
  readonly tierMigrationStatus: TierMigrationStatusStore;
1897
1903
  readonly tierMigrationCoordinator: TierMigrationCoordinator;
1898
1904
  readonly compressionGuidelineCoordinator: CompressionGuidelineCoordinator;
1905
+ readonly semanticConsolidationCoordinator: SemanticConsolidationCoordinator;
1906
+ readonly lifecyclePolicyCoordinator: LifecyclePolicyCoordinator;
1899
1907
  /**
1900
1908
  * In-memory X-ray snapshot from the most recent `recall()` call that
1901
1909
  * was invoked with `xrayCapture: true` (issue #570 PR 1). Scope is
@@ -2251,7 +2259,7 @@ export class Orchestrator {
2251
2259
  * @internal
2252
2260
  */
2253
2261
  applyCodingNamespaceOverlay(sessionKey: string | undefined, baseNamespace: string): string {
2254
- if (!this.config.namespacesEnabled) return baseNamespace;
2262
+ if (!resolveNamespaceCapabilities(this.config).namespaces) return baseNamespace;
2255
2263
  const codingContext = this.getCodingContextForSession(sessionKey);
2256
2264
  const overlay = resolveCodingNamespaceOverlay(codingContext, this.config.codingMode, this.config.defaultNamespace);
2257
2265
  if (!overlay) return baseNamespace;
@@ -2331,7 +2339,7 @@ export class Orchestrator {
2331
2339
  * @internal
2332
2340
  */
2333
2341
  applyCodingRecallOverlay(sessionKey: string | undefined): { namespace: string; readFallbacks: string[] } | null {
2334
- if (!this.config.namespacesEnabled) return null;
2342
+ if (!resolveNamespaceCapabilities(this.config).namespaces) return null;
2335
2343
  const codingContext = this.getCodingContextForSession(sessionKey);
2336
2344
  const overlay = resolveCodingNamespaceOverlay(codingContext, this.config.codingMode, this.config.defaultNamespace);
2337
2345
  if (!overlay) return null;
@@ -2354,7 +2362,7 @@ export class Orchestrator {
2354
2362
  }
2355
2363
 
2356
2364
  private rememberNamespaceStorageDirHint(namespace: string, storageDir?: string): void {
2357
- if (!this.config.namespacesEnabled || !storageDir) return;
2365
+ if (!resolveNamespaceCapabilities(this.config).namespaces || !storageDir) return;
2358
2366
  const ns = normalizeNamespaceIdentity(namespace);
2359
2367
  if (!ns) return;
2360
2368
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
@@ -2558,10 +2566,10 @@ export class Orchestrator {
2558
2566
  private buildConfiguredQmdSearchOptions(
2559
2567
  queryText: string,
2560
2568
  ): SearchQueryOptions | undefined {
2561
- const intentHint = this.config.qmdIntentHintsEnabled
2569
+ const intentHint = resolveQmdCapabilities(this.config).qmdIntentHints
2562
2570
  ? buildQmdIntentHint(inferIntentFromText(queryText))
2563
2571
  : undefined;
2564
- const explain = this.config.qmdExplainEnabled === true;
2572
+ const explain = resolveQmdCapabilities(this.config).qmdExplain === true;
2565
2573
  const searchOptions: SearchQueryOptions = {};
2566
2574
  if (intentHint) {
2567
2575
  searchOptions.intent = intentHint;
@@ -2581,13 +2589,13 @@ export class Orchestrator {
2581
2589
  execution?: SearchExecutionOptions;
2582
2590
  }): Promise<QmdSearchResult[]> {
2583
2591
  if (
2584
- this.config.namespacesEnabled &&
2592
+ resolveNamespaceCapabilities(this.config).namespaces &&
2585
2593
  options.namespaces !== undefined &&
2586
2594
  options.namespaces.length === 0
2587
2595
  ) {
2588
2596
  return [];
2589
2597
  }
2590
- const namespaces = this.config.namespacesEnabled
2598
+ const namespaces = resolveNamespaceCapabilities(this.config).namespaces
2591
2599
  ? Array.from(
2592
2600
  new Set(
2593
2601
  (options.namespaces?.length
@@ -2600,7 +2608,7 @@ export class Orchestrator {
2600
2608
  )
2601
2609
  : [this.config.defaultNamespace];
2602
2610
 
2603
- if (!this.config.namespacesEnabled) {
2611
+ if (!resolveNamespaceCapabilities(this.config).namespaces) {
2604
2612
  switch (options.mode) {
2605
2613
  case "hybrid":
2606
2614
  return await this.qmd.hybridSearch(
@@ -2652,7 +2660,7 @@ export class Orchestrator {
2652
2660
  }
2653
2661
 
2654
2662
  private isSearchAvailableForNamespaceRouting(): boolean {
2655
- if (this.config.namespacesEnabled) return true;
2663
+ if (resolveNamespaceCapabilities(this.config).namespaces) return true;
2656
2664
  return this.qmd.isAvailable();
2657
2665
  }
2658
2666
 
@@ -3019,6 +3027,7 @@ export class Orchestrator {
3019
3027
  fastChatCompletion: (messages, options) =>
3020
3028
  this.fastChatCompletion(messages, options),
3021
3029
  });
3030
+
3022
3031
  this.sessionObserver = new SessionObserverState({
3023
3032
  memoryDir: config.memoryDir,
3024
3033
  debounceMs: config.sessionObserverDebounceMs ?? 120_000,
@@ -3061,6 +3070,15 @@ export class Orchestrator {
3061
3070
  return client;
3062
3071
  })()
3063
3072
  : this.localLlm;
3073
+ this.semanticConsolidationCoordinator = new SemanticConsolidationCoordinator({
3074
+ config,
3075
+ getStorage: () => this.storage,
3076
+ getFastLlm: () => this.fastLlm,
3077
+ embeddingFallback: this.embeddingFallback,
3078
+ removeContentHashForMemory: (targetStorage, memory, context) =>
3079
+ this.removeContentHashForMemory(targetStorage, memory, context),
3080
+ saveContentHashIndexes: () => this.saveContentHashIndexes(),
3081
+ });
3064
3082
  // Initialize gateway fast LLM for fast-tier ops when modelSource is "gateway"
3065
3083
  this._fastGatewayLlm = config.modelSource === "gateway"
3066
3084
  ? new FallbackLlmClient(
@@ -3082,6 +3100,16 @@ export class Orchestrator {
3082
3100
  config.gatewayConfig,
3083
3101
  this.modelRegistry,
3084
3102
  );
3103
+ this.lifecyclePolicyCoordinator = new LifecyclePolicyCoordinator({
3104
+ config,
3105
+ getStorage: () => this.storage,
3106
+ extraction: this.extraction,
3107
+ embeddingFallback: this.embeddingFallback,
3108
+ getEffectiveLifecycleThresholds: () => this.effectiveLifecycleThresholds(),
3109
+ removeContentHashForMemory: (targetStorage, memory, context) =>
3110
+ this.removeContentHashForMemory(targetStorage, memory, context),
3111
+ saveContentHashIndexes: () => this.saveContentHashIndexes(),
3112
+ });
3085
3113
  this.threading = new ThreadingManager(
3086
3114
  path.join(config.memoryDir, "threads"),
3087
3115
  config.threadingGapMinutes,
@@ -3224,7 +3252,7 @@ export class Orchestrator {
3224
3252
  }
3225
3253
 
3226
3254
  private routeEngineOptions(): RoutingEngineOptions {
3227
- const allowedNamespaces = this.config.namespacesEnabled
3255
+ const allowedNamespaces = resolveNamespaceCapabilities(this.config).namespaces
3228
3256
  ? Array.from(
3229
3257
  new Set([
3230
3258
  this.config.defaultNamespace,
@@ -3418,7 +3446,7 @@ export class Orchestrator {
3418
3446
  ? this._fastGatewayLlm?.isAvailable(
3419
3447
  this.config.fastGatewayAgentId || this.config.gatewayAgentId || undefined,
3420
3448
  ) === true
3421
- : this.config.localLlmEnabled;
3449
+ : resolveLocalLlmCapabilities(this.config).localLlm;
3422
3450
  if (!chainAvailable) return undefined;
3423
3451
  return buildChainFollowupGenerator(this.fastLlmForRerank);
3424
3452
  }
@@ -3440,7 +3468,7 @@ export class Orchestrator {
3440
3468
  });
3441
3469
  await this.storage.ensureDirectories();
3442
3470
  await this.storage.loadAliases();
3443
- if (this.config.namespacesEnabled) {
3471
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
3444
3472
  const namespaces = new Set<string>([
3445
3473
  this.config.defaultNamespace,
3446
3474
  this.config.sharedNamespace,
@@ -3494,8 +3522,8 @@ export class Orchestrator {
3494
3522
  this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
3495
3523
  this.utilityRuntimeValues = await loadUtilityRuntimeValues({
3496
3524
  memoryDir: this.config.memoryDir,
3497
- memoryUtilityLearningEnabled: this.config.memoryUtilityLearningEnabled,
3498
- promotionByOutcomeEnabled: this.config.promotionByOutcomeEnabled,
3525
+ memoryUtilityLearningEnabled: resolveUtilityLearningCapabilities(this.config).memoryUtilityLearning,
3526
+ promotionByOutcomeEnabled: resolveUtilityLearningCapabilities(this.config).promotionByOutcome,
3499
3527
  });
3500
3528
 
3501
3529
  // Initialize content-hash dedup index
@@ -3560,14 +3588,14 @@ export class Orchestrator {
3560
3588
  // after a restart. `registerConfiguredNamespaces()` already seeded the
3561
3589
  // catalog above, so `maintenanceNamespaces()` is readable here; it falls
3562
3590
  // back to the configured set on any catalog read failure.
3563
- const namespaces = this.config.namespacesEnabled
3591
+ const namespaces = resolveNamespaceCapabilities(this.config).namespaces
3564
3592
  ? await this.maintenanceNamespaces()
3565
3593
  : [this.config.defaultNamespace];
3566
3594
  const states = await Promise.all(
3567
3595
  namespaces.map(async (namespace) => {
3568
3596
  const collectionCheckAbort = new AbortController();
3569
3597
  const state = await qmdStartupCollectionCheckWithTimeout(
3570
- this.config.namespacesEnabled
3598
+ resolveNamespaceCapabilities(this.config).namespaces
3571
3599
  ? this.namespaceSearchRouter.ensureNamespaceCollection(
3572
3600
  namespace,
3573
3601
  { signal: collectionCheckAbort.signal },
@@ -3681,10 +3709,10 @@ export class Orchestrator {
3681
3709
  // triggered update — which can be days ago if the daemon restarted without
3682
3710
  // new extractions. This is the root cause of "0 memories" recall results
3683
3711
  // despite thousands of facts on disk.
3684
- if (this.qmd.isAvailable() && this.config.qmdMaintenanceEnabled) {
3712
+ if (this.qmd.isAvailable() && resolveQmdCapabilities(this.config).qmdMaintenance) {
3685
3713
  try {
3686
3714
  log.info("QMD startup sync: updating index to match current disk state");
3687
- if (this.config.namespacesEnabled) {
3715
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
3688
3716
  // Cover cataloged dynamic namespaces at startup too (NHZEV, codex P2):
3689
3717
  // a dynamic namespace written before a daemon restart must be synced on
3690
3718
  // boot, not only by the debounced runQmdMaintenance() path. Same union +
@@ -3788,7 +3816,7 @@ export class Orchestrator {
3788
3816
 
3789
3817
  if (signal.aborted) return;
3790
3818
 
3791
- if (this.config.localLlmEnabled) {
3819
+ if (resolveLocalLlmCapabilities(this.config).localLlm) {
3792
3820
  try {
3793
3821
  await this.validateLocalLlmModel();
3794
3822
  } catch (err) {
@@ -3813,7 +3841,7 @@ export class Orchestrator {
3813
3841
  // sweep (capped at 50 demotions) so the hot tier isn't flooded on the
3814
3842
  // first real cron pass. Non-fatal — a failure here must not break init.
3815
3843
  if (signal.aborted) return;
3816
- if (lifecycleCaps.lifecyclePolicy && this.config.qmdTierMigrationEnabled) {
3844
+ if (lifecycleCaps.lifecyclePolicy && resolveQmdCapabilities(this.config).qmdTierMigration) {
3817
3845
  try {
3818
3846
  const { runFirstStartMigration } = await import(
3819
3847
  "./maintenance/first-start-migration.js"
@@ -3918,7 +3946,7 @@ export class Orchestrator {
3918
3946
  log.info(`startupSearchSync: backend now available ${this.qmd.debugStatus()}`);
3919
3947
 
3920
3948
  // Clear namespace router cache so re-probe picks up newly available backends
3921
- if (this.config.namespacesEnabled) {
3949
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
3922
3950
  this.namespaceSearchRouter.clearCache();
3923
3951
  }
3924
3952
 
@@ -3930,14 +3958,14 @@ export class Orchestrator {
3930
3958
  // re-synced here too, otherwise after a backend-was-unavailable-at-boot
3931
3959
  // recovery its collection stays stale. Falls back to the configured set on any
3932
3960
  // catalog read failure.
3933
- const namespaces = this.config.namespacesEnabled
3961
+ const namespaces = resolveNamespaceCapabilities(this.config).namespaces
3934
3962
  ? await this.maintenanceNamespaces()
3935
3963
  : [this.config.defaultNamespace];
3936
3964
 
3937
3965
  const states = await Promise.all(
3938
3966
  namespaces.map(async (namespace) => ({
3939
3967
  namespace,
3940
- state: this.config.namespacesEnabled
3968
+ state: resolveNamespaceCapabilities(this.config).namespaces
3941
3969
  ? await this.namespaceSearchRouter.ensureNamespaceCollection(namespace, { signal })
3942
3970
  : await this.qmd.ensureCollection(this.config.memoryDir, this.config.qmdCollection, { signal }),
3943
3971
  })),
@@ -3972,7 +4000,7 @@ export class Orchestrator {
3972
4000
  // and didn't fail silently.
3973
4001
  // The abort signal is forwarded into the QMD subprocess call so the
3974
4002
  // long-running `qmd update` process is killed promptly on shutdown.
3975
- if (this.config.qmdMaintenanceEnabled) {
4003
+ if (resolveQmdCapabilities(this.config).qmdMaintenance) {
3976
4004
  try {
3977
4005
  const failTsBefore = "lastUpdateFailedAtMs" in this.qmd
3978
4006
  ? (this.qmd as any).lastUpdateFailedAtMs as number | null
@@ -3983,7 +4011,7 @@ export class Orchestrator {
3983
4011
  }
3984
4012
  log.info("startupSearchSync: updating index to match current disk state");
3985
4013
  let namespacesUpdated = 0;
3986
- if (this.config.namespacesEnabled) {
4014
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
3987
4015
  namespacesUpdated = await this.namespaceSearchRouter.updateNamespaces(
3988
4016
  namespaces,
3989
4017
  { signal },
@@ -4005,7 +4033,7 @@ export class Orchestrator {
4005
4033
  log.warn("startupSearchSync: update silently failed (detected via fail timestamp)");
4006
4034
  return false;
4007
4035
  }
4008
- if (this.config.namespacesEnabled) {
4036
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
4009
4037
  if (namespacesUpdated === 0) {
4010
4038
  log.warn("startupSearchSync: no namespace backends were eligible for update (all unavailable or collections missing)");
4011
4039
  return false;
@@ -4374,372 +4402,17 @@ export class Orchestrator {
4374
4402
  return summarizeGovernanceResultForDreams(govResult, options?.dryRun === true);
4375
4403
  }
4376
4404
 
4405
+ // Issue #1526 (seam 5): semantic consolidation moved to
4406
+ // SemanticConsolidationCoordinator. Thin delegation keeps all
4407
+ // call sites (runConsolidation, runSemanticConsolidationNow,
4408
+ // runSemanticConsolidationFanout) stable.
4377
4409
  private async runSemanticConsolidation(options?: {
4378
4410
  dryRun?: boolean;
4379
4411
  thresholdOverride?: number;
4380
4412
  force?: boolean;
4381
4413
  storage?: StorageManager;
4382
4414
  }): Promise<SemanticConsolidationResult> {
4383
- const targetStorage = options?.storage ?? this.storage;
4384
- const result: SemanticConsolidationResult = {
4385
- clustersFound: 0,
4386
- memoriesConsolidated: 0,
4387
- memoriesArchived: 0,
4388
- errors: 0,
4389
- clusters: [],
4390
- };
4391
-
4392
- if (!this.config.semanticConsolidationEnabled && !options?.force) {
4393
- log.debug("[semantic-consolidation] disabled in config");
4394
- return result;
4395
- }
4396
-
4397
- log.info("[semantic-consolidation] starting run");
4398
-
4399
- const allMemories = await targetStorage.readAllMemories();
4400
- if (allMemories.length < 10) {
4401
- log.debug("[semantic-consolidation] too few memories, skipping");
4402
- return result;
4403
- }
4404
-
4405
- const threshold =
4406
- options?.thresholdOverride ?? this.config.semanticConsolidationThreshold;
4407
- const clusters = findSimilarClusters(allMemories, {
4408
- threshold,
4409
- minClusterSize: this.config.semanticConsolidationMinClusterSize,
4410
- excludeCategories: this.config.semanticConsolidationExcludeCategories,
4411
- maxPerRun: this.config.semanticConsolidationMaxPerRun,
4412
- });
4413
-
4414
- result.clustersFound = clusters.length;
4415
- result.clusters = clusters;
4416
-
4417
- if (clusters.length === 0) {
4418
- log.info("[semantic-consolidation] no clusters found");
4419
- return result;
4420
- }
4421
-
4422
- log.info(`[semantic-consolidation] found ${clusters.length} cluster(s)`);
4423
-
4424
- if (options?.dryRun) {
4425
- log.info(
4426
- "[semantic-consolidation] dry run — skipping LLM synthesis and archival",
4427
- );
4428
- return result;
4429
- }
4430
-
4431
- // Use FallbackLlmClient for LLM calls (same pattern as causal-consolidation.ts)
4432
- // Honor semanticConsolidationModel: "auto" = primary, "fast" = local fast, or specific model
4433
- const { FallbackLlmClient, gatewayTaskChainOptions } = await import("./fallback-llm.js");
4434
- const useGateway = this.config.modelSource === "gateway";
4435
- const modelSetting = this.config.semanticConsolidationModel;
4436
- if (modelSetting === "fast" && this.fastLlm && !useGateway) {
4437
- log.info("[semantic-consolidation] using fast local LLM for synthesis");
4438
- }
4439
- // Gateway routing: an explicit "fast" setting keeps the fast persona chain
4440
- // (the operator's deliberate fast-tier choice). Otherwise route through the
4441
- // shared task-chain resolution so taskModelChain applies to semantic
4442
- // consolidation like every other background task (gotcha #22). Issue #1365.
4443
- const gatewayChainOptions: { modelChain?: AgentPersonaModelConfig; agentId?: string } =
4444
- !useGateway
4445
- ? {}
4446
- : modelSetting === "fast"
4447
- ? (this.config.fastGatewayAgentId
4448
- ? { agentId: this.config.fastGatewayAgentId }
4449
- : this.config.gatewayAgentId
4450
- ? { agentId: this.config.gatewayAgentId }
4451
- : {})
4452
- : gatewayTaskChainOptions(this.config);
4453
- const llm = new FallbackLlmClient(
4454
- this.config.gatewayConfig,
4455
- fallbackLlmRuntimeContextFromConfig(this.config),
4456
- );
4457
- if (!llm.isAvailable(gatewayChainOptions) && !(modelSetting === "fast" && this.fastLlm && !useGateway)) {
4458
- log.warn(
4459
- "[semantic-consolidation] no LLM available — skipping synthesis",
4460
- );
4461
- return result;
4462
- }
4463
-
4464
- // Discover memory extensions once for all clusters (#382)
4465
- let extensionsBlock = "";
4466
- try {
4467
- extensionsBlock = await buildExtensionsBlockForConsolidation(this.config);
4468
- } catch (err) {
4469
- log.warn(`[semantic-consolidation] extension discovery failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
4470
- }
4471
-
4472
- for (const cluster of clusters) {
4473
- let canonicalWriteCompleted = false;
4474
- try {
4475
- // Operator-aware prompt (issue #561 PR 3): ask the LLM to pick the
4476
- // SPLIT/MERGE/UPDATE operator alongside the canonical output. Falls
4477
- // back to the legacy plain-blob prompt when operator-aware
4478
- // consolidation is explicitly disabled via config, so rollbacks stay
4479
- // clean.
4480
- // Use the `=== true` idiom for default-false flags (PR #632
4481
- // review, cursor Low): sibling disabled-by-default flags like
4482
- // `semanticConsolidationEnabled` follow the same convention,
4483
- // while `!== false` is reserved for default-on flags.
4484
- const operatorAwareEnabled =
4485
- this.config.operatorAwareConsolidationEnabled === true;
4486
- let prompt = operatorAwareEnabled
4487
- ? buildOperatorAwareConsolidationPrompt(cluster)
4488
- : buildConsolidationPrompt(cluster);
4489
- if (extensionsBlock.length > 0) {
4490
- prompt += "\n\n" + extensionsBlock;
4491
- }
4492
- const messages = [
4493
- {
4494
- role: "system" as const,
4495
- content: operatorAwareEnabled
4496
- ? 'You are a memory consolidation system. Return ONLY a JSON object with two keys, "operator" and "output". The "operator" value MUST be one of the exact strings "merge", "update", or "split" — never a pipe-separated placeholder, never prose. The "output" value is the canonical memory text.'
4497
- : "You are a memory consolidation system. Output only the consolidated memory text.",
4498
- },
4499
- { role: "user" as const, content: prompt },
4500
- ];
4501
- const llmOpts = { temperature: 0.2, maxTokens: 2000 };
4502
-
4503
- // Route to the configured model
4504
- let response: { content: string } | null = null;
4505
- if (useGateway) {
4506
- // Gateway model source — use the appropriate agent chain
4507
- response = await llm.chatCompletion(messages, { ...llmOpts, ...gatewayChainOptions });
4508
- } else if (modelSetting === "fast" && this.fastLlm) {
4509
- const fastResult = await this.fastLlm.chatCompletion(messages, {
4510
- operation: "semantic-consolidation",
4511
- maxTokens: llmOpts.maxTokens,
4512
- temperature: llmOpts.temperature,
4513
- priority: "background",
4514
- forceDisableThinking: true,
4515
- });
4516
- response = fastResult ? { content: fastResult.content } : null;
4517
- } else {
4518
- response = await llm.chatCompletion(messages, llmOpts);
4519
- }
4520
-
4521
- if (!response?.content) {
4522
- log.warn(
4523
- `[semantic-consolidation] empty LLM response for cluster in "${cluster.category}"`,
4524
- );
4525
- result.errors++;
4526
- continue;
4527
- }
4528
-
4529
- // Operator-aware parse (issue #561 PR 3). In legacy mode we fall
4530
- // back to the plain-text parser and derive the operator from the
4531
- // cluster-shape heuristic so `derived_via` still lands.
4532
- // Restricted to `SemanticConsolidationLlmOperator`
4533
- // (split/merge/update) — `pattern-reinforcement` joined the
4534
- // wider `ConsolidationOperator` type in #687 PR 2/4 but is
4535
- // reserved for the maintenance job and must never be assignable
4536
- // here (Cursor Bugbot review, PR #730).
4537
- let canonicalContent: string;
4538
- let operator: SemanticConsolidationLlmOperator;
4539
- if (operatorAwareEnabled) {
4540
- const parsed = parseOperatorAwareConsolidationResponse(
4541
- response.content,
4542
- cluster,
4543
- );
4544
- canonicalContent = parsed.output;
4545
- operator = parsed.operator;
4546
- } else {
4547
- canonicalContent = parseConsolidationResponse(response.content);
4548
- operator = chooseConsolidationOperator(cluster);
4549
- }
4550
- cluster.canonicalContent = canonicalContent;
4551
-
4552
- // Pick the most recent memory's metadata as the basis for lineage
4553
- const sorted = [...cluster.memories].sort(
4554
- (a, b) =>
4555
- new Date(b.frontmatter.created).getTime() -
4556
- new Date(a.frontmatter.created).getTime(),
4557
- );
4558
- const newest = sorted[0];
4559
- const lineageIds = cluster.memories.map((m) => m.frontmatter.id);
4560
-
4561
- // Consolidation provenance (issue #561 PR 2+3): snapshot each
4562
- // source memory BEFORE archiving it, collecting
4563
- // "<relpath>:<versionId>" pointers for the new canonical memory's
4564
- // `derived_from` frontmatter field. Snapshots are best-effort — if
4565
- // page-versioning is disabled (default in `config.ts`) or a single
4566
- // source fails to snapshot we simply omit that entry rather than
4567
- // abort the consolidation. The `derived_via` operator is chosen
4568
- // above (PR 3) from the LLM response or the cluster-shape
4569
- // heuristic fallback and emitted unconditionally so consolidation
4570
- // outputs stay identifiable even when no snapshots are captured
4571
- // (PR #624 review feedback).
4572
- const derivedFromEntries: string[] = [];
4573
- for (const m of cluster.memories) {
4574
- if (!m.path) continue;
4575
- const entry = await targetStorage.snapshotForProvenance(m.path);
4576
- if (entry) derivedFromEntries.push(entry);
4577
- }
4578
-
4579
- // Write the canonical memory
4580
- const { id: canonicalId, tombstoneBlocked: canonicalBlocked } = await targetStorage.writeMemory(
4581
- newest.frontmatter.category,
4582
- canonicalContent,
4583
- {
4584
- actor: "semantic-consolidation",
4585
- confidence: newest.frontmatter.confidence,
4586
- tags: [
4587
- ...new Set(
4588
- cluster.memories.flatMap((m) => m.frontmatter.tags ?? []),
4589
- ),
4590
- ],
4591
- source: "semantic-consolidation",
4592
- lineage: lineageIds,
4593
- derivedFrom: derivedFromEntries.length > 0 ? derivedFromEntries : undefined,
4594
- derivedVia: operator,
4595
- },
4596
- );
4597
- if (canonicalBlocked) {
4598
- // #1645: canonical matched a tombstone (pending_review). Don't archive
4599
- // the sources — consolidation must never delete the only active copy.
4600
- log.info(`[semantic-consolidation] cluster in "${cluster.category}" tombstone-blocked — keeping ${cluster.memories.length} source(s) active (pending_review ${canonicalId})`);
4601
- continue;
4602
- }
4603
- canonicalWriteCompleted = true;
4604
-
4605
- result.memoriesConsolidated++;
4606
-
4607
- // Archive originals
4608
- for (const m of cluster.memories) {
4609
- const archiveResult = await targetStorage.archiveMemory(m, {
4610
- actor: "semantic-consolidation",
4611
- reasonCode: "semantic-consolidation",
4612
- relatedMemoryIds: [canonicalId],
4613
- });
4614
- if (archiveResult) {
4615
- // Remove from the same storage-scoped content-hash index that
4616
- // originally deduped this memory.
4617
- await this.removeContentHashForMemory(
4618
- targetStorage,
4619
- m,
4620
- "semantic-consolidation",
4621
- );
4622
- // Best-effort index cleanup: a failure here (e.g. on-disk index save
4623
- // under disk-full) must NOT abort the archival loop and thereby skip
4624
- // the catalog write touch below for an already-durable canonical write
4625
- // (kilo NV0mh).
4626
- try {
4627
- await this.embeddingFallback.removeFromIndex(m.frontmatter.id);
4628
- if (
4629
- resolveIndexingCapabilities(this.config).queryAwareIndexing &&
4630
- m.path &&
4631
- m.frontmatter?.created
4632
- ) {
4633
- deindexMemory(
4634
- targetStorage.dir,
4635
- m.path,
4636
- m.frontmatter.created,
4637
- m.frontmatter.tags ?? [],
4638
- );
4639
- }
4640
- } catch (cleanupErr) {
4641
- log.warn(
4642
- `[semantic-consolidation] index cleanup failed (non-fatal): ${cleanupErr}`,
4643
- );
4644
- }
4645
- result.memoriesArchived++;
4646
- }
4647
- }
4648
-
4649
- log.info(
4650
- `[semantic-consolidation] consolidated ${cluster.memories.length} memories → ${canonicalId}`,
4651
- );
4652
- } catch (err) {
4653
- log.warn(
4654
- `[semantic-consolidation] cluster processing failed: ${err instanceof Error ? err.message : String(err)}`,
4655
- );
4656
- result.errors++;
4657
- } finally {
4658
- if (canonicalWriteCompleted) {
4659
- // Catalog write touch (issue #1499 sweep): record after the canonical
4660
- // write and, on the happy path, after archival of superseded cluster
4661
- // memories, so `lastWriteAt` reflects every durable mutation in this
4662
- // consolidation (cursor NUtCK). The `finally` also covers partial
4663
- // failures where the canonical memory was written but a later archive
4664
- // step throws and the cluster catch continues (codex NY-dK).
4665
- // Best-effort; namespace decoded from the storage dir since this path
4666
- // #1522: catalog touch handled at the storage chokepoint.
4667
- }
4668
- }
4669
- }
4670
-
4671
- // Save hash indexes if we modified them.
4672
- if (result.memoriesArchived > 0) {
4673
- await this.saveContentHashIndexes().catch((err) =>
4674
- log.warn(
4675
- `[semantic-consolidation] content-hash index save failed: ${err}`,
4676
- ),
4677
- );
4678
- }
4679
-
4680
- log.info(
4681
- `[semantic-consolidation] complete: clusters=${result.clustersFound}, consolidated=${result.memoriesConsolidated}, archived=${result.memoriesArchived}, errors=${result.errors}`,
4682
- );
4683
-
4684
- // #378: fire the Codex materialize post-hook so `codexMaterializeOnConsolidation`
4685
- // actually has a runtime effect. The helper silently no-ops when the
4686
- // feature flag or the per-trigger toggle is off, when the sentinel is
4687
- // missing, or when nothing has changed since the previous run, so it's
4688
- // safe to always call here. Wrapped in a try/catch because a failed
4689
- // materialize must never abort the consolidation result — consolidation
4690
- // is the load-bearing operation; materialization is an optional mirror.
4691
- try {
4692
- await materializeAfterSemanticConsolidation({
4693
- config: this.config,
4694
- memoryDir: targetStorage.dir,
4695
- });
4696
- } catch (err) {
4697
- log.warn(
4698
- `[semantic-consolidation] Codex materialize post-hook failed (non-fatal): ${
4699
- err instanceof Error ? err.message : String(err)
4700
- }`,
4701
- );
4702
- }
4703
-
4704
- // Issue #679 PR 2/5 — async peer profile reasoner runs as part of
4705
- // the REM phase, immediately after semantic consolidation. Gated
4706
- // on `peerProfileReasonerEnabled` (default false, opt-in). Wrapped
4707
- // in a try/catch because reasoner I/O (LLM call, peer-profile
4708
- // writes) must never abort the consolidation result. The reasoner
4709
- // itself also defends against partial failure — see profile-reasoner.ts.
4710
- if (this.config.peerProfileReasonerEnabled) {
4711
- try {
4712
- const { runPeerProfileReasoner } = await import("./peers/index.js");
4713
- const llm = new FallbackLlmClient(
4714
- this.config.gatewayConfig,
4715
- fallbackLlmRuntimeContextFromConfig(this.config),
4716
- );
4717
- const peerResult = await runPeerProfileReasoner({
4718
- memoryDir: targetStorage.dir,
4719
- enabled: true,
4720
- llm,
4721
- model: this.config.peerProfileReasonerModel,
4722
- minInteractions: this.config.peerProfileReasonerMinInteractions,
4723
- maxFieldsPerRun: this.config.peerProfileReasonerMaxFieldsPerRun,
4724
- log: {
4725
- debug: (msg) => log.debug(msg),
4726
- info: (msg) => log.info(msg),
4727
- warn: (msg) => log.warn(msg),
4728
- },
4729
- });
4730
- log.info(
4731
- `[peer-profile-reasoner] complete: peers=${peerResult.peersConsidered}, processed=${peerResult.peersProcessed}, fields=${peerResult.fieldsApplied}`,
4732
- );
4733
- } catch (err) {
4734
- log.warn(
4735
- `[peer-profile-reasoner] post-consolidation hook failed (non-fatal): ${
4736
- err instanceof Error ? err.message : String(err)
4737
- }`,
4738
- );
4739
- }
4740
- }
4741
-
4742
- return result;
4415
+ return this.semanticConsolidationCoordinator.runSemanticConsolidation(options);
4743
4416
  }
4744
4417
 
4745
4418
  async waitForExtractionIdle(timeoutMs: number = 60_000): Promise<boolean> {
@@ -5144,7 +4817,7 @@ export class Orchestrator {
5144
4817
  action: event.action,
5145
4818
  eligibility,
5146
4819
  options: {
5147
- actionsEnabled: this.config.contextCompressionActionsEnabled,
4820
+ actionsEnabled: resolveCompressionCapabilities(this.config).contextCompressionActions,
5148
4821
  maxCompressionTokensPerHour: this.config.maxCompressionTokensPerHour,
5149
4822
  },
5150
4823
  });
@@ -5950,7 +5623,7 @@ export class Orchestrator {
5950
5623
  options.principalOverride.length > 0
5951
5624
  ? options.principalOverride
5952
5625
  : resolvePrincipal(sessionKey, this.config);
5953
- const namespacesEnabled = this.config.namespacesEnabled;
5626
+ const namespacesEnabled = resolveNamespaceCapabilities(this.config).namespaces;
5954
5627
  if (namespacesEnabled && !principal) {
5955
5628
  throw new Error("authentication required: namespaces are enabled and no principal was supplied");
5956
5629
  }
@@ -6419,7 +6092,7 @@ export class Orchestrator {
6419
6092
  for (const memoryPath of paths) {
6420
6093
  if (!memoryPath || isArtifactMemoryPath(memoryPath)) continue;
6421
6094
  if (
6422
- this.config.namespacesEnabled &&
6095
+ resolveNamespaceCapabilities(this.config).namespaces &&
6423
6096
  !recallNamespaces.includes(this.namespaceFromPath(memoryPath))
6424
6097
  ) {
6425
6098
  continue;
@@ -6536,7 +6209,7 @@ export class Orchestrator {
6536
6209
  const memories = (
6537
6210
  await Promise.all(
6538
6211
  Array.from(candidatePaths).map(async (memoryPath) => {
6539
- const namespace = this.config.namespacesEnabled
6212
+ const namespace = resolveNamespaceCapabilities(this.config).namespaces
6540
6213
  ? this.namespaceFromPath(memoryPath)
6541
6214
  : this.config.defaultNamespace;
6542
6215
  const storage = await this.storageRouter.storageFor(namespace);
@@ -7160,7 +6833,7 @@ export class Orchestrator {
7160
6833
  private async resolveStateDirForNamespace(
7161
6834
  namespace: string,
7162
6835
  ): Promise<string> {
7163
- if (!this.config.namespacesEnabled) {
6836
+ if (!resolveNamespaceCapabilities(this.config).namespaces) {
7164
6837
  return path.join(this.config.memoryDir, "state");
7165
6838
  }
7166
6839
  if (namespace !== this.config.defaultNamespace) {
@@ -7480,7 +7153,7 @@ export class Orchestrator {
7480
7153
  }
7481
7154
  const timings: Record<string, string> = {};
7482
7155
  const profileTraceId = this.profiler.startTrace("recall", sessionKey, {
7483
- qmdEnabled: this.config.qmdEnabled,
7156
+ qmdEnabled: resolveQmdCapabilities(this.config).qmd,
7484
7157
  rerankEnabled: caps.rerank,
7485
7158
  parallelRetrieval: caps.parallelRetrieval,
7486
7159
  });
@@ -7753,7 +7426,7 @@ export class Orchestrator {
7753
7426
  && options.principalOverride.length > 0
7754
7427
  ? options.principalOverride
7755
7428
  : resolvePrincipal(sessionKey, this.config);
7756
- const namespacesEnabled = this.config.namespacesEnabled;
7429
+ const namespacesEnabled = resolveNamespaceCapabilities(this.config).namespaces;
7757
7430
  if (namespacesEnabled && !principal) {
7758
7431
  throw new Error("authentication required: namespaces are enabled and no principal was supplied");
7759
7432
  }
@@ -7998,7 +7671,7 @@ export class Orchestrator {
7998
7671
  retrievalQueryLength: retrievalQuery.length,
7999
7672
  recallMode,
8000
7673
  recallResultLimit,
8001
- qmdEnabled: this.config.qmdEnabled,
7674
+ qmdEnabled: resolveQmdCapabilities(this.config).qmd,
8002
7675
  qmdAvailable: this.qmd.isAvailable(),
8003
7676
  recallNamespaces: [],
8004
7677
  source: recallSource,
@@ -8404,7 +8077,7 @@ export class Orchestrator {
8404
8077
  if (
8405
8078
  !this.isRecallSectionEnabled(
8406
8079
  "identity-continuity",
8407
- this.config.identityContinuityEnabled === true,
8080
+ resolveIdentityContinuityCapabilities(this.config).identityContinuity === true,
8408
8081
  )
8409
8082
  )
8410
8083
  return null;
@@ -8644,11 +8317,11 @@ export class Orchestrator {
8644
8317
  const objectiveStatePromise = (async (): Promise<string | null> => {
8645
8318
  const t0 = Date.now();
8646
8319
  if (
8647
- !this.config.objectiveStateMemoryEnabled ||
8648
- !this.config.objectiveStateRecallEnabled ||
8320
+ !resolveObjectiveStateCapabilities(this.config).objectiveStateMemory ||
8321
+ !resolveObjectiveStateCapabilities(this.config).objectiveStateRecall ||
8649
8322
  !this.isRecallSectionEnabled(
8650
8323
  "objective-state",
8651
- this.config.objectiveStateRecallEnabled === true,
8324
+ resolveObjectiveStateCapabilities(this.config).objectiveStateRecall === true,
8652
8325
  )
8653
8326
  ) {
8654
8327
  recordRecallSectionMetric({
@@ -8679,17 +8352,17 @@ export class Orchestrator {
8679
8352
 
8680
8353
  const objectiveStateSearches = await Promise.all(
8681
8354
  recallNamespaces.map(async (namespace) => {
8682
- const storage = this.config.namespacesEnabled
8355
+ const storage = resolveNamespaceCapabilities(this.config).namespaces
8683
8356
  ? await this.getStorage(namespace)
8684
8357
  : null;
8685
8358
  return searchObjectiveStateSnapshots({
8686
- memoryDir: this.config.namespacesEnabled
8359
+ memoryDir: resolveNamespaceCapabilities(this.config).namespaces
8687
8360
  ? storage!.dir
8688
8361
  : this.config.memoryDir,
8689
8362
  objectiveStateStoreDir: objectiveStateStoreOverrideForNamespace({
8690
8363
  memoryDir: this.config.memoryDir,
8691
8364
  configuredStoreDir: this.config.objectiveStateStoreDir,
8692
- namespacesEnabled: this.config.namespacesEnabled,
8365
+ namespacesEnabled: resolveNamespaceCapabilities(this.config).namespaces,
8693
8366
  namespace,
8694
8367
  }),
8695
8368
  query: retrievalQuery,
@@ -8901,11 +8574,11 @@ export class Orchestrator {
8901
8574
  const trustZonePromise = (async (): Promise<string | null> => {
8902
8575
  const t0 = Date.now();
8903
8576
  if (
8904
- !this.config.trustZonesEnabled ||
8905
- !this.config.trustZoneRecallEnabled ||
8577
+ !resolveSecurityCapabilities(this.config).trustZones ||
8578
+ !resolveSecurityCapabilities(this.config).trustZoneRecall ||
8906
8579
  !this.isRecallSectionEnabled(
8907
8580
  "trust-zones",
8908
- this.config.trustZoneRecallEnabled === true,
8581
+ resolveSecurityCapabilities(this.config).trustZoneRecall === true,
8909
8582
  )
8910
8583
  ) {
8911
8584
  recordRecallSectionMetric({
@@ -9593,7 +9266,7 @@ export class Orchestrator {
9593
9266
  qmdFetchLimit,
9594
9267
  qmdHybridFetchLimit,
9595
9268
  {
9596
- namespacesEnabled: this.config.namespacesEnabled,
9269
+ namespacesEnabled: resolveNamespaceCapabilities(this.config).namespaces,
9597
9270
  recallNamespaces,
9598
9271
  resolveNamespace: (p) => this.namespaceFromPath(p),
9599
9272
  queryAwarePrefilter,
@@ -10011,7 +9684,7 @@ export class Orchestrator {
10011
9684
  workspaceDir: this.config.workspaceDir,
10012
9685
  memoryDir: this.config.memoryDir,
10013
9686
  config: this.config.nativeKnowledge,
10014
- recallNamespaces: this.config.namespacesEnabled
9687
+ recallNamespaces: resolveNamespaceCapabilities(this.config).namespaces
10015
9688
  ? recallNamespaces
10016
9689
  : undefined,
10017
9690
  defaultNamespace: this.config.defaultNamespace,
@@ -11085,7 +10758,7 @@ export class Orchestrator {
11085
10758
  let memoryResults = memoryResultsRaw;
11086
10759
 
11087
10760
  // Enforce namespace read policies by filtering paths.
11088
- if (this.config.namespacesEnabled) {
10761
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
11089
10762
  memoryResults = memoryResults.filter((r) =>
11090
10763
  recallNamespaces.includes(this.namespaceFromPath(r.path)),
11091
10764
  );
@@ -11459,7 +11132,7 @@ export class Orchestrator {
11459
11132
  const scopedCandidates = filterRecallCandidates(
11460
11133
  prefilteredEmbeddingResults,
11461
11134
  {
11462
- namespacesEnabled: this.config.namespacesEnabled,
11135
+ namespacesEnabled: resolveNamespaceCapabilities(this.config).namespaces,
11463
11136
  recallNamespaces,
11464
11137
  resolveNamespace: (p) => this.namespaceFromPath(p),
11465
11138
  limit: embeddingFetchLimit,
@@ -11638,7 +11311,7 @@ export class Orchestrator {
11638
11311
  const scopedCandidates = filterRecallCandidates(
11639
11312
  prefilteredEmbeddingResults,
11640
11313
  {
11641
- namespacesEnabled: this.config.namespacesEnabled,
11314
+ namespacesEnabled: resolveNamespaceCapabilities(this.config).namespaces,
11642
11315
  recallNamespaces,
11643
11316
  resolveNamespace: (p) => this.namespaceFromPath(p),
11644
11317
  limit: embeddingFetchLimit,
@@ -12074,7 +11747,7 @@ export class Orchestrator {
12074
11747
  if (
12075
11748
  this.isRecallSectionEnabled(
12076
11749
  "compression-guidelines",
12077
- this.config.compressionGuidelineLearningEnabled === true,
11750
+ resolveCompressionCapabilities(this.config).compressionGuidelineLearning === true,
12078
11751
  )
12079
11752
  ) {
12080
11753
  const compressionGuidelineSection =
@@ -12476,7 +12149,7 @@ export class Orchestrator {
12476
12149
  retrievalQueryLength: retrievalQuery.length,
12477
12150
  recallMode,
12478
12151
  recallResultLimit,
12479
- qmdEnabled: this.config.qmdEnabled,
12152
+ qmdEnabled: resolveQmdCapabilities(this.config).qmd,
12480
12153
  qmdAvailable: this.qmd.isAvailable(),
12481
12154
  recallNamespaces,
12482
12155
  source: recallSource,
@@ -14193,7 +13866,7 @@ export class Orchestrator {
14193
13866
  // #1713: hoist namespaces-enabled once for the scope-routing mirrors
14194
13867
  // (pre-judge + write-loop) so no new scattered config.*Enabled read is
14195
13868
  // introduced (ratchet scatteredConfigFlagReads; see #1523).
14196
- const namespacesEnabled = this.config.namespacesEnabled;
13869
+ const namespacesEnabled = resolveNamespaceCapabilities(this.config).namespaces;
14197
13870
  const profileAutoPromotionAllows = (
14198
13871
  category: string,
14199
13872
  confidence: number,
@@ -14235,7 +13908,7 @@ export class Orchestrator {
14235
13908
  confidence: number,
14236
13909
  ): boolean => {
14237
13910
  if (
14238
- !this.config.namespacesEnabled ||
13911
+ !resolveNamespaceCapabilities(this.config).namespaces ||
14239
13912
  !profileAllowsSharedWrites ||
14240
13913
  !sharedAutoPromotionAllows(category, confidence)
14241
13914
  )
@@ -16769,7 +16442,7 @@ export class Orchestrator {
16769
16442
  // On full rebuild with namespaces enabled, span all configured namespaces so
16770
16443
  // memories written to other namespaces before the index existed are also captured.
16771
16444
  const allMemories =
16772
- needsFullRebuild && this.config.namespacesEnabled
16445
+ needsFullRebuild && resolveNamespaceCapabilities(this.config).namespaces
16773
16446
  ? await this.readAllMemoriesForNamespaces(
16774
16447
  Array.from(
16775
16448
  new Set<string>([
@@ -17356,81 +17029,6 @@ export class Orchestrator {
17356
17029
  return this.compressionGuidelineCoordinator.buildCompressionGuidelineRecallSection();
17357
17030
  }
17358
17031
 
17359
- private actionOutcomePriorDelta(event: MemoryActionEvent): number {
17360
- if (event.outcome === "failed") return -0.3;
17361
- if (event.policyDecision === "deny") return -0.22;
17362
- if (event.policyDecision === "defer") return -0.14;
17363
- if (event.outcome === "skipped") return -0.1;
17364
-
17365
- if (event.outcome !== "applied") return 0;
17366
- switch (event.action) {
17367
- case "store_episode":
17368
- case "store_note":
17369
- case "update_note":
17370
- return 0.08;
17371
- case "create_artifact":
17372
- case "summarize_node":
17373
- case "link_graph":
17374
- return 0.04;
17375
- case "discard":
17376
- return -0.03;
17377
- default:
17378
- return 0;
17379
- }
17380
- }
17381
-
17382
- private async buildLifecycleActionPriors(
17383
- storage: StorageManager = this.storage,
17384
- ): Promise<Map<string, number>> {
17385
- const events = await storage.readMemoryActionEvents(1200);
17386
- if (events.length === 0) return new Map<string, number>();
17387
-
17388
- const nowMs = Date.now();
17389
- const windowMs = 14 * 24 * 60 * 60 * 1000;
17390
- const byMemory = new Map<
17391
- string,
17392
- Array<{ weightedDelta: number; weight: number }>
17393
- >();
17394
-
17395
- for (const event of events) {
17396
- if (
17397
- typeof event.memoryId !== "string" ||
17398
- event.memoryId.trim().length === 0
17399
- )
17400
- continue;
17401
- const ts = Date.parse(event.timestamp);
17402
- if (!Number.isFinite(ts)) continue;
17403
- const ageMs = nowMs - ts;
17404
- if (ageMs < 0 || ageMs > windowMs) continue;
17405
-
17406
- const delta = this.actionOutcomePriorDelta(event);
17407
- if (delta === 0) continue;
17408
-
17409
- const recencyWeight = Math.max(0.2, 1 - ageMs / windowMs);
17410
- const list = byMemory.get(event.memoryId) ?? [];
17411
- if (list.length >= 8) list.shift();
17412
- list.push({
17413
- weightedDelta: delta * recencyWeight,
17414
- weight: recencyWeight,
17415
- });
17416
- byMemory.set(event.memoryId, list);
17417
- }
17418
-
17419
- const out = new Map<string, number>();
17420
- for (const [memoryId, deltas] of byMemory.entries()) {
17421
- if (deltas.length === 0) continue;
17422
- const weightedSum = deltas.reduce(
17423
- (sum, item) => sum + item.weightedDelta,
17424
- 0,
17425
- );
17426
- const weightTotal = deltas.reduce((sum, item) => sum + item.weight, 0);
17427
- if (weightTotal <= 0) continue;
17428
- const score = weightedSum / weightTotal;
17429
- out.set(memoryId, Math.max(-0.25, Math.min(0.15, score)));
17430
- }
17431
- return out;
17432
- }
17433
-
17434
17032
  private async recordScheduledDreamsPhaseRun(
17435
17033
  phase: "lightSleep" | "rem" | "deepSleep",
17436
17034
  itemsProcessed: number,
@@ -17465,315 +17063,23 @@ export class Orchestrator {
17465
17063
  allMemories: MemoryFile[],
17466
17064
  storage: StorageManager = this.storage,
17467
17065
  ): Promise<number> {
17468
- const now = new Date();
17469
- const nowIso = now.toISOString();
17470
- const countsByState: Record<LifecycleState, number> = {
17471
- candidate: 0,
17472
- validated: 0,
17473
- active: 0,
17474
- stale: 0,
17475
- archived: 0,
17476
- };
17477
- const transitionCounts: Record<string, number> = {};
17478
- let updatedCount = 0;
17479
- let disputedCount = 0;
17480
- let evaluatedCount = 0;
17481
- const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
17482
-
17483
- const thresholds = this.effectiveLifecycleThresholds();
17484
- const policy = {
17485
- promoteHeatThreshold: thresholds.promoteHeatThreshold,
17486
- staleDecayThreshold: thresholds.staleDecayThreshold,
17487
- archiveDecayThreshold: thresholds.archiveDecayThreshold,
17488
- protectedCategories: this.config.lifecycleProtectedCategories,
17489
- };
17490
- const actionPriors = await this.buildLifecycleActionPriors(storage);
17491
-
17492
- for (const memory of allMemories) {
17493
- if (
17494
- memory.frontmatter.status === "superseded" ||
17495
- memory.frontmatter.status === "forgotten"
17496
- ) {
17497
- continue;
17498
- }
17499
- evaluatedCount += 1;
17500
- const currentState = resolveLifecycleState(memory.frontmatter);
17501
- const actionPriorScore = actionPriors.get(memory.frontmatter.id);
17502
- const signals: LifecycleSignals | undefined =
17503
- typeof actionPriorScore === "number" &&
17504
- Number.isFinite(actionPriorScore)
17505
- ? { actionPriorScore }
17506
- : undefined;
17507
- const decision = decideLifecycleTransition(memory, policy, now, signals);
17508
- const nextState: LifecycleState =
17509
- memory.frontmatter.status === "archived"
17510
- ? "archived"
17511
- : decision.nextState;
17512
-
17513
- countsByState[nextState] += 1;
17514
- if (memory.frontmatter.verificationState === "disputed") {
17515
- disputedCount += 1;
17516
- }
17517
- if (nextState !== currentState) {
17518
- const key = `${currentState}->${nextState}`;
17519
- transitionCounts[key] = (transitionCounts[key] ?? 0) + 1;
17520
- }
17521
-
17522
- const prevHeat = memory.frontmatter.heatScore;
17523
- const prevDecay = memory.frontmatter.decayScore;
17524
- const scoreDelta =
17525
- Math.abs((prevHeat ?? -1) - decision.heatScore) +
17526
- Math.abs((prevDecay ?? -1) - decision.decayScore);
17527
- const shouldPersist =
17528
- memory.frontmatter.lifecycleState !== nextState ||
17529
- memory.frontmatter.heatScore === undefined ||
17530
- memory.frontmatter.decayScore === undefined ||
17531
- memory.frontmatter.lastValidatedAt === undefined ||
17532
- scoreDelta >= 0.01;
17533
-
17534
- if (!shouldPersist) continue;
17535
-
17536
- const wrote = await storage.writeMemoryFrontmatter(memory, {
17537
- lifecycleState: nextState,
17538
- heatScore: decision.heatScore,
17539
- decayScore: decision.decayScore,
17540
- lastValidatedAt: nowIso,
17541
- });
17542
- if (wrote) updatedCount += 1;
17543
- }
17544
-
17545
- // Report how many memories had frontmatter rewritten so callers can record a
17546
- // catalog write touch for lifecycle-only passes (codex NR-tS).
17547
- if (!lifecycleCaps.lifecycleMetrics) return updatedCount;
17548
-
17549
- const total = evaluatedCount;
17550
- const metrics = {
17551
- generatedAt: nowIso,
17552
- memoriesEvaluated: total,
17553
- memoriesUpdated: updatedCount,
17554
- countsByLifecycleState: countsByState,
17555
- transitionCounts,
17556
- staleRatio: total > 0 ? countsByState.stale / total : 0,
17557
- disputedRatio: total > 0 ? disputedCount / total : 0,
17558
- policy: {
17559
- promoteHeatThreshold: thresholds.promoteHeatThreshold,
17560
- staleDecayThreshold: thresholds.staleDecayThreshold,
17561
- archiveDecayThreshold: thresholds.archiveDecayThreshold,
17562
- protectedCategories: this.config.lifecycleProtectedCategories,
17563
- },
17564
- };
17565
- const metricsPath = path.join(
17566
- storage.dir,
17567
- "state",
17568
- "lifecycle-metrics.json",
17569
- );
17570
- await mkdir(path.dirname(metricsPath), { recursive: true });
17571
- await writeFile(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
17572
- return updatedCount;
17066
+ return this.lifecyclePolicyCoordinator.runLifecyclePolicyPass(allMemories, storage);
17573
17067
  }
17574
-
17575
- /**
17576
- * Archive old, low-importance, rarely-accessed facts (v6.0).
17577
- * Moves eligible facts from facts/ to archive/YYYY-MM-DD/.
17578
- * Returns the number of archived facts.
17579
- */
17580
17068
  private async runFactArchival(
17581
17069
  allMemories: import("./types.js").MemoryFile[],
17582
17070
  ): Promise<number> {
17583
- const now = Date.now();
17584
- const ageCutoffMs = this.config.factArchivalAgeDays * 24 * 60 * 60 * 1000;
17585
- const protectedCategories = new Set(
17586
- this.config.factArchivalProtectedCategories,
17587
- );
17588
- let archivedCount = 0;
17589
-
17590
- for (const memory of allMemories) {
17591
- const fm = memory.frontmatter;
17592
-
17593
- // Skip already-archived or superseded
17594
- if (fm.status && fm.status !== "active") continue;
17595
-
17596
- // Skip protected categories
17597
- if (protectedCategories.has(fm.category)) continue;
17598
-
17599
- // Skip corrections (always keep)
17600
- if (fm.category === "correction") continue;
17601
-
17602
- // Check age requirement
17603
- const createdMs = new Date(fm.created).getTime();
17604
- if (now - createdMs < ageCutoffMs) continue;
17605
-
17606
- // Check importance (only archive low-importance facts)
17607
- const importanceScore = fm.importance?.score ?? 0.5;
17608
- if (importanceScore >= this.config.factArchivalMaxImportance) continue;
17609
-
17610
- // Check access count
17611
- const accessCount = fm.accessCount ?? 0;
17612
- if (accessCount > this.config.factArchivalMaxAccessCount) continue;
17613
-
17614
- // All criteria met — archive
17615
- const result = await this.storage.archiveMemory(memory);
17616
- if (result) {
17617
- // Remove from the same storage-scoped content-hash index since it is
17618
- // no longer in hot search.
17619
- await this.removeContentHashForMemory(
17620
- this.storage,
17621
- memory,
17622
- "fact-archival",
17623
- );
17624
- await this.embeddingFallback.removeFromIndex(memory.frontmatter.id);
17625
- if (
17626
- resolveIndexingCapabilities(this.config).queryAwareIndexing &&
17627
- memory.path &&
17628
- memory.frontmatter?.created
17629
- ) {
17630
- deindexMemory(
17631
- this.config.memoryDir,
17632
- memory.path,
17633
- memory.frontmatter.created,
17634
- memory.frontmatter.tags ?? [],
17635
- );
17636
- }
17637
- archivedCount++;
17638
- }
17639
- }
17640
-
17641
- // Save hash indexes if we removed any entries.
17642
- if (archivedCount > 0) {
17643
- await this.saveContentHashIndexes().catch((err) =>
17644
- log.warn(`content-hash index save failed during archival: ${err}`),
17645
- );
17646
- }
17647
-
17648
- return archivedCount;
17071
+ return this.lifecyclePolicyCoordinator.runFactArchival(allMemories);
17649
17072
  }
17650
-
17651
- /**
17652
- * Run memory summarization if memory count exceeds threshold (Phase 4A).
17653
- */
17654
17073
  private async runSummarization(
17655
17074
  allMemories: import("./types.js").MemoryFile[],
17656
17075
  ): Promise<void> {
17657
- // Only active memories count toward the threshold
17658
- const activeMemories = allMemories.filter(
17659
- (m) => isActiveMemoryStatus(m.frontmatter.status),
17660
- );
17661
-
17662
- if (activeMemories.length < this.config.summarizationTriggerCount) {
17663
- return;
17664
- }
17665
-
17666
- log.info(
17667
- `memory count (${activeMemories.length}) exceeds threshold (${this.config.summarizationTriggerCount}) — running summarization`,
17668
- );
17669
-
17670
- // Sort by creation date, oldest first
17671
- const sorted = activeMemories.sort(
17672
- (a, b) =>
17673
- new Date(a.frontmatter.created).getTime() -
17674
- new Date(b.frontmatter.created).getTime(),
17675
- );
17676
-
17677
- // Keep recent memories, with explicit zero handling so `slice(-0)` does not
17678
- // accidentally keep every memory out of the summarization candidate set.
17679
- const recentToKeep = Math.max(0, this.config.summarizationRecentToKeep);
17680
- const toSummarize = recentToKeep > 0 ? sorted.slice(0, -recentToKeep) : sorted;
17681
-
17682
- // Filter candidates for summarization
17683
- const candidates = toSummarize.filter((m) => {
17684
- // Skip if protected by entity reference
17685
- if (m.frontmatter.entityRef) return false;
17686
-
17687
- // Skip if protected by tag
17688
- const protectedTags = this.config.summarizationProtectedTags;
17689
- if (m.frontmatter.tags.some((t) => protectedTags.includes(t)))
17690
- return false;
17691
-
17692
- // Skip if importance is above threshold
17693
- const importance = m.frontmatter.importance?.score ?? 0.5;
17694
- if (importance >= this.config.summarizationImportanceThreshold)
17695
- return false;
17696
-
17697
- return true;
17698
- });
17699
-
17700
- if (candidates.length < 50) {
17701
- log.debug(
17702
- `only ${candidates.length} candidates for summarization — skipping`,
17703
- );
17704
- return;
17705
- }
17706
-
17707
- // Summarize in batches of 50
17708
- const batchSize = 50;
17709
- for (let i = 0; i < candidates.length; i += batchSize) {
17710
- const batch = candidates.slice(i, i + batchSize);
17711
- const batchData = batch.map((m) => ({
17712
- id: m.frontmatter.id,
17713
- content: m.content,
17714
- category: m.frontmatter.category,
17715
- created: m.frontmatter.created,
17716
- }));
17717
-
17718
- const result = await this.extraction.summarizeMemories(batchData);
17719
- if (!result) continue;
17720
-
17721
- // Create summary
17722
- const summary: MemorySummary = {
17723
- id: `summary-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
17724
- createdAt: new Date().toISOString(),
17725
- timeRangeStart: batch[0].frontmatter.created,
17726
- timeRangeEnd: batch[batch.length - 1].frontmatter.created,
17727
- summaryText: result.summaryText,
17728
- keyFacts: result.keyFacts,
17729
- keyEntities: result.keyEntities,
17730
- sourceEpisodeIds: batch.map((m) => m.frontmatter.id),
17731
- };
17732
-
17733
- await this.storage.writeSummary(summary);
17734
-
17735
- // Archive source memories
17736
- const archived = await this.storage.archiveMemories(
17737
- batch.map((m) => m.frontmatter.id),
17738
- summary.id,
17739
- );
17740
-
17741
- // Catalog write touch (issue #1499 sweep): summarization writes a durable
17742
- // summary and then rewrites source-memory archive status, bypassing the
17743
- // extraction write path. Record the touch after both mutations complete so
17744
- // `lastWriteAt` covers the final archived-state write.
17745
- // #1522: catalog touch handled at the storage chokepoint.
17746
-
17747
- log.info(
17748
- `created summary ${summary.id} from ${batch.length} memories, archived ${archived}`,
17749
- );
17750
- }
17076
+ return this.lifecyclePolicyCoordinator.runSummarization(allMemories);
17751
17077
  }
17752
-
17753
- /**
17754
- * Run topic extraction on all memories (Phase 4B).
17755
- */
17756
17078
  private async runTopicExtraction(
17757
17079
  allMemories: import("./types.js").MemoryFile[],
17758
17080
  ): Promise<void> {
17759
- // Only extract from active memories
17760
- const activeMemories = allMemories.filter(
17761
- (m) => isActiveMemoryStatus(m.frontmatter.status),
17762
- );
17763
-
17764
- if (activeMemories.length === 0) return;
17765
-
17766
- const topics = extractTopics(
17767
- activeMemories,
17768
- this.config.topicExtractionTopN,
17769
- );
17770
- await this.storage.saveTopics(topics);
17771
-
17772
- log.debug(
17773
- `extracted ${topics.length} topics from ${activeMemories.length} memories`,
17774
- );
17081
+ return this.lifecyclePolicyCoordinator.runTopicExtraction(allMemories);
17775
17082
  }
17776
-
17777
17083
  /** Threshold (bytes) at which IDENTITY.md reflections get auto-consolidated */
17778
17084
  private static readonly IDENTITY_CONSOLIDATE_THRESHOLD = 8_000;
17779
17085
 
@@ -17782,14 +17088,14 @@ export class Orchestrator {
17782
17088
  // namespace that accumulated IDENTITY.md reflections must also be eligible for
17783
17089
  // auto-consolidation, otherwise its identity file grows unbounded and is never
17784
17090
  // consolidated. Falls back to the configured set on any catalog read failure.
17785
- const namespaces = this.config.namespacesEnabled
17091
+ const namespaces = resolveNamespaceCapabilities(this.config).namespaces
17786
17092
  ? await this.maintenanceNamespaces()
17787
17093
  : [this.config.defaultNamespace];
17788
17094
 
17789
17095
  for (const namespace of namespaces) {
17790
17096
  const storage = await this.storageRouter.storageFor(namespace);
17791
17097
  const identityNamespace =
17792
- this.config.namespacesEnabled &&
17098
+ resolveNamespaceCapabilities(this.config).namespaces &&
17793
17099
  namespace !== this.config.defaultNamespace
17794
17100
  ? namespace
17795
17101
  : undefined;
@@ -18138,7 +17444,7 @@ export class Orchestrator {
18138
17444
  injectedChars: number;
18139
17445
  truncated: boolean;
18140
17446
  } | null> {
18141
- if (!this.config.identityContinuityEnabled) return null;
17447
+ if (!resolveIdentityContinuityCapabilities(this.config).identityContinuity) return null;
18142
17448
  if (this.config.identityMaxInjectChars <= 0) return null;
18143
17449
 
18144
17450
  const resolved = resolveEffectiveIdentityInjectionMode({
@@ -18227,7 +17533,7 @@ export class Orchestrator {
18227
17533
  private queueEvalShadowRecall(
18228
17534
  record: Omit<EvalShadowRecallRecord, "schemaVersion">,
18229
17535
  ): void {
18230
- if (!this.config.evalHarnessEnabled || !this.config.evalShadowModeEnabled)
17536
+ if (!resolveEvalCapabilities(this.config).evalHarness || !resolveEvalCapabilities(this.config).evalShadowMode)
18231
17537
  return;
18232
17538
  this.evalShadowWriteChain = this.evalShadowWriteChain
18233
17539
  .catch(() => undefined)
@@ -18344,7 +17650,7 @@ export class Orchestrator {
18344
17650
  : this.config.defaultNamespace;
18345
17651
  if (
18346
17652
  recallNamespaces.length === 0 ||
18347
- !this.config.namespacesEnabled ||
17653
+ !resolveNamespaceCapabilities(this.config).namespaces ||
18348
17654
  recallNamespaces.includes(fallbackNamespace)
18349
17655
  ) {
18350
17656
  addStorage(fallbackStorage);
@@ -18471,7 +17777,7 @@ export class Orchestrator {
18471
17777
  recallNamespaces,
18472
17778
  );
18473
17779
  ownerNamespace = ownerStorage?.namespace ?? null;
18474
- if (!ownerNamespace && this.config.namespacesEnabled) return null;
17780
+ if (!ownerNamespace && resolveNamespaceCapabilities(this.config).namespaces) return null;
18475
17781
  }
18476
17782
  ownerNamespace ??= this.namespaceFromPath(memory.path);
18477
17783
  if (
@@ -19030,7 +18336,7 @@ export class Orchestrator {
19030
18336
  pathPrefix?: string;
19031
18337
  pathExcludePrefixes?: readonly string[];
19032
18338
  } {
19033
- if (!this.config.namespacesEnabled) return {};
18339
+ if (!resolveNamespaceCapabilities(this.config).namespaces) return {};
19034
18340
  const memoryDir = path.resolve(this.config.memoryDir);
19035
18341
  const storageDir = path.resolve(targetStorage.dir);
19036
18342
  if (storageDir === memoryDir) {
@@ -19267,7 +18573,7 @@ export class Orchestrator {
19267
18573
  }
19268
18574
  };
19269
18575
 
19270
- const coldQmdEnabled = this.config.qmdColdTierEnabled === true;
18576
+ const coldQmdEnabled = resolveQmdCapabilities(this.config).qmdColdTier === true;
19271
18577
  const coldCollection =
19272
18578
  this.config.qmdColdCollection ?? "openclaw-engram-cold";
19273
18579
  const coldMaxResults =
@@ -19312,7 +18618,7 @@ export class Orchestrator {
19312
18618
  coldFetchLimit,
19313
18619
  coldHybridLimit,
19314
18620
  {
19315
- namespacesEnabled: this.config.namespacesEnabled,
18621
+ namespacesEnabled: resolveNamespaceCapabilities(this.config).namespaces,
19316
18622
  recallNamespaces: options.recallNamespaces,
19317
18623
  resolveNamespace: (p) => this.namespaceFromPath(p),
19318
18624
  collection: coldCollection,
@@ -19359,7 +18665,7 @@ export class Orchestrator {
19359
18665
  if (longTerm.length === 0) return [];
19360
18666
 
19361
18667
  let results = longTerm;
19362
- if (this.config.namespacesEnabled) {
18668
+ if (resolveNamespaceCapabilities(this.config).namespaces) {
19363
18669
  const recallRoots: string[] = [];
19364
18670
  const seenRecallRoots = new Set<string>();
19365
18671
  for (const namespace of options.recallNamespaces) {
@@ -19417,13 +18723,13 @@ export class Orchestrator {
19417
18723
  if (results.length === 0) return [];
19418
18724
 
19419
18725
  const isFullModeGraphAssist =
19420
- this.config.qmdTierParityGraphEnabled &&
18726
+ resolveQmdCapabilities(this.config).qmdTierParityGraph &&
19421
18727
  graphCaps.multiGraphMemory &&
19422
18728
  caps.graphAssistInFullMode &&
19423
18729
  options.recallMode === "full" &&
19424
18730
  results.length >= Math.max(1, this.config.graphAssistMinSeedResults ?? 3);
19425
18731
  const shouldRunGraphExpansion =
19426
- this.config.qmdTierParityGraphEnabled &&
18732
+ resolveQmdCapabilities(this.config).qmdTierParityGraph &&
19427
18733
  (options.recallMode === "graph_mode" || isFullModeGraphAssist);
19428
18734
 
19429
18735
  if (shouldRunGraphExpansion) {
@@ -19620,7 +18926,7 @@ export class Orchestrator {
19620
18926
 
19621
18927
  // Build entries from buffer, merging with existing counts
19622
18928
  const entries: AccessTrackingEntry[] = [];
19623
- const namespaces = this.config.namespacesEnabled
18929
+ const namespaces = resolveNamespaceCapabilities(this.config).namespaces
19624
18930
  ? Array.from(
19625
18931
  new Set<string>([
19626
18932
  this.config.defaultNamespace,
@@ -20580,7 +19886,7 @@ export class Orchestrator {
20580
19886
  }
20581
19887
 
20582
19888
  private namespaceFromPath(p: string): string {
20583
- if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
19889
+ if (!resolveNamespaceCapabilities(this.config).namespaces) return this.config.defaultNamespace;
20584
19890
  const parts = qmdCollectionPathParts(p);
20585
19891
  const collectionNamespace = parts
20586
19892
  ? this.qmdCollectionNamespaceFromPrefix(parts.collection)