@remnic/core 9.3.690 → 9.3.692

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 (294) hide show
  1. package/dist/access-boundary.d.ts +7 -6
  2. package/dist/access-boundary.js +21 -21
  3. package/dist/access-cli.js +49 -47
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +7 -6
  6. package/dist/access-http.js +26 -26
  7. package/dist/access-mcp.d.ts +7 -6
  8. package/dist/access-mcp.js +23 -23
  9. package/dist/access-operations.d.ts +7 -6
  10. package/dist/access-operations.js +22 -22
  11. package/dist/{access-service-Dujr3MTm.d.ts → access-service-DvApf9LZ.d.ts} +2 -2
  12. package/dist/access-service.d.ts +7 -6
  13. package/dist/access-service.js +20 -20
  14. package/dist/access-surface-catalog.d.ts +7 -6
  15. package/dist/action-confidence.d.ts +1 -1
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-recall.d.ts +1 -1
  18. package/dist/active-recall.js +1 -1
  19. package/dist/adapters/index.js +4 -4
  20. package/dist/adapters/registry.js +2 -2
  21. package/dist/{auto-sync-NUQWSFQD.js → auto-sync-RWXT6P5M.js} +5 -5
  22. package/dist/behavior-learner.d.ts +1 -1
  23. package/dist/behavior-signals.d.ts +1 -1
  24. package/dist/bootstrap.d.ts +6 -5
  25. package/dist/briefing.d.ts +1 -1
  26. package/dist/briefing.js +5 -5
  27. package/dist/buffer-surprise-report.d.ts +1 -1
  28. package/dist/buffer.d.ts +1 -1
  29. package/dist/calibration.d.ts +1 -1
  30. package/dist/calibration.js +2 -2
  31. package/dist/capabilities.d.ts +1 -1
  32. package/dist/{catalog-CKPtJ114.d.ts → catalog-BEWLjdOr.d.ts} +1 -1
  33. package/dist/causal-behavior.d.ts +1 -1
  34. package/dist/causal-consolidation.d.ts +1 -1
  35. package/dist/causal-consolidation.js +8 -8
  36. package/dist/{chunk-GTDH3IUH.js → chunk-2D27ODNX.js} +2 -2
  37. package/dist/{chunk-46URPRE6.js → chunk-2EN5WZQB.js} +2 -2
  38. package/dist/{chunk-3K37VPM3.js → chunk-3BRA3N5A.js} +2 -2
  39. package/dist/{chunk-RTFAZOIR.js → chunk-3VVH6ZQW.js} +2 -2
  40. package/dist/{chunk-ZU7N3S6V.js → chunk-4IQNAOP3.js} +6 -142
  41. package/dist/chunk-4IQNAOP3.js.map +1 -0
  42. package/dist/{chunk-FIVDN2SM.js → chunk-4RGWCRIJ.js} +4 -4
  43. package/dist/{chunk-AGJH5ISO.js → chunk-5KBNNUXS.js} +2 -2
  44. package/dist/{chunk-X7RLU5CR.js → chunk-66HROLW5.js} +2 -2
  45. package/dist/chunk-6C2BMUZA.js +444 -0
  46. package/dist/chunk-6C2BMUZA.js.map +1 -0
  47. package/dist/{chunk-TYF3D4MS.js → chunk-6ZNIUGXB.js} +11 -11
  48. package/dist/{chunk-UPIBE2DK.js → chunk-ADUAF5EV.js} +2 -2
  49. package/dist/{chunk-LLONI6PY.js → chunk-AFWEUTMS.js} +2 -2
  50. package/dist/{chunk-ZUDM75KG.js → chunk-DCWIQFNA.js} +4 -4
  51. package/dist/{chunk-OMKUJTVJ.js → chunk-DO7MDZMU.js} +4 -4
  52. package/dist/{chunk-NGFEWFNK.js → chunk-DQHZA7Y3.js} +1 -1
  53. package/dist/chunk-DQHZA7Y3.js.map +1 -0
  54. package/dist/{chunk-LXIEXSHU.js → chunk-E6BOD3UJ.js} +2 -2
  55. package/dist/{chunk-OBXTMFZQ.js → chunk-ECZPWSB5.js} +2 -2
  56. package/dist/{chunk-CWE74HRG.js → chunk-FM7KLRKN.js} +3 -3
  57. package/dist/{chunk-BFVPIKDY.js → chunk-GCOSQBQ2.js} +3 -3
  58. package/dist/chunk-HXNMDPHN.js +149 -0
  59. package/dist/chunk-HXNMDPHN.js.map +1 -0
  60. package/dist/{chunk-MDYG7VI7.js → chunk-IREVVFTM.js} +10 -1
  61. package/dist/{chunk-MDYG7VI7.js.map → chunk-IREVVFTM.js.map} +1 -1
  62. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  63. package/dist/{chunk-CP2NZQLT.js → chunk-KYH53EFD.js} +6 -6
  64. package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
  65. package/dist/{chunk-SB6CQTKP.js → chunk-MCV2DD6F.js} +2 -2
  66. package/dist/{chunk-3FC6LW6T.js → chunk-MRFVMSTR.js} +2 -2
  67. package/dist/{chunk-WDXCNJSF.js → chunk-MXZWKOOR.js} +2 -2
  68. package/dist/{chunk-NSVXK7M5.js → chunk-NEILAC3H.js} +3 -3
  69. package/dist/{chunk-RGNEARXW.js → chunk-NUZ3N445.js} +7 -7
  70. package/dist/{chunk-WROKD3XC.js → chunk-O4ZIV62T.js} +35 -35
  71. package/dist/{chunk-PHZKALOE.js → chunk-QFG23CRE.js} +2 -2
  72. package/dist/{chunk-A4HH2EWA.js → chunk-QVG4LAQO.js} +2 -2
  73. package/dist/{chunk-54PVJDO5.js → chunk-SAC5V6JT.js} +2 -2
  74. package/dist/{chunk-XKUKJIOY.js → chunk-SEK4XIDN.js} +6 -6
  75. package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
  76. package/dist/{chunk-2N6UNJSX.js → chunk-U2OBZSGG.js} +2 -2
  77. package/dist/{chunk-BKAMHZYR.js → chunk-UOCLRM74.js} +2 -2
  78. package/dist/{chunk-A7UZR6RA.js → chunk-UTUYAPLY.js} +14 -1
  79. package/dist/chunk-UTUYAPLY.js.map +1 -0
  80. package/dist/{chunk-Z7KILAOU.js → chunk-V56S3A23.js} +19 -19
  81. package/dist/{chunk-CUNTLEJP.js → chunk-VFVG4NS2.js} +30 -9
  82. package/dist/chunk-VFVG4NS2.js.map +1 -0
  83. package/dist/{chunk-UD5OKH4J.js → chunk-W4ELOUNX.js} +2 -2
  84. package/dist/{chunk-CE6CBRCV.js → chunk-W4MICQIZ.js} +89 -407
  85. package/dist/chunk-W4MICQIZ.js.map +1 -0
  86. package/dist/{chunk-GR77Z2BM.js → chunk-WIKNVFBS.js} +2 -2
  87. package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
  88. package/dist/{chunk-67MMWC74.js → chunk-ZLXGFAML.js} +2 -2
  89. package/dist/{cli-BkDp6WNi.d.ts → cli-BjsY9Le7.d.ts} +3 -3
  90. package/dist/cli.d.ts +8 -7
  91. package/dist/cli.js +45 -44
  92. package/dist/compounding/engine.d.ts +1 -1
  93. package/dist/compounding/engine.js +5 -5
  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/config.js +1 -1
  98. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  99. package/dist/connectors/codex-materialize-runner.js +5 -5
  100. package/dist/connectors/codex-materialize.d.ts +1 -1
  101. package/dist/connectors/index.d.ts +1 -1
  102. package/dist/connectors/index.js +5 -5
  103. package/dist/consolidation-provenance-check.d.ts +1 -1
  104. package/dist/consolidation-undo.d.ts +1 -1
  105. package/dist/contradiction/index.d.ts +1 -1
  106. package/dist/conversation-index/backend.d.ts +1 -1
  107. package/dist/conversation-index/backend.js +2 -2
  108. package/dist/conversation-index/chunker.d.ts +1 -1
  109. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  110. package/dist/conversation-index/indexer.d.ts +1 -1
  111. package/dist/conversation-index/search.d.ts +1 -1
  112. package/dist/day-summary.d.ts +1 -1
  113. package/dist/delinearize.d.ts +1 -1
  114. package/dist/direct-answer-wiring.d.ts +1 -1
  115. package/dist/direct-answer.d.ts +1 -1
  116. package/dist/embedding-fallback.d.ts +1 -1
  117. package/dist/enrichment/index.d.ts +1 -1
  118. package/dist/entity-retrieval.d.ts +1 -1
  119. package/dist/entity-retrieval.js +5 -5
  120. package/dist/entity-schema.d.ts +1 -1
  121. package/dist/event-time.d.ts +35 -0
  122. package/dist/event-time.js +325 -0
  123. package/dist/event-time.js.map +1 -0
  124. package/dist/explicit-capture.d.ts +6 -5
  125. package/dist/extraction-judge-telemetry.d.ts +1 -1
  126. package/dist/extraction-judge-training.d.ts +1 -1
  127. package/dist/extraction-judge.d.ts +1 -1
  128. package/dist/extraction-judge.js +3 -3
  129. package/dist/extraction.d.ts +1 -1
  130. package/dist/extraction.js +3 -3
  131. package/dist/fallback-llm.d.ts +1 -1
  132. package/dist/fallback-llm.js +2 -2
  133. package/dist/identity-continuity.d.ts +1 -1
  134. package/dist/importance.d.ts +1 -1
  135. package/dist/index.d.ts +11 -10
  136. package/dist/index.js +77 -75
  137. package/dist/index.js.map +1 -1
  138. package/dist/intent.d.ts +1 -1
  139. package/dist/lcm/engine.d.ts +1 -1
  140. package/dist/lcm/index.d.ts +1 -1
  141. package/dist/lcm/tools.d.ts +1 -1
  142. package/dist/lifecycle.d.ts +1 -1
  143. package/dist/live-connectors-runner.d.ts +1 -1
  144. package/dist/local-llm.d.ts +1 -1
  145. package/dist/maintenance/memory-governance.d.ts +1 -1
  146. package/dist/maintenance/memory-governance.js +5 -5
  147. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  148. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  149. package/dist/mcp-memory-inspector-app.d.ts +7 -6
  150. package/dist/memory-action-policy.d.ts +1 -1
  151. package/dist/memory-cache.d.ts +1 -1
  152. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  153. package/dist/memory-projection-store.d.ts +1 -1
  154. package/dist/memory-provenance.d.ts +1 -1
  155. package/dist/memory-worth-outcomes.d.ts +1 -1
  156. package/dist/models-json.d.ts +1 -1
  157. package/dist/namespaces/migrate.d.ts +2 -2
  158. package/dist/namespaces/migrate.js +15 -15
  159. package/dist/namespaces/principal.d.ts +1 -1
  160. package/dist/namespaces/search.d.ts +1 -1
  161. package/dist/namespaces/search.js +9 -9
  162. package/dist/namespaces/storage.d.ts +2 -2
  163. package/dist/namespaces/storage.js +5 -5
  164. package/dist/native-knowledge.d.ts +1 -1
  165. package/dist/operator-toolkit.d.ts +1 -1
  166. package/dist/operator-toolkit.js +20 -19
  167. package/dist/orchestration/maintenance.d.ts +112 -0
  168. package/dist/orchestration/maintenance.js +39 -0
  169. package/dist/orchestration/maintenance.js.map +1 -0
  170. package/dist/{orchestrator-B7ixmUkP.d.ts → orchestrator-CNJCYjZ1.d.ts} +12 -36
  171. package/dist/orchestrator.d.ts +5 -4
  172. package/dist/orchestrator.js +42 -40
  173. package/dist/patterns-cli.d.ts +1 -1
  174. package/dist/policy-runtime.d.ts +1 -1
  175. package/dist/provenance.d.ts +1 -1
  176. package/dist/qmd-recall-cache.d.ts +1 -1
  177. package/dist/qmd.d.ts +1 -1
  178. package/dist/recall-disclosure-escalation.d.ts +1 -1
  179. package/dist/recall-explain-renderer.d.ts +1 -1
  180. package/dist/recall-explain-renderer.js +3 -3
  181. package/dist/recall-planner-llm.d.ts +1 -1
  182. package/dist/recall-planner-llm.js +2 -2
  183. package/dist/recall-state.d.ts +1 -1
  184. package/dist/recall-tag-filter.d.ts +1 -1
  185. package/dist/recall-xray-cli.d.ts +1 -1
  186. package/dist/recall-xray-cli.js +4 -4
  187. package/dist/recall-xray-renderer.d.ts +1 -1
  188. package/dist/recall-xray-renderer.js +3 -3
  189. package/dist/recall-xray.d.ts +1 -1
  190. package/dist/recall-xray.js +2 -2
  191. package/dist/resolve-auth-token.d.ts +1 -1
  192. package/dist/resume-bundles.js +2 -2
  193. package/dist/retrieval-agents.d.ts +1 -1
  194. package/dist/retrieval-tiers.d.ts +1 -1
  195. package/dist/routing/engine.d.ts +1 -1
  196. package/dist/routing/store.d.ts +1 -1
  197. package/dist/search/embed-helper.d.ts +1 -1
  198. package/dist/search/factory.d.ts +1 -1
  199. package/dist/search/factory.js +8 -8
  200. package/dist/search/index.d.ts +1 -1
  201. package/dist/search/index.js +13 -13
  202. package/dist/search/lancedb-backend.d.ts +1 -1
  203. package/dist/search/lancedb-backend.js +2 -2
  204. package/dist/search/meilisearch-backend.d.ts +1 -1
  205. package/dist/search/meilisearch-backend.js +2 -2
  206. package/dist/search/noop-backend.d.ts +1 -1
  207. package/dist/search/orama-backend.d.ts +1 -1
  208. package/dist/search/orama-backend.js +2 -2
  209. package/dist/search/port.d.ts +1 -1
  210. package/dist/search/remote-backend.d.ts +1 -1
  211. package/dist/{semantic-consolidation-CWch5uM7.d.ts → semantic-consolidation-D5U0Mrdb.d.ts} +1 -1
  212. package/dist/semantic-consolidation.d.ts +2 -2
  213. package/dist/semantic-consolidation.js +6 -6
  214. package/dist/semantic-rule-promotion.js +5 -5
  215. package/dist/semantic-rule-verifier.d.ts +1 -1
  216. package/dist/semantic-rule-verifier.js +5 -5
  217. package/dist/session-observer-bands.d.ts +1 -1
  218. package/dist/session-observer-state.d.ts +1 -1
  219. package/dist/shared-context/manager.d.ts +1 -1
  220. package/dist/signal.d.ts +1 -1
  221. package/dist/storage.d.ts +3 -1
  222. package/dist/storage.js +4 -4
  223. package/dist/summarizer.d.ts +1 -1
  224. package/dist/summarizer.js +3 -3
  225. package/dist/summary-snapshot.d.ts +1 -1
  226. package/dist/temporal-supersession.d.ts +1 -1
  227. package/dist/temporal-supersession.js +2 -2
  228. package/dist/temporal-validity.d.ts +22 -2
  229. package/dist/temporal-validity.js +3 -1
  230. package/dist/threading.d.ts +1 -1
  231. package/dist/tier-migration.d.ts +1 -1
  232. package/dist/tier-routing.d.ts +1 -1
  233. package/dist/topics.d.ts +1 -1
  234. package/dist/transcript.d.ts +1 -1
  235. package/dist/{types-Dm5xxVrr.d.ts → types-2NoBoRBM.d.ts} +38 -0
  236. package/dist/types.d.ts +1 -1
  237. package/dist/types.js +1 -1
  238. package/dist/utility-runtime.d.ts +1 -1
  239. package/dist/verified-recall.js +5 -5
  240. package/package.json +12 -2
  241. package/src/bitemporal-frontmatter.test.ts +177 -0
  242. package/src/config.ts +13 -0
  243. package/src/event-time.test.ts +514 -0
  244. package/src/event-time.ts +548 -0
  245. package/src/orchestration/maintenance.test.ts +738 -0
  246. package/src/orchestration/maintenance.ts +557 -0
  247. package/src/orchestrator-flush.test.ts +0 -599
  248. package/src/orchestrator.ts +60 -420
  249. package/src/storage.ts +34 -0
  250. package/src/temporal-validity.ts +34 -0
  251. package/src/types.ts +38 -0
  252. package/dist/chunk-A7UZR6RA.js.map +0 -1
  253. package/dist/chunk-CE6CBRCV.js.map +0 -1
  254. package/dist/chunk-CUNTLEJP.js.map +0 -1
  255. package/dist/chunk-NGFEWFNK.js.map +0 -1
  256. package/dist/chunk-ZU7N3S6V.js.map +0 -1
  257. /package/dist/{auto-sync-NUQWSFQD.js.map → auto-sync-RWXT6P5M.js.map} +0 -0
  258. /package/dist/{chunk-GTDH3IUH.js.map → chunk-2D27ODNX.js.map} +0 -0
  259. /package/dist/{chunk-46URPRE6.js.map → chunk-2EN5WZQB.js.map} +0 -0
  260. /package/dist/{chunk-3K37VPM3.js.map → chunk-3BRA3N5A.js.map} +0 -0
  261. /package/dist/{chunk-RTFAZOIR.js.map → chunk-3VVH6ZQW.js.map} +0 -0
  262. /package/dist/{chunk-FIVDN2SM.js.map → chunk-4RGWCRIJ.js.map} +0 -0
  263. /package/dist/{chunk-AGJH5ISO.js.map → chunk-5KBNNUXS.js.map} +0 -0
  264. /package/dist/{chunk-X7RLU5CR.js.map → chunk-66HROLW5.js.map} +0 -0
  265. /package/dist/{chunk-TYF3D4MS.js.map → chunk-6ZNIUGXB.js.map} +0 -0
  266. /package/dist/{chunk-UPIBE2DK.js.map → chunk-ADUAF5EV.js.map} +0 -0
  267. /package/dist/{chunk-LLONI6PY.js.map → chunk-AFWEUTMS.js.map} +0 -0
  268. /package/dist/{chunk-ZUDM75KG.js.map → chunk-DCWIQFNA.js.map} +0 -0
  269. /package/dist/{chunk-OMKUJTVJ.js.map → chunk-DO7MDZMU.js.map} +0 -0
  270. /package/dist/{chunk-LXIEXSHU.js.map → chunk-E6BOD3UJ.js.map} +0 -0
  271. /package/dist/{chunk-OBXTMFZQ.js.map → chunk-ECZPWSB5.js.map} +0 -0
  272. /package/dist/{chunk-CWE74HRG.js.map → chunk-FM7KLRKN.js.map} +0 -0
  273. /package/dist/{chunk-BFVPIKDY.js.map → chunk-GCOSQBQ2.js.map} +0 -0
  274. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  275. /package/dist/{chunk-CP2NZQLT.js.map → chunk-KYH53EFD.js.map} +0 -0
  276. /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
  277. /package/dist/{chunk-SB6CQTKP.js.map → chunk-MCV2DD6F.js.map} +0 -0
  278. /package/dist/{chunk-3FC6LW6T.js.map → chunk-MRFVMSTR.js.map} +0 -0
  279. /package/dist/{chunk-WDXCNJSF.js.map → chunk-MXZWKOOR.js.map} +0 -0
  280. /package/dist/{chunk-NSVXK7M5.js.map → chunk-NEILAC3H.js.map} +0 -0
  281. /package/dist/{chunk-RGNEARXW.js.map → chunk-NUZ3N445.js.map} +0 -0
  282. /package/dist/{chunk-WROKD3XC.js.map → chunk-O4ZIV62T.js.map} +0 -0
  283. /package/dist/{chunk-PHZKALOE.js.map → chunk-QFG23CRE.js.map} +0 -0
  284. /package/dist/{chunk-A4HH2EWA.js.map → chunk-QVG4LAQO.js.map} +0 -0
  285. /package/dist/{chunk-54PVJDO5.js.map → chunk-SAC5V6JT.js.map} +0 -0
  286. /package/dist/{chunk-XKUKJIOY.js.map → chunk-SEK4XIDN.js.map} +0 -0
  287. /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
  288. /package/dist/{chunk-2N6UNJSX.js.map → chunk-U2OBZSGG.js.map} +0 -0
  289. /package/dist/{chunk-BKAMHZYR.js.map → chunk-UOCLRM74.js.map} +0 -0
  290. /package/dist/{chunk-Z7KILAOU.js.map → chunk-V56S3A23.js.map} +0 -0
  291. /package/dist/{chunk-UD5OKH4J.js.map → chunk-W4ELOUNX.js.map} +0 -0
  292. /package/dist/{chunk-GR77Z2BM.js.map → chunk-WIKNVFBS.js.map} +0 -0
  293. /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
  294. /package/dist/{chunk-67MMWC74.js.map → chunk-ZLXGFAML.js.map} +0 -0
@@ -0,0 +1,557 @@
1
+ /**
2
+ * Maintenance scheduler — extracted from the orchestrator (issue #1526 PR1).
3
+ *
4
+ * Owns the WHEN of memory maintenance:
5
+ * - cron auto-registration (writes jobs.json entries the OpenClaw cron
6
+ * daemon picks up)
7
+ * - debounced + singleflight QMD index maintenance (re-index + embed
8
+ * after writes)
9
+ * - consolidation scheduling trigger (count + interval gated)
10
+ *
11
+ * Does NOT own the WHAT — the actual job runners (runSemanticConsolidation,
12
+ * runPatternReinforcement, runDeepSleepGovernanceNow, runConsolidation)
13
+ * remain on the orchestrator because they need the full LLM + extraction
14
+ * dependency surface. The scheduler invokes consolidation through a
15
+ * callback so the orchestrator stays the source of truth for that work.
16
+ *
17
+ * Behavior-preserving move from orchestrator.ts. No logic changes — the
18
+ * orchestrator keeps thin delegating methods so existing tests that stub
19
+ * `orchestrator.requestQmdMaintenance` etc. continue to work.
20
+ */
21
+
22
+ import { existsSync } from "node:fs";
23
+ import path from "node:path";
24
+
25
+ import type { PluginConfig } from "../types.js";
26
+ import type { SearchBackend } from "../search/port.js";
27
+ import {
28
+ NamespaceSearchRouter,
29
+ type NamespaceUpdateResult,
30
+ } from "../namespaces/search.js";
31
+ import type { NamespaceCatalog } from "../namespaces/catalog.js";
32
+ import {
33
+ planNamespaceMaintenance,
34
+ runNamespaceMaintenanceBatchPlan,
35
+ type NamespaceMaintenancePlan,
36
+ type NamespaceMaintenanceSkipReason,
37
+ } from "../maintenance/namespace-planner.js";
38
+ import {
39
+ ensureContradictionScanCron,
40
+ ensureDaySummaryCron,
41
+ ensureGraphEdgeDecayCron,
42
+ ensureNightlyGovernanceCron,
43
+ ensurePatternReinforcementCron,
44
+ ensureProceduralMiningCron,
45
+ graphEdgeDecayCadenceToCronExpr,
46
+ } from "../maintenance/memory-governance-cron.js";
47
+ import { resolveHomeDir } from "../runtime/env.js";
48
+ import { log } from "../logger.js";
49
+
50
+ /** Reason a QMD maintenance pass was skipped, used for status recording. */
51
+ function qmdMaintenanceSkipReasonForError(
52
+ error: unknown,
53
+ ): NamespaceMaintenanceSkipReason | null {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ return /^QMD (?:update|embed) skipped by .*min-interval gate$/.test(message)
56
+ ? "throttled"
57
+ : null;
58
+ }
59
+
60
+ /** Dependencies injected by the orchestrator. All stable references. */
61
+ export interface MaintenanceSchedulerDeps {
62
+ config: PluginConfig;
63
+ /** Live accessor — the orchestrator can reassign its qmd backend after
64
+ * construction (e.g. swap to NoopSearchBackend when the collection is
65
+ * missing), and the scheduler must always observe the current backend. */
66
+ getQmd: () => SearchBackend;
67
+ namespaceSearchRouter: NamespaceSearchRouter;
68
+ namespaceCatalog: NamespaceCatalog;
69
+ }
70
+
71
+ /**
72
+ * Schedules and debounces memory maintenance work. Owns the cadence +
73
+ * singleflight state that previously lived as private orchestrator fields.
74
+ *
75
+ * The orchestrator constructs one instance in its constructor and delegates
76
+ * the public scheduling entrypoints to it. Job execution stays on the
77
+ * orchestrator (LLM-heavy) and is invoked through callbacks.
78
+ */
79
+ export class MaintenanceScheduler {
80
+ // ── Consolidation scheduling state ──
81
+ private nonZeroExtractionsSinceConsolidation = 0;
82
+ private lastConsolidationRunAtMs = 0;
83
+ private consolidationInFlight = false;
84
+
85
+ // ── QMD maintenance scheduling state ──
86
+ private qmdMaintenanceTimer: NodeJS.Timeout | null = null;
87
+ private qmdMaintenancePending = false;
88
+ private qmdMaintenanceInFlight = false;
89
+ private lastQmdEmbedAtMs = 0;
90
+ private lastQmdEmbedAtMsByNamespace = new Map<string, number>();
91
+
92
+ constructor(private readonly deps: MaintenanceSchedulerDeps) {}
93
+
94
+ // ───────────────────────────────────────────────────────────────────────
95
+ // Cron auto-registration
96
+ // ───────────────────────────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Register every cron job the daemon should run, gated by its config flag.
100
+ * Each registration is independent and non-fatal — a failure in one does
101
+ * not block the others (mirrors the orchestrator's prior sequential try/
102
+ * catch behavior exactly).
103
+ *
104
+ * Returns when all eligible registrations have completed so callers that
105
+ * `await deferredReady` can rely on jobs.json being current.
106
+ */
107
+ async autoRegisterCrons(_signal: AbortSignal): Promise<void> {
108
+ if (this.deps.config.daySummaryEnabled) {
109
+ try {
110
+ await this.autoRegisterDaySummaryCron();
111
+ } catch (err) {
112
+ log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
113
+ }
114
+ }
115
+ if (this.deps.config.nightlyGovernanceCronAutoRegister) {
116
+ try {
117
+ await this.autoRegisterNightlyGovernanceCron();
118
+ } catch (err) {
119
+ log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);
120
+ }
121
+ }
122
+ if (this.deps.config.procedural?.proceduralMiningCronAutoRegister) {
123
+ try {
124
+ await this.autoRegisterProceduralMiningCron();
125
+ } catch (err) {
126
+ log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);
127
+ }
128
+ }
129
+ if (this.deps.config.contradictionScan?.enabled) {
130
+ try {
131
+ await this.autoRegisterContradictionScanCron();
132
+ } catch (err) {
133
+ log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);
134
+ }
135
+ }
136
+ if (this.deps.config.patternReinforcementEnabled) {
137
+ try {
138
+ await this.autoRegisterPatternReinforcementCron();
139
+ } catch (err) {
140
+ log.debug(`pattern reinforcement cron auto-register failed (non-fatal): ${err}`);
141
+ }
142
+ }
143
+ if (this.deps.config.graphEdgeDecayEnabled) {
144
+ try {
145
+ await this.autoRegisterGraphEdgeDecayCron();
146
+ } catch (err) {
147
+ log.debug(`graph edge decay cron auto-register failed (non-fatal): ${err}`);
148
+ }
149
+ }
150
+ }
151
+
152
+ async autoRegisterDaySummaryCron(): Promise<void> {
153
+ const home = resolveHomeDir();
154
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
155
+
156
+ try {
157
+ if (!existsSync(jobsPath)) {
158
+ log.debug("day-summary cron: jobs.json not found, skipping auto-register");
159
+ return;
160
+ }
161
+
162
+ // Resolve an OpenClaw cron-routing model only in gateway mode. In plugin
163
+ // mode, summaryModel is a direct-client model id for Remnic's own LLM
164
+ // calls and may be unroutable as an OpenClaw agentTurn model.
165
+ const rawSummaryModel = this.deps.config.summaryModel;
166
+ const taskPrimary = this.deps.config.taskModelChain?.primary;
167
+ const isGateway = this.deps.config.modelSource === "gateway";
168
+ const model = isGateway ? (rawSummaryModel || taskPrimary || undefined) : undefined;
169
+ // Attach task-chain fallbacks only when the model matches the task-chain
170
+ // primary. If summaryModel is a distinct override, its fallbacks would
171
+ // be unrelated to the task chain. Also append gateway default models as
172
+ // tail fallbacks (de-duped) so a task-chain outage doesn't stop the cron
173
+ // before reaching the gateway default chain. Mirrors hourly cron pattern.
174
+ const fallbacks: string[] = [];
175
+ if (model && taskPrimary && model === taskPrimary) {
176
+ const seen = new Set<string>(model ? [model] : []);
177
+ const addUnique = (value: string | undefined) => {
178
+ if (typeof value !== "string") return;
179
+ const trimmed = value.trim();
180
+ if (trimmed.length > 0 && !seen.has(trimmed)) {
181
+ seen.add(trimmed);
182
+ fallbacks.push(trimmed);
183
+ }
184
+ };
185
+ for (const fb of this.deps.config.taskModelChain?.fallbacks ?? []) addUnique(fb);
186
+ const gwDefaults = this.deps.config.gatewayConfig?.agents?.defaults?.model;
187
+ addUnique(gwDefaults?.primary);
188
+ if (Array.isArray(gwDefaults?.fallbacks)) {
189
+ for (const fb of gwDefaults.fallbacks) addUnique(fb);
190
+ }
191
+ }
192
+
193
+ const timezone =
194
+ this.deps.config.daySummaryTimezone ||
195
+ Intl.DateTimeFormat().resolvedOptions().timeZone;
196
+
197
+ const result = await ensureDaySummaryCron(jobsPath, {
198
+ timezone,
199
+ ...(model ? { model } : {}),
200
+ ...(fallbacks.length > 0 ? { fallbacks } : {}),
201
+ });
202
+ if (result.created) {
203
+ log.info(
204
+ `day-summary cron auto-registered (${result.jobId}, 23:47 ${timezone}${model ? `, model: ${model}` : ""})`,
205
+ );
206
+ } else if (result.updated) {
207
+ log.info(
208
+ `day-summary cron reconciled (${result.jobId}, timezone: ${timezone}${model ? `, model: ${model}` : ""})`,
209
+ );
210
+ } else {
211
+ log.debug("day-summary cron already up to date");
212
+ }
213
+ } catch (err) {
214
+ log.debug(`day-summary cron auto-register error: ${err}`);
215
+ }
216
+ }
217
+
218
+ async autoRegisterNightlyGovernanceCron(): Promise<void> {
219
+ const home = resolveHomeDir();
220
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
221
+
222
+ try {
223
+ if (!existsSync(jobsPath)) {
224
+ log.debug("nightly governance cron: jobs.json not found, skipping auto-register");
225
+ return;
226
+ }
227
+
228
+ const created = await ensureNightlyGovernanceCron(jobsPath, {
229
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
230
+ });
231
+ if (created.created) {
232
+ log.info(
233
+ `nightly governance cron auto-registered (${created.jobId}, 02:23 ${Intl.DateTimeFormat().resolvedOptions().timeZone})`,
234
+ );
235
+ } else {
236
+ log.debug("nightly governance cron already exists, skipping auto-register");
237
+ }
238
+ } catch (err) {
239
+ log.debug(`nightly governance cron auto-register error: ${err}`);
240
+ }
241
+ }
242
+
243
+ async autoRegisterProceduralMiningCron(): Promise<void> {
244
+ const home = resolveHomeDir();
245
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
246
+ try {
247
+ if (!existsSync(jobsPath)) {
248
+ log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
249
+ return;
250
+ }
251
+ const created = await ensureProceduralMiningCron(jobsPath, {
252
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
253
+ });
254
+ if (created.created) {
255
+ log.info(`procedural mining cron auto-registered (${created.jobId})`);
256
+ } else {
257
+ log.debug("procedural mining cron already exists, skipping auto-register");
258
+ }
259
+ } catch (err) {
260
+ log.debug(`procedural mining cron auto-register error: ${err}`);
261
+ }
262
+ }
263
+
264
+ async autoRegisterContradictionScanCron(): Promise<void> {
265
+ const home = resolveHomeDir();
266
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
267
+ try {
268
+ if (!existsSync(jobsPath)) {
269
+ log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
270
+ return;
271
+ }
272
+ const created = await ensureContradictionScanCron(jobsPath, {
273
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
274
+ });
275
+ if (created.created) {
276
+ log.info(`contradiction scan cron auto-registered (${created.jobId})`);
277
+ } else {
278
+ log.debug("contradiction scan cron already exists, skipping auto-register");
279
+ }
280
+ } catch (err) {
281
+ log.debug(`contradiction scan cron auto-register error: ${err}`);
282
+ }
283
+ }
284
+
285
+ async autoRegisterPatternReinforcementCron(): Promise<void> {
286
+ const home = resolveHomeDir();
287
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
288
+ try {
289
+ if (!existsSync(jobsPath)) {
290
+ log.debug("pattern reinforcement cron: jobs.json not found, skipping auto-register");
291
+ return;
292
+ }
293
+ const created = await ensurePatternReinforcementCron(jobsPath, {
294
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
295
+ });
296
+ if (created.created) {
297
+ log.info(`pattern reinforcement cron auto-registered (${created.jobId})`);
298
+ } else {
299
+ log.debug("pattern reinforcement cron already exists, skipping auto-register");
300
+ }
301
+ } catch (err) {
302
+ log.debug(`pattern reinforcement cron auto-register error: ${err}`);
303
+ }
304
+ }
305
+
306
+ async autoRegisterGraphEdgeDecayCron(): Promise<void> {
307
+ const home = resolveHomeDir();
308
+ const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
309
+ try {
310
+ if (!existsSync(jobsPath)) {
311
+ log.debug("graph edge decay cron: jobs.json not found, skipping auto-register");
312
+ return;
313
+ }
314
+ const scheduleExpr = graphEdgeDecayCadenceToCronExpr(
315
+ this.deps.config.graphEdgeDecayCadenceMs,
316
+ );
317
+ const created = await ensureGraphEdgeDecayCron(jobsPath, {
318
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
319
+ scheduleExpr,
320
+ });
321
+ if (created.created) {
322
+ log.info(`graph edge decay cron auto-registered (${created.jobId}, ${scheduleExpr})`);
323
+ } else {
324
+ log.debug("graph edge decay cron already exists, skipping auto-register");
325
+ }
326
+ } catch (err) {
327
+ log.debug(`graph edge decay cron auto-register error: ${err}`);
328
+ }
329
+ }
330
+
331
+ // ───────────────────────────────────────────────────────────────────────
332
+ // Consolidation scheduling
333
+ // ───────────────────────────────────────────────────────────────────────
334
+
335
+ /**
336
+ * Maybe trigger a background consolidation pass after an extraction.
337
+ * Counted + interval gated so a burst of extractions does not fan into a
338
+ * burst of consolidation passes. The consolidation runner itself is
339
+ * passed in as a callback because it lives on the orchestrator (LLM +
340
+ * storage dependency surface) and is not owned by this module.
341
+ */
342
+ maybeScheduleConsolidation(
343
+ nonZeroExtraction: boolean,
344
+ runConsolidation: () => Promise<unknown>,
345
+ ): void {
346
+ if (nonZeroExtraction) this.nonZeroExtractionsSinceConsolidation += 1;
347
+ if (
348
+ this.deps.config.consolidationRequireNonZeroExtraction &&
349
+ !nonZeroExtraction
350
+ ) {
351
+ return;
352
+ }
353
+ if (
354
+ this.nonZeroExtractionsSinceConsolidation <
355
+ this.deps.config.consolidateEveryN
356
+ ) {
357
+ return;
358
+ }
359
+
360
+ const now = Date.now();
361
+ if (
362
+ now - this.lastConsolidationRunAtMs <
363
+ this.deps.config.consolidationMinIntervalMs
364
+ ) {
365
+ return;
366
+ }
367
+ if (this.consolidationInFlight) return;
368
+
369
+ this.consolidationInFlight = true;
370
+ this.lastConsolidationRunAtMs = now;
371
+ this.nonZeroExtractionsSinceConsolidation = 0;
372
+ runConsolidation()
373
+ .catch((err) => log.error("background consolidation failed", err))
374
+ .finally(() => {
375
+ this.consolidationInFlight = false;
376
+ });
377
+ }
378
+
379
+ /** Whether a background consolidation pass is currently running. Exposed so
380
+ * the orchestrator's waitForConsolidationIdle can poll idle state without
381
+ * owning the cadence state (issue #1526 PR1). */
382
+ isConsolidationInFlight(): boolean {
383
+ return this.consolidationInFlight;
384
+ }
385
+
386
+ /** Whether a debounced QMD maintenance pass is armed and waiting for the
387
+ * debounce timer. Exposed so drain/teardown diagnostics (e.g. the bench
388
+ * adapter) can observe scheduler idle state after the #1526 PR1 extraction
389
+ * moved this flag off the orchestrator. */
390
+ isQmdMaintenancePending(): boolean {
391
+ return this.qmdMaintenancePending;
392
+ }
393
+
394
+ /** Whether a QMD maintenance pass is currently running (singleflight guard).
395
+ * Exposed for drain/teardown diagnostics (see isQmdMaintenancePending). */
396
+ isQmdMaintenanceInFlight(): boolean {
397
+ return this.qmdMaintenanceInFlight;
398
+ }
399
+ // ───────────────────────────────────────────────────────────────────────
400
+ // QMD maintenance scheduling (debounced + singleflight)
401
+ // ───────────────────────────────────────────────────────────────────────
402
+
403
+ /** Internal: queue a debounced QMD maintenance pass. */
404
+ private requestQmdMaintenance(): void {
405
+ if (!this.deps.getQmd().isAvailable()) return;
406
+ if (!this.deps.config.qmdMaintenanceEnabled) return;
407
+
408
+ this.qmdMaintenancePending = true;
409
+ if (this.qmdMaintenanceTimer) return;
410
+
411
+ this.qmdMaintenanceTimer = setTimeout(() => {
412
+ this.qmdMaintenanceTimer = null;
413
+ this.runQmdMaintenance().catch((err) =>
414
+ log.debug(`background qmd maintenance failed: ${err}`),
415
+ );
416
+ }, this.deps.config.qmdMaintenanceDebounceMs);
417
+ }
418
+
419
+ /**
420
+ * Public entrypoint for tool-driven QMD maintenance requests.
421
+ * Routes through existing debounced/singleflight maintenance controls.
422
+ */
423
+ requestQmdMaintenanceForTool(reason: string): void {
424
+ try {
425
+ this.requestQmdMaintenance();
426
+ } catch (err) {
427
+ const msg = err instanceof Error ? err.message : String(err);
428
+ log.warn(`qmd maintenance request failed (${reason}): ${msg}`);
429
+ }
430
+ }
431
+
432
+ /** Internal: run a single QMD maintenance pass under singleflight guard. */
433
+ async runQmdMaintenance(): Promise<void> {
434
+ if (this.qmdMaintenanceInFlight) return;
435
+ if (!this.qmdMaintenancePending) return;
436
+ this.qmdMaintenanceInFlight = true;
437
+ this.qmdMaintenancePending = false;
438
+
439
+ try {
440
+ if (this.deps.config.namespacesEnabled) {
441
+ // Include cataloged dynamic namespaces, not just the configured set
442
+ // (NGnei), but run through the namespace-aware maintenance planner so
443
+ // each namespace is budgeted, lock-protected, and status-recorded
444
+ // independently (issue #1500).
445
+ const plan = await this.namespaceMaintenancePlan("qmd");
446
+ const now = Date.now();
447
+ const lastEmbedAtByNamespace = this.lastQmdEmbedAtMsByNamespace;
448
+ const dueEmbedNamespaces = (namespaces: string[]): string[] => {
449
+ if (!this.deps.config.qmdAutoEmbedEnabled) return [];
450
+ return namespaces.filter(
451
+ (namespace) =>
452
+ now - (lastEmbedAtByNamespace.get(namespace) ?? 0) >=
453
+ this.deps.config.qmdEmbedMinIntervalMs,
454
+ );
455
+ };
456
+ const markEmbedded = (namespaces: string[]): void => {
457
+ if (namespaces.length === 0) return;
458
+ for (const namespace of namespaces) {
459
+ lastEmbedAtByNamespace.set(namespace, now);
460
+ }
461
+ this.lastQmdEmbedAtMs = now;
462
+ };
463
+ await runNamespaceMaintenanceBatchPlan(
464
+ this.deps.config,
465
+ plan,
466
+ async (candidates) => {
467
+ const namespaces = candidates.map((candidate) => candidate.namespace);
468
+ const embedNamespaces = dueEmbedNamespaces(namespaces);
469
+ let result: NamespaceUpdateResult;
470
+ try {
471
+ result = await this.deps.namespaceSearchRouter.updateNamespacesDetailed(
472
+ namespaces,
473
+ undefined,
474
+ { strict: true },
475
+ );
476
+ } catch (error) {
477
+ if (
478
+ embedNamespaces.length > 0 &&
479
+ qmdMaintenanceSkipReasonForError(error) === "throttled"
480
+ ) {
481
+ await this.deps.namespaceSearchRouter.embedNamespaces(embedNamespaces, {
482
+ strict: true,
483
+ });
484
+ markEmbedded(embedNamespaces);
485
+ }
486
+ throw error;
487
+ }
488
+ if (result.backendCount <= 0) {
489
+ throw new Error("no eligible QMD backend for selected namespaces");
490
+ }
491
+ if (result.eligibleNamespaces.length !== namespaces.length) {
492
+ const eligible = new Set(result.eligibleNamespaces);
493
+ const missing = namespaces.filter(
494
+ (namespace) => !eligible.has(namespace),
495
+ );
496
+ throw new Error(
497
+ `QMD backend ineligible for selected namespaces (${missing.length})`,
498
+ );
499
+ }
500
+ if (embedNamespaces.length > 0) {
501
+ await this.deps.namespaceSearchRouter.embedNamespaces(embedNamespaces, {
502
+ strict: true,
503
+ });
504
+ markEmbedded(embedNamespaces);
505
+ }
506
+ return { itemCount: result.backendCount };
507
+ },
508
+ this.deps.namespaceCatalog,
509
+ {
510
+ skipReasonForError: qmdMaintenanceSkipReasonForError,
511
+ },
512
+ );
513
+ } else {
514
+ await this.deps.getQmd().update();
515
+ const now = Date.now();
516
+ if (
517
+ this.deps.config.qmdAutoEmbedEnabled &&
518
+ now - this.lastQmdEmbedAtMs >= this.deps.config.qmdEmbedMinIntervalMs
519
+ ) {
520
+ await this.deps.getQmd().embed();
521
+ this.lastQmdEmbedAtMs = now;
522
+ }
523
+ }
524
+ } finally {
525
+ this.qmdMaintenanceInFlight = false;
526
+ if (this.qmdMaintenancePending) {
527
+ this.requestQmdMaintenance();
528
+ }
529
+ }
530
+ }
531
+
532
+ /** Build the namespace-aware maintenance plan for one job. */
533
+ private async namespaceMaintenancePlan(
534
+ jobName: string,
535
+ ): Promise<NamespaceMaintenancePlan> {
536
+ return planNamespaceMaintenance(this.deps.config, {
537
+ jobName,
538
+ catalog: this.deps.namespaceCatalog,
539
+ });
540
+ }
541
+
542
+ // ───────────────────────────────────────────────────────────────────────
543
+ // Lifecycle
544
+ // ───────────────────────────────────────────────────────────────────────
545
+
546
+ /**
547
+ * Clear any pending debounce timer. Called from the orchestrator's
548
+ * destroy() so a late tick does not fire on a torn-down instance.
549
+ */
550
+ dispose(): void {
551
+ if (this.qmdMaintenanceTimer) {
552
+ clearTimeout(this.qmdMaintenanceTimer);
553
+ this.qmdMaintenanceTimer = null;
554
+ }
555
+ this.qmdMaintenancePending = false;
556
+ }
557
+ }