@remnic/core 9.7.8 → 9.7.10

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 (406) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +32 -30
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-boundary.d.ts +6 -6
  5. package/dist/access-boundary.js +33 -31
  6. package/dist/access-cli.js +92 -90
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-http.d.ts +6 -6
  9. package/dist/access-http.js +40 -38
  10. package/dist/access-identity-continuity-surface.d.ts +5 -5
  11. package/dist/access-identity-continuity-surface.js +32 -30
  12. package/dist/access-lcm-surface.d.ts +6 -6
  13. package/dist/access-lcm-surface.js +32 -30
  14. package/dist/access-mcp.d.ts +6 -6
  15. package/dist/access-mcp.js +37 -35
  16. package/dist/access-observe-write-surface.d.ts +6 -6
  17. package/dist/access-observe-write-surface.js +32 -30
  18. package/dist/access-operations-batch.js +35 -33
  19. package/dist/access-operations.d.ts +9 -9
  20. package/dist/access-operations.js +36 -34
  21. package/dist/access-recall-surface.d.ts +6 -6
  22. package/dist/access-recall-surface.js +32 -30
  23. package/dist/access-schema.d.ts +64 -64
  24. package/dist/{access-service-CxDZXTKt.d.ts → access-service-DSuzikHQ.d.ts} +4 -4
  25. package/dist/access-service.d.ts +6 -6
  26. package/dist/access-service.js +32 -30
  27. package/dist/access-surface-catalog.d.ts +6 -6
  28. package/dist/action-confidence.d.ts +1 -1
  29. package/dist/active-memory-bridge.d.ts +1 -1
  30. package/dist/active-recall.d.ts +1 -1
  31. package/dist/active-recall.js +1 -1
  32. package/dist/adapters/index.js +4 -4
  33. package/dist/adapters/registry.js +2 -2
  34. package/dist/{auto-sync-676N6N6Y.js → auto-sync-AAMTVZMK.js} +5 -5
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +5 -5
  38. package/dist/briefing.d.ts +2 -2
  39. package/dist/briefing.js +15 -13
  40. package/dist/buffer-surprise-report.d.ts +1 -1
  41. package/dist/buffer.d.ts +2 -2
  42. package/dist/bulk-import/index.d.ts +3 -3
  43. package/dist/calibration.d.ts +1 -1
  44. package/dist/calibration.js +2 -2
  45. package/dist/capabilities.d.ts +1 -1
  46. package/dist/{capsule-merge-GK5E647P.js → capsule-merge-Z2HUO6IK.js} +10 -2
  47. package/dist/capsule-merge-Z2HUO6IK.js.map +1 -0
  48. package/dist/{catalog-DzU9HowE.d.ts → catalog-BNwjACa5.d.ts} +1 -1
  49. package/dist/causal-behavior.d.ts +1 -1
  50. package/dist/causal-consolidation.d.ts +1 -1
  51. package/dist/causal-consolidation.js +16 -14
  52. package/dist/causal-consolidation.js.map +1 -1
  53. package/dist/causal-trajectory-graph.d.ts +1 -1
  54. package/dist/{chunk-HHAA35A3.js → chunk-2VN4JAX7.js} +548 -161
  55. package/dist/chunk-2VN4JAX7.js.map +1 -0
  56. package/dist/{chunk-CNWC274T.js → chunk-3OQ4IZJN.js} +28 -28
  57. package/dist/{chunk-JO5AT6QT.js → chunk-3ZVFLMFQ.js} +1 -1
  58. package/dist/chunk-3ZVFLMFQ.js.map +1 -0
  59. package/dist/{chunk-7XH7VJN4.js → chunk-5S7Z5SHA.js} +2 -2
  60. package/dist/{chunk-WKE4VXSZ.js → chunk-673NPXE3.js} +2 -2
  61. package/dist/{chunk-Q6JPMCPO.js → chunk-7CKS4AXY.js} +63 -58
  62. package/dist/chunk-7CKS4AXY.js.map +1 -0
  63. package/dist/{chunk-UHGHTOR5.js → chunk-7EX4YOKH.js} +7 -7
  64. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  65. package/dist/{chunk-EJCVMPIM.js → chunk-7LMEY5KZ.js} +15 -1
  66. package/dist/chunk-7LMEY5KZ.js.map +1 -0
  67. package/dist/{chunk-3F4N4WDH.js → chunk-AHLPZ2SF.js} +2 -2
  68. package/dist/{chunk-6C35R7P4.js → chunk-DJCKXI4M.js} +2 -2
  69. package/dist/{chunk-AD2KK5MT.js → chunk-DLWYAN6A.js} +9 -3
  70. package/dist/chunk-DLWYAN6A.js.map +1 -0
  71. package/dist/{chunk-TVRPRFUB.js → chunk-EQF5763Z.js} +9 -7
  72. package/dist/chunk-EQF5763Z.js.map +1 -0
  73. package/dist/{chunk-FYKIEOG6.js → chunk-FNJX2O6G.js} +3 -3
  74. package/dist/{chunk-DQPPRXFO.js → chunk-HQ7QZJM5.js} +2 -2
  75. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  76. package/dist/{chunk-KIPLG7S7.js → chunk-JKLJ6A7W.js} +2 -2
  77. package/dist/{chunk-O3BZ6PFY.js → chunk-JZM5QUTE.js} +2 -2
  78. package/dist/{chunk-KTOH7EJE.js → chunk-KXLRMM35.js} +312 -48
  79. package/dist/chunk-KXLRMM35.js.map +1 -0
  80. package/dist/{chunk-ZUFJ7ZTR.js → chunk-L4H6MQ7I.js} +6 -2
  81. package/dist/chunk-L4H6MQ7I.js.map +1 -0
  82. package/dist/{chunk-SYNYI4ZM.js → chunk-L5HJQC36.js} +2 -2
  83. package/dist/{chunk-VXMWDXIJ.js → chunk-LC7OODWZ.js} +2 -2
  84. package/dist/{chunk-ZW6OKHVX.js → chunk-LQKITFGB.js} +2 -2
  85. package/dist/{chunk-TZJVTH3F.js → chunk-MXRMNKSY.js} +14 -14
  86. package/dist/{chunk-GL3SMLMZ.js → chunk-N3LWQJ6Y.js} +150 -119
  87. package/dist/chunk-N3LWQJ6Y.js.map +1 -0
  88. package/dist/{chunk-M5QKGHCR.js → chunk-OC35KQIN.js} +6 -2
  89. package/dist/chunk-OC35KQIN.js.map +1 -0
  90. package/dist/chunk-OKUEZOGW.js +27 -0
  91. package/dist/chunk-OKUEZOGW.js.map +1 -0
  92. package/dist/{chunk-PKGWJFNR.js → chunk-OVMS3DCB.js} +2 -2
  93. package/dist/chunk-PDJTHRAW.js +36 -0
  94. package/dist/chunk-PDJTHRAW.js.map +1 -0
  95. package/dist/{chunk-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
  96. package/dist/{chunk-NMMKRVUF.js → chunk-QVALQE4P.js} +6 -6
  97. package/dist/{chunk-FXGC5UUH.js → chunk-RT7IP3D3.js} +2 -2
  98. package/dist/{chunk-AH3M6F4X.js → chunk-SBFDL2EQ.js} +2 -2
  99. package/dist/{chunk-UYZUJ4EB.js → chunk-SECMZACR.js} +2 -2
  100. package/dist/{chunk-HXDKC3BI.js → chunk-TGBJ2OMK.js} +2 -2
  101. package/dist/{chunk-DE6ENYLG.js → chunk-TXLGHDHS.js} +2 -2
  102. package/dist/{chunk-74MJH7I3.js → chunk-U4KHQTJ6.js} +12 -3
  103. package/dist/chunk-U4KHQTJ6.js.map +1 -0
  104. package/dist/{chunk-CRFTLUWT.js → chunk-UTDA43TB.js} +2 -2
  105. package/dist/{chunk-GKCCHHZK.js → chunk-UXKMMNJD.js} +4 -4
  106. package/dist/{chunk-57INMZ6F.js → chunk-UZRIKUFS.js} +2 -2
  107. package/dist/{chunk-2FSHROYK.js → chunk-V53GJUSL.js} +8 -8
  108. package/dist/{chunk-LKT755LP.js → chunk-VA4WSW54.js} +2 -2
  109. package/dist/{chunk-ZHKD4EZM.js → chunk-VCEH5UYS.js} +3 -3
  110. package/dist/{chunk-CHVU4RE5.js → chunk-VDLMAJXS.js} +3 -3
  111. package/dist/{chunk-XFTPUXCE.js → chunk-VRAD2FGO.js} +10 -10
  112. package/dist/{chunk-VF4XKTX3.js → chunk-VZ5OJE4X.js} +10 -2
  113. package/dist/chunk-VZ5OJE4X.js.map +1 -0
  114. package/dist/{chunk-UVIQHSS7.js → chunk-WUDBMLC6.js} +9 -3
  115. package/dist/chunk-WUDBMLC6.js.map +1 -0
  116. package/dist/{chunk-AWJ2FHCF.js → chunk-X47LWMKA.js} +49 -21
  117. package/dist/chunk-X47LWMKA.js.map +1 -0
  118. package/dist/{chunk-VMGMY2AI.js → chunk-XF4KOVDK.js} +8 -8
  119. package/dist/{chunk-VMGMY2AI.js.map → chunk-XF4KOVDK.js.map} +1 -1
  120. package/dist/{chunk-T7VNN3ZM.js → chunk-XVLEGMJI.js} +2 -2
  121. package/dist/{chunk-R2SJDIPO.js → chunk-YLTG6BO4.js} +9 -7
  122. package/dist/chunk-YLTG6BO4.js.map +1 -0
  123. package/dist/{chunk-QDCJNRKK.js → chunk-YXOAQAH2.js} +3 -3
  124. package/dist/{cli-BqyGVyOe.d.ts → cli-DACyuK8-.d.ts} +5 -5
  125. package/dist/cli.d.ts +8 -8
  126. package/dist/cli.js +66 -64
  127. package/dist/compounding/engine.d.ts +2 -2
  128. package/dist/compounding/engine.js +15 -13
  129. package/dist/compounding/preference-consolidator.d.ts +1 -1
  130. package/dist/compression-optimizer.d.ts +1 -1
  131. package/dist/config.d.ts +1 -1
  132. package/dist/config.js +1 -1
  133. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  134. package/dist/connectors/codex-materialize-runner.js +15 -13
  135. package/dist/connectors/codex-materialize.d.ts +1 -1
  136. package/dist/connectors/index.d.ts +1 -1
  137. package/dist/connectors/index.js +15 -13
  138. package/dist/consolidation-provenance-check.d.ts +2 -2
  139. package/dist/consolidation-provenance-check.js +6 -2
  140. package/dist/consolidation-undo.d.ts +2 -2
  141. package/dist/consolidation-undo.js +8 -1
  142. package/dist/consolidation-undo.js.map +1 -1
  143. package/dist/contradiction/index.d.ts +2 -2
  144. package/dist/conversation-index/backend.d.ts +1 -1
  145. package/dist/conversation-index/chunker.d.ts +1 -1
  146. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  147. package/dist/conversation-index/indexer.d.ts +1 -1
  148. package/dist/conversation-index/search.d.ts +1 -1
  149. package/dist/day-summary.d.ts +1 -1
  150. package/dist/delinearize.d.ts +1 -1
  151. package/dist/direct-answer-wiring.d.ts +1 -1
  152. package/dist/direct-answer.d.ts +1 -1
  153. package/dist/embedding-fallback.d.ts +1 -1
  154. package/dist/enrichment/index.d.ts +1 -1
  155. package/dist/entity-retrieval.d.ts +2 -2
  156. package/dist/entity-retrieval.js +15 -13
  157. package/dist/entity-schema.d.ts +1 -1
  158. package/dist/explicit-capture.d.ts +5 -5
  159. package/dist/extraction-faithfulness.d.ts +1 -1
  160. package/dist/extraction-faithfulness.js +3 -3
  161. package/dist/extraction-judge-telemetry.d.ts +1 -1
  162. package/dist/extraction-judge-training.d.ts +1 -1
  163. package/dist/extraction-judge.d.ts +1 -1
  164. package/dist/extraction-judge.js +3 -3
  165. package/dist/extraction.d.ts +1 -1
  166. package/dist/extraction.js +3 -3
  167. package/dist/fallback-llm.d.ts +1 -1
  168. package/dist/fallback-llm.js +2 -2
  169. package/dist/{forget-KHF7A44H.js → forget-CQCLB5LB.js} +2 -2
  170. package/dist/graph-dashboard-diff.d.ts +1 -1
  171. package/dist/graph-dashboard-key.d.ts +1 -1
  172. package/dist/graph-dashboard-parser.d.ts +1 -1
  173. package/dist/graph-edge-reinforcement.d.ts +1 -1
  174. package/dist/graph-snapshot.d.ts +1 -1
  175. package/dist/graph.d.ts +1 -1
  176. package/dist/identity-continuity.d.ts +1 -1
  177. package/dist/importance.d.ts +1 -1
  178. package/dist/importers/index.d.ts +1 -1
  179. package/dist/in-flight-reads.d.ts +19 -0
  180. package/dist/in-flight-reads.js +16 -0
  181. package/dist/in-flight-reads.js.map +1 -0
  182. package/dist/index.d.ts +13 -13
  183. package/dist/index.js +116 -114
  184. package/dist/intent.d.ts +1 -1
  185. package/dist/lcm/engine.d.ts +1 -1
  186. package/dist/lcm/engine.js +4 -4
  187. package/dist/lcm/index.d.ts +1 -1
  188. package/dist/lcm/index.js +13 -13
  189. package/dist/lcm/tools.d.ts +1 -1
  190. package/dist/lifecycle.d.ts +1 -1
  191. package/dist/live-connectors-runner.d.ts +1 -1
  192. package/dist/local-llm.d.ts +1 -1
  193. package/dist/local-model-endpoint.d.ts +1 -1
  194. package/dist/maintenance/memory-governance.d.ts +1 -1
  195. package/dist/maintenance/memory-governance.js +15 -13
  196. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -13
  197. package/dist/maintenance/rebuild-memory-projection.js +16 -14
  198. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  199. package/dist/memory-action-policy.d.ts +1 -1
  200. package/dist/memory-cache.d.ts +24 -13
  201. package/dist/memory-cache.js +3 -1
  202. package/dist/memory-corpus-version.d.ts +19 -0
  203. package/dist/memory-corpus-version.js +13 -0
  204. package/dist/memory-corpus-version.js.map +1 -0
  205. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  206. package/dist/memory-projection-store.d.ts +1 -1
  207. package/dist/memory-provenance.d.ts +1 -1
  208. package/dist/memory-worth-outcomes.d.ts +2 -2
  209. package/dist/models-json.d.ts +1 -1
  210. package/dist/namespaces/migrate.d.ts +3 -3
  211. package/dist/namespaces/migrate.js +22 -20
  212. package/dist/namespaces/principal.d.ts +1 -1
  213. package/dist/namespaces/search.d.ts +1 -1
  214. package/dist/namespaces/search.js +7 -7
  215. package/dist/namespaces/storage.d.ts +3 -3
  216. package/dist/namespaces/storage.js +15 -13
  217. package/dist/native-knowledge.d.ts +1 -1
  218. package/dist/operator-toolkit.d.ts +2 -2
  219. package/dist/operator-toolkit.js +27 -25
  220. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  221. package/dist/orchestration/maintenance.d.ts +2 -2
  222. package/dist/orchestration/maintenance.js +17 -15
  223. package/dist/{orchestrator-37Cx7yyw.d.ts → orchestrator-D83AvrPf.d.ts} +4 -4
  224. package/dist/orchestrator.d.ts +5 -5
  225. package/dist/orchestrator.js +92 -90
  226. package/dist/page-versioning.js +5 -1
  227. package/dist/patterns-cli.d.ts +1 -1
  228. package/dist/{pipeline-C6onljHc.d.ts → pipeline-BSqGpYl1.d.ts} +1 -1
  229. package/dist/policy-runtime.d.ts +1 -1
  230. package/dist/provenance.d.ts +1 -1
  231. package/dist/{qmd-J08G7MEm.d.ts → qmd-CutnpRVe.d.ts} +1 -1
  232. package/dist/qmd-preflight.d.ts +2 -2
  233. package/dist/qmd-recall-cache.d.ts +1 -1
  234. package/dist/qmd.d.ts +2 -2
  235. package/dist/qmd.js +2 -2
  236. package/dist/recall-disclosure-escalation.d.ts +1 -1
  237. package/dist/recall-explain-renderer.d.ts +1 -1
  238. package/dist/recall-explain-renderer.js +3 -3
  239. package/dist/recall-planner-llm.d.ts +1 -1
  240. package/dist/recall-planner-llm.js +2 -2
  241. package/dist/recall-state.d.ts +1 -1
  242. package/dist/recall-tag-filter.d.ts +1 -1
  243. package/dist/recall-timings.d.ts +1 -1
  244. package/dist/recall-xray-cli.d.ts +1 -1
  245. package/dist/recall-xray-cli.js +4 -4
  246. package/dist/recall-xray-renderer.d.ts +1 -1
  247. package/dist/recall-xray-renderer.js +3 -3
  248. package/dist/recall-xray.d.ts +1 -1
  249. package/dist/recall-xray.js +2 -2
  250. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  251. package/dist/replay/normalizers/claude.d.ts +1 -1
  252. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  253. package/dist/replay/normalizers/shared.d.ts +1 -1
  254. package/dist/replay/runner.d.ts +1 -1
  255. package/dist/replay/types.d.ts +1 -1
  256. package/dist/resolve-auth-token.d.ts +1 -1
  257. package/dist/resume-bundles.js +2 -2
  258. package/dist/retrieval-agents.d.ts +2 -2
  259. package/dist/retrieval-agents.js +2 -2
  260. package/dist/retrieval-tiers.d.ts +1 -1
  261. package/dist/routing/engine.d.ts +1 -1
  262. package/dist/routing/store.d.ts +1 -1
  263. package/dist/schemas.d.ts +52 -52
  264. package/dist/search/embed-helper.d.ts +1 -1
  265. package/dist/search/factory.d.ts +1 -1
  266. package/dist/search/factory.js +6 -6
  267. package/dist/search/index.d.ts +1 -1
  268. package/dist/search/index.js +12 -12
  269. package/dist/search/lancedb-backend.d.ts +1 -1
  270. package/dist/search/meilisearch-backend.d.ts +1 -1
  271. package/dist/search/noop-backend.d.ts +1 -1
  272. package/dist/search/orama-backend.d.ts +1 -1
  273. package/dist/search/port.d.ts +1 -1
  274. package/dist/search/remote-backend.d.ts +1 -1
  275. package/dist/{semantic-consolidation-BFtoVosl.d.ts → semantic-consolidation-btIVRC4l.d.ts} +1 -1
  276. package/dist/semantic-consolidation.d.ts +2 -2
  277. package/dist/semantic-consolidation.js +16 -14
  278. package/dist/semantic-rule-promotion.js +15 -13
  279. package/dist/semantic-rule-verifier.d.ts +5 -1
  280. package/dist/semantic-rule-verifier.js +15 -13
  281. package/dist/session-observer-bands.d.ts +1 -1
  282. package/dist/session-observer-state.d.ts +1 -1
  283. package/dist/shared-context/manager.d.ts +9 -9
  284. package/dist/signal.d.ts +1 -1
  285. package/dist/{storage-CXjwBr69.d.ts → storage-BVv_Mqa0.d.ts} +133 -3
  286. package/dist/storage.d.ts +2 -2
  287. package/dist/storage.js +14 -12
  288. package/dist/summarizer.d.ts +1 -1
  289. package/dist/summarizer.js +3 -3
  290. package/dist/summary-snapshot.d.ts +1 -1
  291. package/dist/temporal-index-compat.d.ts +1 -0
  292. package/dist/temporal-index-compat.js +16 -0
  293. package/dist/temporal-index-compat.js.map +1 -0
  294. package/dist/temporal-index.d.ts +58 -7
  295. package/dist/temporal-index.js +23 -3
  296. package/dist/temporal-supersession.d.ts +2 -2
  297. package/dist/temporal-timeline-recall.d.ts +1 -1
  298. package/dist/temporal-validity.d.ts +1 -1
  299. package/dist/threading.d.ts +1 -1
  300. package/dist/tier-migration.d.ts +2 -2
  301. package/dist/tier-routing.d.ts +1 -1
  302. package/dist/topics.d.ts +1 -1
  303. package/dist/transcript.d.ts +1 -1
  304. package/dist/transfer/capsule-import.js +6 -2
  305. package/dist/transfer/import-sqlite.js +2 -2
  306. package/dist/transfer/types.d.ts +66 -66
  307. package/dist/trust-score-stage.d.ts +1 -1
  308. package/dist/trust-score.d.ts +1 -1
  309. package/dist/{types-DpxqC8dI.d.ts → types-Cmkz7csa.d.ts} +27 -0
  310. package/dist/types.d.ts +1 -1
  311. package/dist/types.js +1 -1
  312. package/dist/utility-runtime.d.ts +1 -1
  313. package/dist/verified-recall.d.ts +4 -0
  314. package/dist/verified-recall.js +15 -13
  315. package/package.json +12 -2
  316. package/src/binary-lifecycle/pipeline.ts +22 -0
  317. package/src/cli.ts +8 -0
  318. package/src/compounding/engine.ts +9 -1
  319. package/src/config.ts +14 -0
  320. package/src/consolidation-undo.ts +8 -0
  321. package/src/curation/index.ts +9 -0
  322. package/src/in-flight-reads.ts +63 -0
  323. package/src/maintenance/memory-governance.ts +6 -0
  324. package/src/memory-cache-hot.test.ts +131 -0
  325. package/src/memory-cache.ts +146 -22
  326. package/src/memory-corpus-version.ts +45 -0
  327. package/src/namespaces/storage.ts +20 -1
  328. package/src/operator-toolkit.ts +7 -1
  329. package/src/orchestration/consolidation-run.ts +5 -5
  330. package/src/orchestration/contradiction-linking-coordinator.ts +2 -2
  331. package/src/orchestration/lifecycle-policy-coordinator.ts +2 -2
  332. package/src/orchestration/persistence-index.ts +6 -6
  333. package/src/orchestration/recall-internal.ts +6 -3
  334. package/src/orchestration/recall-search-pipeline.ts +36 -21
  335. package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
  336. package/src/orchestrator.ts +25 -5
  337. package/src/page-versioning.ts +14 -1
  338. package/src/provenance-frontmatter.test.ts +6 -0
  339. package/src/review/index.ts +16 -8
  340. package/src/semantic-rule-promotion.ts +9 -1
  341. package/src/semantic-rule-verifier.ts +19 -6
  342. package/src/spaces/index.ts +10 -0
  343. package/src/storage/memory-read-store.ts +9 -2
  344. package/src/storage-contract/cache-coherence.test.ts +242 -0
  345. package/src/storage-contract/namespace-routing.test.ts +26 -1
  346. package/src/storage.ts +513 -31
  347. package/src/temporal-index-compat.ts +311 -0
  348. package/src/temporal-index.test.ts +273 -22
  349. package/src/temporal-index.ts +427 -178
  350. package/src/transfer/capsule-cache-coherence.test.ts +107 -0
  351. package/src/transfer/capsule-import.ts +8 -0
  352. package/src/transfer/capsule-merge.ts +7 -0
  353. package/src/types.ts +27 -0
  354. package/src/verified-recall.ts +20 -6
  355. package/dist/capsule-merge-GK5E647P.js.map +0 -1
  356. package/dist/chunk-74MJH7I3.js.map +0 -1
  357. package/dist/chunk-AD2KK5MT.js.map +0 -1
  358. package/dist/chunk-AWJ2FHCF.js.map +0 -1
  359. package/dist/chunk-EJCVMPIM.js.map +0 -1
  360. package/dist/chunk-GL3SMLMZ.js.map +0 -1
  361. package/dist/chunk-HHAA35A3.js.map +0 -1
  362. package/dist/chunk-JO5AT6QT.js.map +0 -1
  363. package/dist/chunk-KTOH7EJE.js.map +0 -1
  364. package/dist/chunk-M5QKGHCR.js.map +0 -1
  365. package/dist/chunk-Q6JPMCPO.js.map +0 -1
  366. package/dist/chunk-R2SJDIPO.js.map +0 -1
  367. package/dist/chunk-TVRPRFUB.js.map +0 -1
  368. package/dist/chunk-UVIQHSS7.js.map +0 -1
  369. package/dist/chunk-VF4XKTX3.js.map +0 -1
  370. package/dist/chunk-ZUFJ7ZTR.js.map +0 -1
  371. /package/dist/{auto-sync-676N6N6Y.js.map → auto-sync-AAMTVZMK.js.map} +0 -0
  372. /package/dist/{chunk-CNWC274T.js.map → chunk-3OQ4IZJN.js.map} +0 -0
  373. /package/dist/{chunk-7XH7VJN4.js.map → chunk-5S7Z5SHA.js.map} +0 -0
  374. /package/dist/{chunk-WKE4VXSZ.js.map → chunk-673NPXE3.js.map} +0 -0
  375. /package/dist/{chunk-UHGHTOR5.js.map → chunk-7EX4YOKH.js.map} +0 -0
  376. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  377. /package/dist/{chunk-3F4N4WDH.js.map → chunk-AHLPZ2SF.js.map} +0 -0
  378. /package/dist/{chunk-6C35R7P4.js.map → chunk-DJCKXI4M.js.map} +0 -0
  379. /package/dist/{chunk-FYKIEOG6.js.map → chunk-FNJX2O6G.js.map} +0 -0
  380. /package/dist/{chunk-DQPPRXFO.js.map → chunk-HQ7QZJM5.js.map} +0 -0
  381. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  382. /package/dist/{chunk-KIPLG7S7.js.map → chunk-JKLJ6A7W.js.map} +0 -0
  383. /package/dist/{chunk-O3BZ6PFY.js.map → chunk-JZM5QUTE.js.map} +0 -0
  384. /package/dist/{chunk-SYNYI4ZM.js.map → chunk-L5HJQC36.js.map} +0 -0
  385. /package/dist/{chunk-VXMWDXIJ.js.map → chunk-LC7OODWZ.js.map} +0 -0
  386. /package/dist/{chunk-ZW6OKHVX.js.map → chunk-LQKITFGB.js.map} +0 -0
  387. /package/dist/{chunk-TZJVTH3F.js.map → chunk-MXRMNKSY.js.map} +0 -0
  388. /package/dist/{chunk-PKGWJFNR.js.map → chunk-OVMS3DCB.js.map} +0 -0
  389. /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
  390. /package/dist/{chunk-NMMKRVUF.js.map → chunk-QVALQE4P.js.map} +0 -0
  391. /package/dist/{chunk-FXGC5UUH.js.map → chunk-RT7IP3D3.js.map} +0 -0
  392. /package/dist/{chunk-AH3M6F4X.js.map → chunk-SBFDL2EQ.js.map} +0 -0
  393. /package/dist/{chunk-UYZUJ4EB.js.map → chunk-SECMZACR.js.map} +0 -0
  394. /package/dist/{chunk-HXDKC3BI.js.map → chunk-TGBJ2OMK.js.map} +0 -0
  395. /package/dist/{chunk-DE6ENYLG.js.map → chunk-TXLGHDHS.js.map} +0 -0
  396. /package/dist/{chunk-CRFTLUWT.js.map → chunk-UTDA43TB.js.map} +0 -0
  397. /package/dist/{chunk-GKCCHHZK.js.map → chunk-UXKMMNJD.js.map} +0 -0
  398. /package/dist/{chunk-57INMZ6F.js.map → chunk-UZRIKUFS.js.map} +0 -0
  399. /package/dist/{chunk-2FSHROYK.js.map → chunk-V53GJUSL.js.map} +0 -0
  400. /package/dist/{chunk-LKT755LP.js.map → chunk-VA4WSW54.js.map} +0 -0
  401. /package/dist/{chunk-ZHKD4EZM.js.map → chunk-VCEH5UYS.js.map} +0 -0
  402. /package/dist/{chunk-CHVU4RE5.js.map → chunk-VDLMAJXS.js.map} +0 -0
  403. /package/dist/{chunk-XFTPUXCE.js.map → chunk-VRAD2FGO.js.map} +0 -0
  404. /package/dist/{chunk-T7VNN3ZM.js.map → chunk-XVLEGMJI.js.map} +0 -0
  405. /package/dist/{chunk-QDCJNRKK.js.map → chunk-YXOAQAH2.js.map} +0 -0
  406. /package/dist/{forget-KHF7A44H.js.map → forget-CQCLB5LB.js.map} +0 -0
package/src/storage.ts CHANGED
@@ -27,7 +27,24 @@ import { isErrnoCode } from "./utils/errno.js";
27
27
  import { RECALL_FALLBACK_DIRS, getCategoryDir, categoryDirName } from "./utils/category-dir.js";
28
28
  import { assertPathInsideRoot } from "./utils/path-containment.js";
29
29
  import { decodeYamlScalar } from "./utils/yaml-scalar.js";
30
- import { getCachedEntities, invalidateAllForDir, setCachedEntities } from "./memory-cache.js";
30
+ import {
31
+ clearMemoryCache,
32
+ getCachedEntities,
33
+ getCachedMemories,
34
+ invalidateAllForDir,
35
+ invalidateDerivedAndGlobalForDir,
36
+ setCachedEntities,
37
+ setCachedMemories,
38
+ updateCacheOnDelete,
39
+ updateCacheOnWrite,
40
+ } from "./memory-cache.js";
41
+ import {
42
+ getInFlightRead,
43
+ setInFlightRead,
44
+ deleteInFlightRead,
45
+ deleteInFlightReadsForDir,
46
+ clearInFlightReads,
47
+ } from "./in-flight-reads.js";
31
48
  import { rotateMarkdownFileToArchive } from "./hygiene.js";
32
49
  import { sanitizeMemoryContent } from "./sanitize.js";
33
50
  import {
@@ -2305,23 +2322,72 @@ export class StorageManager {
2305
2322
  static readonly ARTIFACT_INDEX_CACHE_TTL_MS = 60_000; // 1 minute
2306
2323
  private static readonly artifactWriteVersionByDir = new Map<string, number>();
2307
2324
  private static readonly memoryStatusVersionByDir = new Map<string, number>();
2325
+ // Corpus version sentinel (issue #1902): bumped on every memory-FILE mutation
2326
+ // so the version-keyed hot-memories cache stays coherent across processes,
2327
+ // without touching memory-status (which would invalidate the entity cache on
2328
+ // every plain create). In-process fallback when the on-disk sentinel is
2329
+ // unavailable; canonical source is state/.memory-corpus-version.log.
2330
+ private static readonly memoryCorpusVersionByDir = new Map<string, number>();
2308
2331
  private static readonly secureStoreEntityCacheKeyIds = new WeakMap<Buffer, number>();
2309
2332
  private static nextSecureStoreEntityCacheKeyId = 1;
2310
2333
  // In-process fallback for the cold-write sentinel (used when the disk file
2311
2334
  // is not accessible). The canonical source of truth is state/cold-write.log.
2312
2335
  private static readonly coldWriteVersionByDir = new Map<string, number>();
2313
2336
 
2314
- // Module-level cache for readAllMemories() keyed by base directory.
2315
- // Shared across all StorageManager instances to avoid duplicate I/O when
2316
- // multiple concurrent callers (e.g. verifiedRecall + verifiedRules) read the
2317
- // same directory simultaneously. In-flight deduplication prevents multiple
2318
- // concurrent reads of the same directory.
2319
- //
2320
- // Stale-while-revalidate: once the cache has a value, subsequent reads after
2321
- // TTL expiry return the stale cached data immediately and kick off a background
2322
- // refresh. This eliminates the 13-60 s cold-scan penalty that would otherwise
2323
- // block recall requests every 5 minutes on large memory collections (80k+ files).
2324
- private static readonly allMemoriesInFlight = new Map<string, Promise<MemoryFile[]>>();
2337
+ /**
2338
+ * Process-wide default for the hot-memories result cache gate (issue #1902).
2339
+ * Set once from config by the orchestrator/access layer via
2340
+ * setHotMemoriesCacheDefault() so that EVERY StorageManager — including the
2341
+ * ephemeral instances recall sub-stages (verified-recall, semantic-rule
2342
+ * verification, ...) construct over the same dir — honors the operator's
2343
+ * hotMemoriesCacheEnabled setting. The constructor option overrides this
2344
+ * per-instance (tests). Defaults true (cache on).
2345
+ */
2346
+ private static hotMemoriesCacheDefault = true;
2347
+
2348
+ /**
2349
+ * Per-memory-directory override of the hot-cache gate (issue #1902, Codex
2350
+ * P2). setHotMemoriesCacheDefault(memoryDir, enabled) registers the
2351
+ * operator's setting keyed by dir, so a StorageManager constructed over that
2352
+ * dir honors ITS owning orchestrator's config — not merely the last
2353
+ * orchestrator constructed in the process. Unregistered dirs fall back to
2354
+ * the process-wide default above.
2355
+ */
2356
+ private static readonly hotMemoriesCacheDefaultByDir = new Map<string, boolean>();
2357
+
2358
+ /**
2359
+ * Process-wide TTL (ms) safety net for the hot cache (issue #1902). Bounds
2360
+ * how long a version-keyed entry is served before a fresh disk scan, so
2361
+ * external filesystem edits (manual/git/editor) that don't bump the sentinel
2362
+ * self-heal. Set from config by the orchestrator via setHotMemoriesCacheDefault.
2363
+ * 0 disables the TTL (version invalidation only). Default 60s.
2364
+ */
2365
+ private static hotMemoriesCacheTtlMs = 60_000;
2366
+
2367
+ /**
2368
+ * Per-memory-directory override of the hot-cache TTL (issue #1902, Codex P2),
2369
+ * mirroring hotMemoriesCacheDefaultByDir. Registered by
2370
+ * setHotMemoriesCacheDefault so two orchestrators in one process with
2371
+ * different TTLs don't clobber each other's setting. Unregistered dirs fall
2372
+ * back to the process-wide default above.
2373
+ */
2374
+ private static readonly hotMemoriesCacheTtlByDir = new Map<string, number>();
2375
+
2376
+ /**
2377
+ * Whether the process-wide hot-cache fallbacks (hotMemoriesCacheDefault /
2378
+ * hotMemoriesCacheTtlMs) have been seeded by the first
2379
+ * setHotMemoriesCacheDefault call (issue #1902, Cursor Low). Only the first
2380
+ * registration seeds them; later registrations touch ONLY their per-dir map
2381
+ * entry. This makes the process-wide fallback deterministic (first-writer,
2382
+ * by init order) instead of last-writer-wins, so a second orchestrator with
2383
+ * a divergent config cannot silently flip the fallback that unregistered
2384
+ * dirs resolve to. All REGISTERED dirs are isolated by the per-dir maps.
2385
+ */
2386
+ private static hotMemoriesCacheProcessDefaultSeeded = false;
2387
+
2388
+ // Module-level maps for readAllMemories() keyed by base directory, shared
2389
+ // across all StorageManager instances (static) so concurrent callers over
2390
+ // the same dir cooperate.
2325
2391
 
2326
2392
  // Cache for readAllColdMemories() — keyed by cold root directory path.
2327
2393
  // Prevents an uncached full-tree directory scan on every structured-attribute
@@ -2336,7 +2402,7 @@ export class StorageManager {
2336
2402
  /** Read by storage/memory-read-store.ts (decomposition). */
2337
2403
  static readonly coldMemoriesCache = new Map<
2338
2404
  string,
2339
- { memories: MemoryFile[]; loadedAt: number; coldVersion: number }
2405
+ { memories: MemoryFile[]; loadedAt: number; coldVersion: number; keyId: string }
2340
2406
  >();
2341
2407
 
2342
2408
  // Cache for readQuestions() — avoids serially re-reading tens of thousands of
@@ -2457,6 +2523,54 @@ export class StorageManager {
2457
2523
  return `secure-store:key:${id}`;
2458
2524
  }
2459
2525
 
2526
+ /**
2527
+ * Secure-store key identity for the version-keyed hot/archive memory caches
2528
+ * (issue #1902, Codex P1). Scopes cached DECRYPTED corpora by the key that
2529
+ * decrypted them, so a locked/unkeyed StorageManager for the same baseDir
2530
+ * never reads content another instance decrypted under a key (which would
2531
+ * bypass the locked-store error). Plaintext and locked stores share the ""
2532
+ * namespace; an unlocked encrypted store uses its per-key id.
2533
+ */
2534
+ hotCacheKeyId(): string {
2535
+ return this._secureStoreKey === null ? "" : this.getEntityCacheSecureStoreKey();
2536
+ }
2537
+
2538
+ /**
2539
+ * Resolved hot-cache gate for THIS store (issue #1902, Cursor Medium): the
2540
+ * per-instance override if one was passed, else the per-dir/process default.
2541
+ * Recall sub-stages (verified-recall, semantic-rule) read this to honor the
2542
+ * operator's opt-out for their derived caches even when the caller omits the
2543
+ * flag in options.
2544
+ */
2545
+ isHotCacheEnabled(): boolean {
2546
+ return this.hotMemoriesCacheEnabled;
2547
+ }
2548
+
2549
+ /**
2550
+ * Effective hot-cache TTL (ms) for this store's dir (issue #1902): the per-dir
2551
+ * registration if present, else the process-wide default. 0 disables the TTL.
2552
+ */
2553
+ hotCacheTtlMs(): number {
2554
+ return StorageManager.hotMemoriesCacheTtlByDir.get(this.baseDir) ?? StorageManager.hotMemoriesCacheTtlMs;
2555
+ }
2556
+
2557
+ /**
2558
+ * True if `p` lives in this store's cold/ or archive/ tier (issue #1902,
2559
+ * Codex P2). Determined RELATIVE to baseDir — a substring scan of the
2560
+ * absolute path would misclassify every active path when memoryDir itself is
2561
+ * nested under a dir literally named "cold" or "archive" (e.g.
2562
+ * /srv/cold/remnic), skipping the patch and stranding a stale hot entry.
2563
+ */
2564
+ private isColdOrArchiveTierPath(p: string): boolean {
2565
+ const rel = path.relative(this.baseDir, p);
2566
+ return (
2567
+ rel === "cold" ||
2568
+ rel === "archive" ||
2569
+ rel.startsWith(`cold${path.sep}`) ||
2570
+ rel.startsWith(`archive${path.sep}`)
2571
+ );
2572
+ }
2573
+
2460
2574
  /**
2461
2575
  * Mark the secure-store as required for this storage instance.
2462
2576
  * When required and locked, writes throw SecureStoreLockedError
@@ -2553,10 +2667,23 @@ export class StorageManager {
2553
2667
  }
2554
2668
  }
2555
2669
 
2670
+ private readonly hotMemoriesCacheEnabled: boolean;
2671
+
2556
2672
  constructor(
2557
2673
  private readonly baseDir: string,
2558
- private readonly entitySchemas?: PluginConfig["entitySchemas"]
2674
+ private readonly entitySchemas?: PluginConfig["entitySchemas"],
2675
+ /**
2676
+ * Hot-memories result cache gate (issue #1902). When omitted, resolves to
2677
+ * the per-directory default registered by setHotMemoriesCacheDefault()
2678
+ * (falling back to the process-wide default); pass an explicit boolean to
2679
+ * override per-instance (tests, memory-constrained readers).
2680
+ */
2681
+ hotMemoriesCacheEnabledOverride?: boolean,
2559
2682
  ) {
2683
+ this.hotMemoriesCacheEnabled =
2684
+ hotMemoriesCacheEnabledOverride ??
2685
+ StorageManager.hotMemoriesCacheDefaultByDir.get(baseDir) ??
2686
+ StorageManager.hotMemoriesCacheDefault;
2560
2687
  // Load this store's alias table at construction (#1534): StorageManager
2561
2688
  // is created in a dozen places (namespace router, operator toolkit,
2562
2689
  // compounding engine, cold storage, ...) and most never call
@@ -2565,6 +2692,40 @@ export class StorageManager {
2565
2692
  this.loadAliasesSync();
2566
2693
  }
2567
2694
 
2695
+ /** Set the process-wide hot-memories cache default (issue #1902). The
2696
+ * orchestrator/access layer calls this with the operator's
2697
+ * hotMemoriesCacheEnabled config so the escape hatch reaches every
2698
+ * StorageManager, including ephemeral recall-sub-stage instances. Only an
2699
+ * explicit `false` disables; `undefined` (e.g. a cast test config missing
2700
+ * the field) leaves the cache on. */
2701
+ static setHotMemoriesCacheDefault(
2702
+ memoryDir: string,
2703
+ enabled: boolean | undefined,
2704
+ ttlMs?: number,
2705
+ ): void {
2706
+ // Register per-dir so each orchestrator's config reaches every
2707
+ // StorageManager built over its own memory dir (Codex P2). The per-dir map
2708
+ // is authoritative for every registered dir.
2709
+ StorageManager.hotMemoriesCacheDefaultByDir.set(memoryDir, enabled !== false);
2710
+ // TTL safety net: a finite, non-negative override wins (0 honored = disable).
2711
+ // Registered per-dir (Codex P2) so concurrent orchestrators with different
2712
+ // TTLs don't clobber each other.
2713
+ const ttlValid = typeof ttlMs === "number" && Number.isFinite(ttlMs) && ttlMs >= 0;
2714
+ if (ttlValid) {
2715
+ StorageManager.hotMemoriesCacheTtlByDir.set(memoryDir, ttlMs as number);
2716
+ }
2717
+ // Seed the process-wide fallbacks (for UNregistered dirs) ONCE, from the
2718
+ // first registration only (issue #1902, Cursor Low). A later orchestrator
2719
+ // with a divergent config must not flip the fallback out from under the
2720
+ // first — that was a last-writer-wins race. First-writer is deterministic
2721
+ // by init order, and every registered dir is isolated per-dir above.
2722
+ if (!StorageManager.hotMemoriesCacheProcessDefaultSeeded) {
2723
+ StorageManager.hotMemoriesCacheDefault = enabled !== false;
2724
+ if (ttlValid) StorageManager.hotMemoriesCacheTtlMs = ttlMs as number;
2725
+ StorageManager.hotMemoriesCacheProcessDefaultSeeded = true;
2726
+ }
2727
+ }
2728
+
2568
2729
  /**
2569
2730
  * The LIVE class object of this instance (issue #1809 review of the
2570
2731
  * storage decomposition): extracted store modules must read/write the
@@ -2622,18 +2783,20 @@ export class StorageManager {
2622
2783
  return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
2623
2784
  }
2624
2785
 
2625
- private versionFilePath(kind: "memory-status" | "artifact-write" | "cold-write"): string {
2786
+ private versionFilePath(kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus"): string {
2626
2787
  const fileName =
2627
2788
  kind === "memory-status"
2628
2789
  ? ".memory-status-version.log"
2629
2790
  : kind === "artifact-write"
2630
2791
  ? ".artifact-write-version.log"
2631
- : ".cold-write-version.log";
2792
+ : kind === "cold-write"
2793
+ ? ".cold-write-version.log"
2794
+ : ".memory-corpus-version.log";
2632
2795
  return path.join(this.stateDir, fileName);
2633
2796
  }
2634
2797
 
2635
2798
  private bumpSharedVersion(
2636
- kind: "memory-status" | "artifact-write" | "cold-write",
2799
+ kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus",
2637
2800
  fallbackMap: Map<string, number>
2638
2801
  ): number {
2639
2802
  const filePath = this.versionFilePath(kind);
@@ -2651,7 +2814,7 @@ export class StorageManager {
2651
2814
  }
2652
2815
 
2653
2816
  private readSharedVersion(
2654
- kind: "memory-status" | "artifact-write" | "cold-write",
2817
+ kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus",
2655
2818
  fallbackMap: Map<string, number>
2656
2819
  ): number {
2657
2820
  const filePath = this.versionFilePath(kind);
@@ -2664,6 +2827,10 @@ export class StorageManager {
2664
2827
 
2665
2828
  private bumpMemoryStatusVersion(): void {
2666
2829
  this.bumpSharedVersion("memory-status", StorageManager.memoryStatusVersionByDir);
2830
+ // Also bump the corpus sentinel so peer processes rescan the hot-memories
2831
+ // cache after status/lifecycle/bulk mutations (issue #1902). invalidateAllForDir
2832
+ // below drops the hot layer locally; the corpus bump handles cross-process.
2833
+ this.bumpSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
2667
2834
  // Invalidation chokepoint (issue #1535): status/entity mutations funnel
2668
2835
  // through this bump — several of them (supersedeMemory, archiveMemories,
2669
2836
  // writeEntity, cleanExpiredCommitments) do NOT call
@@ -2677,6 +2844,62 @@ export class StorageManager {
2677
2844
  return this.readSharedVersion("memory-status", StorageManager.memoryStatusVersionByDir);
2678
2845
  }
2679
2846
 
2847
+ /**
2848
+ * Corpus version sentinel for the hot-memories result cache (issue #1902).
2849
+ * Distinct from memory-status: it bumps on EVERY memory-file mutation
2850
+ * (create/update/delete/bulk) so the version-keyed hot cache stays coherent
2851
+ * across processes, WITHOUT bumping memory-status — which would needlessly
2852
+ * invalidate the version-keyed entity cache on every plain fact create (the
2853
+ * ki-spike regression the sibling perf work removes). memory-status keeps its
2854
+ * lifecycle/status/entity semantics unchanged.
2855
+ */
2856
+ getMemoryCorpusVersion(): number {
2857
+ return this.readSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
2858
+ }
2859
+
2860
+ private bumpMemoryCorpusVersion(): void {
2861
+ // Bump only — unlike bumpMemoryStatusVersion this must NOT invalidateAllForDir
2862
+ // (that would drop the very hot entry the write path just patched).
2863
+ this.bumpSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
2864
+ // Also drop the in-flight readAllMemories slot (Cursor Medium, #1902): after
2865
+ // the sentinel advances, a concurrent read must not attach to a scan that
2866
+ // began BEFORE this mutation and receive a pre-mutation corpus. Clearing the
2867
+ // slot forces such a read to start a fresh scan of current disk state. (The
2868
+ // patch path uses bumpMemoryCorpusVersionExclusive and clears the slot
2869
+ // itself; this covers the per-item supersede/archive/TTL-cleanup bumps.)
2870
+ deleteInFlightReadsForDir(this.baseDir);
2871
+ }
2872
+
2873
+ /**
2874
+ * Corpus bump that reports whether THIS process's append was the only one
2875
+ * since the pre-bump size (issue #1902, Codex P1). appendFileSync is atomic
2876
+ * per write, but a peer process can append concurrently; the `exclusive`
2877
+ * flag lets patchHotMemoriesCache refuse to re-key its locally patched
2878
+ * (peer-incomplete) corpus at a version that already reflects a peer's
2879
+ * still-unread write.
2880
+ */
2881
+ private bumpMemoryCorpusVersionExclusive(): { produced: number; exclusive: boolean } {
2882
+ const filePath = this.versionFilePath("memory-corpus");
2883
+ try {
2884
+ mkdirSync(this.stateDir, { recursive: true });
2885
+ let before = 0;
2886
+ try {
2887
+ before = statSync(filePath).size;
2888
+ } catch {
2889
+ before = 0;
2890
+ }
2891
+ appendFileSync(filePath, "x");
2892
+ const produced = statSync(filePath).size;
2893
+ StorageManager.memoryCorpusVersionByDir.set(this.baseDir, produced);
2894
+ // Exclusive iff exactly our single byte landed between the two stats.
2895
+ return { produced, exclusive: produced === before + 1 };
2896
+ } catch {
2897
+ const next = (StorageManager.memoryCorpusVersionByDir.get(this.baseDir) ?? 0) + 1;
2898
+ StorageManager.memoryCorpusVersionByDir.set(this.baseDir, next);
2899
+ return { produced: next, exclusive: true };
2900
+ }
2901
+ }
2902
+
2680
2903
  private bumpArtifactWriteVersion(): number {
2681
2904
  return this.bumpSharedVersion("artifact-write", StorageManager.artifactWriteVersionByDir);
2682
2905
  }
@@ -3581,6 +3804,19 @@ export class StorageManager {
3581
3804
  await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
3582
3805
  await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
3583
3806
  await mkdir(path.join(this.baseDir, "config"), { recursive: true });
3807
+ // Activate the corpus sentinel at setup (issue #1902, Cursor Medium): an
3808
+ // existing corpus predating this feature has a size-0/absent sentinel, which
3809
+ // getCachedMemories treats as a miss — so a read-heavy workload would never
3810
+ // engage the hot cache until the first mutation bumped it. The daemon calls
3811
+ // ensureDirectories() at startup (orchestration/orchestrator-init.ts) before
3812
+ // serving recall reads, so seeding a nonzero version here makes the cache
3813
+ // engage from the first read. Fires once, only while the version is still 0.
3814
+ // Deliberately NOT done on the readAllMemories() path: a write-side-effect
3815
+ // during a read perturbs concurrent hot/cold reads in the tier-migration
3816
+ // cycle. Fail-open: a failed bump falls back to the in-process counter.
3817
+ if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
3818
+ this.bumpMemoryCorpusVersion();
3819
+ }
3584
3820
  }
3585
3821
 
3586
3822
  /**
@@ -3848,7 +4084,7 @@ export class StorageManager {
3848
4084
 
3849
4085
  await this.snapshotBeforeWrite(filePath, "write");
3850
4086
  await this.writeStorageSecureFile(filePath, fileContent);
3851
- this.invalidateAllMemoriesCache();
4087
+ await this.patchHotMemoriesCache({ addedPath: filePath });
3852
4088
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
3853
4089
  memoryId: id,
3854
4090
  eventType: "created",
@@ -4190,21 +4426,65 @@ export class StorageManager {
4190
4426
  }
4191
4427
 
4192
4428
  async readAllMemories(): Promise<MemoryFile[]> {
4429
+ // Version-keyed hot-memories cache (issue #1902): once populated, serve the
4430
+ // full parsed corpus from memory within a version epoch. getCachedMemories
4431
+ // returns null when the sentinel is 0 (fresh/test dirs) or the stored
4432
+ // version differs from the current on-disk sentinel (a peer or local write
4433
+ // bumped it), so a stale entry is never served — the miss falls through to
4434
+ // a fresh disk scan below.
4435
+ if (this.hotMemoriesCacheEnabled) {
4436
+ const cached = getCachedMemories(
4437
+ this.baseDir,
4438
+ this.getMemoryCorpusVersion(),
4439
+ this.hotCacheKeyId(),
4440
+ this.hotCacheTtlMs(),
4441
+ );
4442
+ // Null-check, not truthiness (kilo WARNING): getCachedMemories returns []
4443
+ // for a cached EMPTY corpus, which is falsy — a truthiness guard would
4444
+ // force a full disk scan on every read of an empty/near-empty store.
4445
+ if (cached !== null) return cached;
4446
+ }
4193
4447
  // Deduplicate concurrent reads for the same directory so multiple
4194
4448
  // callers in the same recall share one disk scan.
4195
- const inFlight = StorageManager.allMemoriesInFlight.get(this.baseDir);
4449
+ // Snapshot the secure-store key identity ONCE (issue #1902, Codex Medium):
4450
+ // hotCacheKeyId() can change mid-scan if setSecureStoreKey(null/other) runs
4451
+ // during the await. Recomputing it at publish time would store K-decrypted
4452
+ // results under the new ""/other identity, letting a locked/differently-keyed
4453
+ // read match the entry and receive plaintext instead of SecureStoreLockedError.
4454
+ // The in-flight slot get/set/delete also key on this snapshot so a mid-scan
4455
+ // key change can't orphan the slot registered under the old identity.
4456
+ const keyId = this.hotCacheKeyId();
4457
+ const inFlight = getInFlightRead(this.baseDir, keyId);
4196
4458
  if (inFlight) return inFlight;
4197
4459
 
4198
- const readPromise = this._readAllMemoriesFromDisk();
4199
- StorageManager.allMemoriesInFlight.set(this.baseDir, readPromise);
4460
+ const readPromise = (async (): Promise<MemoryFile[]> => {
4461
+ // Capture the version BEFORE the scan and store under it. If a concurrent
4462
+ // bump (peer or local write) lands during the scan, the stored entry's
4463
+ // version is older than the sentinel, so the next getCachedMemories()
4464
+ // rejects it (version mismatch → rescan) — correct, never stale.
4465
+ const version = this.getMemoryCorpusVersion();
4466
+ const memories = await this._readAllMemoriesFromDisk();
4467
+ // Publish only if neither the corpus version NOR the key identity changed
4468
+ // during the scan. The version guard prevents clobbering a newer patched +
4469
+ // re-keyed entry; the keyId guard prevents publishing this key's decrypted
4470
+ // corpus under a since-changed identity (Codex Medium, #1902).
4471
+ if (
4472
+ this.hotMemoriesCacheEnabled &&
4473
+ this.getMemoryCorpusVersion() === version &&
4474
+ this.hotCacheKeyId() === keyId
4475
+ ) {
4476
+ setCachedMemories(this.baseDir, memories, version, keyId, this.hotCacheTtlMs());
4477
+ }
4478
+ return memories;
4479
+ })();
4480
+ setInFlightRead(this.baseDir, keyId, readPromise);
4200
4481
  try {
4201
4482
  return await readPromise;
4202
4483
  } finally {
4203
- // Only delete if we still own the slot — invalidateAllMemoriesCache()
4204
- // may have already cleared it and a new read may have claimed it.
4205
- if (StorageManager.allMemoriesInFlight.get(this.baseDir) === readPromise) {
4206
- StorageManager.allMemoriesInFlight.delete(this.baseDir);
4207
- }
4484
+ // Only delete if we still own the slot for our snapshot key identity
4485
+ // invalidateAllMemoriesCache() may have already cleared it and a new read
4486
+ // may have claimed it (the expected-promise guard enforces owner-only).
4487
+ deleteInFlightRead(this.baseDir, keyId, readPromise);
4208
4488
  }
4209
4489
  }
4210
4490
 
@@ -4237,9 +4517,22 @@ export class StorageManager {
4237
4517
  /** Clear ALL static caches. Use in tests that write files directly
4238
4518
  * (bypassing StorageManager.writeMemory) to avoid stale reads. */
4239
4519
  static clearAllStaticCaches(): void {
4240
- StorageManager.allMemoriesInFlight.clear();
4520
+ clearInFlightReads();
4241
4521
  StorageManager.questionsCache.clear();
4242
4522
  StorageManager.coldMemoriesCache.clear(); // also wipe the cold-scan TTL cache
4523
+ // Also clear the module-level memory-cache layers (hot-memories result cache
4524
+ // + entity/derived/QMD layers) so the reset seam is authoritative for tests
4525
+ // that write files directly (issue #1902).
4526
+ clearMemoryCache();
4527
+ // Reset the hot-cache config statics to construction defaults so each test
4528
+ // starts from a clean slate (issue #1902). resetStaticCaches() runs before
4529
+ // every contract test; without this the per-dir maps and the first-writer
4530
+ // process-wide seed would leak across tests.
4531
+ StorageManager.hotMemoriesCacheDefaultByDir.clear();
4532
+ StorageManager.hotMemoriesCacheTtlByDir.clear();
4533
+ StorageManager.hotMemoriesCacheDefault = true;
4534
+ StorageManager.hotMemoriesCacheTtlMs = 60_000;
4535
+ StorageManager.hotMemoriesCacheProcessDefaultSeeded = false;
4243
4536
  }
4244
4537
 
4245
4538
  /** Cancel any in-flight concurrent read so the next readAllMemories()
@@ -4255,7 +4548,11 @@ export class StorageManager {
4255
4548
  * cold content actually changes (hot→cold demotions, writeMemoryFileAtomic
4256
4549
  * inside cold/, archiveMemory, etc.). */
4257
4550
  private invalidateAllMemoriesCache(): void {
4258
- StorageManager.allMemoriesInFlight.delete(this.baseDir);
4551
+ deleteInFlightReadsForDir(this.baseDir);
4552
+ // Bulk/ambiguous mutations drop the hot layer wholesale (below); bump the
4553
+ // corpus sentinel too so PEER processes rescan instead of serving a warm
4554
+ // pre-mutation corpus entry (issue #1902 cross-process coherence).
4555
+ this.bumpMemoryCorpusVersion();
4259
4556
  // Invalidation chokepoint (issue #1535): eagerly evict EVERY process-level
4260
4557
  // cache layer that can serve memory-derived content for this dir — hot,
4261
4558
  // archive, entity, derived episode/rule views, and both QMD result caches
@@ -4274,6 +4571,103 @@ export class StorageManager {
4274
4571
  }
4275
4572
  }
4276
4573
 
4574
+ /**
4575
+ * Single-file write/delete fast path for the hot-memories cache (issue
4576
+ * #1902). Keeps THIS process's version-keyed corpus entry warm by patching
4577
+ * the changed memory in place, bumps the dedicated `.memory-corpus-version.log`
4578
+ * sentinel so PEER processes rescan (memory-status is deliberately NOT bumped
4579
+ * here so plain creates/content edits don't invalidate the entity cache), and
4580
+ * evicts only the derived/global layers (which
4581
+ * are cheap to rebuild and are never patched). Mirrors the in-flight-dedup
4582
+ * and tombstone-store invalidation that invalidateAllMemoriesCache() does.
4583
+ *
4584
+ * `added` is the written MemoryFile (writes/overwrites); `removedPath` is a
4585
+ * deleted path. A path outside the active scan roots (under cold/ or
4586
+ * archive/) is NOT applied to the hot map — that cache holds active memories
4587
+ * only — but the version bump and derived/global eviction still run so
4588
+ * coherence is preserved. Bulk/ambiguous mutations keep the wholesale
4589
+ * invalidateAllMemoriesCache() drop instead (issue #1902 Step 5).
4590
+ */
4591
+ private async patchHotMemoriesCache(opts: { addedPath?: string; removedPath?: string }): Promise<void> {
4592
+ const prevVersion = this.getMemoryCorpusVersion();
4593
+ // Snapshot the key identity once (issue #1902, Codex Medium): a mid-await
4594
+ // setSecureStoreKey change would otherwise let the post-await patch/re-key
4595
+ // store this key's decrypted memory under a since-changed identity.
4596
+ const keyId = this.hotCacheKeyId();
4597
+ const warm = this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
4598
+ if (
4599
+ warm &&
4600
+ opts.removedPath !== undefined &&
4601
+ !this.isColdOrArchiveTierPath(opts.removedPath)
4602
+ ) {
4603
+ updateCacheOnDelete(this.baseDir, opts.removedPath, keyId);
4604
+ }
4605
+ // Bump the corpus sentinel AND drop the in-flight slot BEFORE the awaited
4606
+ // one-file re-parse below (Cursor Medium, #1902). The on-disk write already
4607
+ // landed in the caller, so advancing the version now makes any concurrent
4608
+ // readAllMemories() during the re-parse window miss the still-old hot entry
4609
+ // and start a FRESH scan of disk truth (which includes the write) instead of
4610
+ // serving the pre-write corpus or joining a pre-write in-flight scan.
4611
+ // memory-status is deliberately NOT bumped here so plain creates/content
4612
+ // edits don't invalidate the entity cache (issue #1902).
4613
+ const { produced, exclusive } = this.bumpMemoryCorpusVersionExclusive();
4614
+ deleteInFlightReadsForDir(this.baseDir);
4615
+ let patchedOk = warm;
4616
+ if (
4617
+ warm &&
4618
+ opts.addedPath !== undefined &&
4619
+ !this.isColdOrArchiveTierPath(opts.addedPath)
4620
+ ) {
4621
+ // Re-read + parse the ONE just-written file so the cached object is
4622
+ // exactly what a fresh disk scan would yield — serialize→parse normalizes
4623
+ // fields (e.g. an empty array becomes undefined), and the in-memory write
4624
+ // object would otherwise diverge from disk truth. This is a single O(1)
4625
+ // read (the file is page-cache-hot), NOT the O(corpus) scan the cache
4626
+ // exists to avoid.
4627
+ // Best-effort re-parse: this cache patch runs AFTER the on-disk write and
4628
+ // corpus-version bump have already persisted, so it must never fail the
4629
+ // completed mutation. If the re-read throws (e.g. setSecureStoreKey(null)
4630
+ // locked the store mid-await → SecureStoreLockedError), treat the entry as
4631
+ // unpatchable — leave it version-invalidated so the next read rescans —
4632
+ // rather than propagating and making writeMemory/updateMemory reject an
4633
+ // already-durable write (which a caller would retry, duplicating memories).
4634
+ // Issue #1902, Codex Medium.
4635
+ let parsed: MemoryFile | undefined;
4636
+ try {
4637
+ [parsed] = await this.readParsedMemoriesFromPaths([opts.addedPath], 1);
4638
+ } catch {
4639
+ parsed = undefined;
4640
+ }
4641
+ if (parsed) {
4642
+ updateCacheOnWrite(this.baseDir, parsed, keyId);
4643
+ } else {
4644
+ // Could not re-parse the write — don't trust the patched entry; leaving
4645
+ // it tagged at prevVersion means the version-bumped read rejects it
4646
+ // (version mismatch) and rescans.
4647
+ patchedOk = false;
4648
+ }
4649
+ }
4650
+ if (patchedOk && exclusive && produced === prevVersion + 1 && this.getMemoryCorpusVersion() === produced && this.hotCacheKeyId() === keyId) {
4651
+ // Re-key the patched entry (still tagged prevVersion) to the version THIS
4652
+ // mutation produced — ONLY if no peer appended between prevVersion capture
4653
+ // and our bump (produced === prevVersion + 1), our bump was exclusive, and
4654
+ // no further peer append landed during the re-parse (still === produced).
4655
+ // Otherwise a concurrent read has already republished disk truth at the
4656
+ // newer version, or our patched corpus lacks a peer's write — either way
4657
+ // leave it version-rejected so the next read rescans.
4658
+ const patched = getCachedMemories(this.baseDir, prevVersion, keyId);
4659
+ if (patched) setCachedMemories(this.baseDir, patched, produced, keyId, this.hotCacheTtlMs());
4660
+ }
4661
+ // Evict every OTHER layer; the hot layer is patched (or intentionally left
4662
+ // to be version-rejected) above, not dropped here.
4663
+ invalidateDerivedAndGlobalForDir(this.baseDir);
4664
+ if (this.tombstoneStore) {
4665
+ this.tombstoneStore.invalidate();
4666
+ this.tombstoneStore = null;
4667
+ this.tombstoneStoreLoadPromise = null;
4668
+ }
4669
+ }
4670
+
4277
4671
  /**
4278
4672
  * Invalidate the cold-scan cache for this storage root and bump the
4279
4673
  * on-disk cold-version sentinel so that other processes (gateway, CLI) see
@@ -4792,7 +5186,7 @@ export class StorageManager {
4792
5186
  }
4793
5187
  const fileContent = `${serializeFrontmatter(updated)}\n\n${sanitized.text}\n`;
4794
5188
  await this.writeStorageSecureFile(memory.path, fileContent);
4795
- this.invalidateAllMemoriesCache();
5189
+ await this.patchHotMemoriesCache({ addedPath: memory.path });
4796
5190
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
4797
5191
  memoryId: id,
4798
5192
  eventType: "updated",
@@ -4827,7 +5221,7 @@ export class StorageManager {
4827
5221
 
4828
5222
  const fileContent = `${serializeFrontmatter(updated)}\n\n${memory.content}\n`;
4829
5223
  await this.writeStorageSecureFile(memory.path, fileContent);
4830
- this.invalidateAllMemoriesCache();
5224
+ await this.patchHotMemoriesCache({ addedPath: memory.path });
4831
5225
  // If the target file lives in cold/, bump the cold-version sentinel so
4832
5226
  // other processes detect the change on their next readAllColdMemories()
4833
5227
  // call (Finding UvUy fix).
@@ -4862,6 +5256,9 @@ export class StorageManager {
4862
5256
  lifecycle?.ruleVersion
4863
5257
  );
4864
5258
  if (beforeStatus !== afterStatus) {
5259
+ // Status/lifecycle change must bump memory-status so the version-keyed
5260
+ // entity/derived caches and peer processes observe it (issue #1902:
5261
+ // restored — corpus bump alone doesn't cover status-derived views).
4865
5262
  this.bumpMemoryStatusVersion();
4866
5263
  }
4867
5264
  return true;
@@ -4891,6 +5288,10 @@ export class StorageManager {
4891
5288
  try {
4892
5289
  await unlink(m.path);
4893
5290
  markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
5291
+ // Bump per deletion so a concurrent readAllMemories mid-loop rescans
5292
+ // and never re-caches a partially-cleaned corpus (Cursor Medium,
5293
+ // #1902); invalidateAllMemoriesCache below still runs at loop end.
5294
+ this.bumpMemoryCorpusVersion();
4894
5295
  deleted.push(m);
4895
5296
  log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
4896
5297
  } catch {
@@ -5906,6 +6307,10 @@ export class StorageManager {
5906
6307
  try {
5907
6308
  await unlink(m.path);
5908
6309
  markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
6310
+ // Bump per deletion so a concurrent readAllMemories mid-loop rescans
6311
+ // and never re-caches a partially-cleaned corpus (Cursor Medium,
6312
+ // #1902); bumpMemoryStatusVersion below still runs at loop end.
6313
+ this.bumpMemoryCorpusVersion();
5909
6314
  deleted.push(m);
5910
6315
  log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
5911
6316
  } catch {
@@ -5935,6 +6340,22 @@ export class StorageManager {
5935
6340
  const memories = await this.readAllMemories();
5936
6341
  const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
5937
6342
  let updated = 0;
6343
+ // Capture the corpus version + warmth BEFORE writing so we can patch the hot
6344
+ // entries in place and then re-key them to the version this flush produces
6345
+ // (issue #1902). Access-tracking flush is batched (consolidation / buffer
6346
+ // full), not per-recall, so the single corpus bump below is cheap.
6347
+ const prevVersion = this.getMemoryCorpusVersion();
6348
+ // Snapshot the secure-store key identity once (Cursor Medium #1902), mirroring
6349
+ // readAllMemories/patchHotMemoriesCache: a mid-flush setSecureStoreKey change
6350
+ // would otherwise let loop patches or the re-keyed entry be stored under a
6351
+ // different identity than the one that decrypted this corpus.
6352
+ const keyId = this.hotCacheKeyId();
6353
+ const warm =
6354
+ this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
6355
+ // Record each applied patch so the end-of-flush re-key can re-apply them on
6356
+ // top of whatever is cached at prevVersion — robust to a concurrent scan
6357
+ // that republished an UNpatched corpus mid-flush (Cursor Medium #1902).
6358
+ const appliedPatches = new Map<string, { accessCount: number; lastAccessed: string }>();
5938
6359
 
5939
6360
  for (const entry of entries) {
5940
6361
  const memory = memoryMap.get(entry.memoryId);
@@ -5949,6 +6370,14 @@ export class StorageManager {
5949
6370
  try {
5950
6371
  const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
5951
6372
  await this.writeStorageSecureFile(memory.path, fileContent);
6373
+ // Patch the hot corpus cache entry in place with the new accessCount/
6374
+ // lastAccessed (issue #1902). The shared sentinel is bumped once after
6375
+ // the loop for cross-process coherence; the re-key below keeps this
6376
+ // process's patched entries warm.
6377
+ if (warm) {
6378
+ updateCacheOnWrite(this.baseDir, { ...memory, frontmatter: newFm }, keyId);
6379
+ }
6380
+ appliedPatches.set(entry.memoryId, { accessCount: entry.newCount, lastAccessed: entry.lastAccessed });
5952
6381
  updated++;
5953
6382
  } catch (err) {
5954
6383
  log.debug(`failed to update access tracking for ${entry.memoryId}: ${err}`);
@@ -5956,6 +6385,45 @@ export class StorageManager {
5956
6385
  }
5957
6386
 
5958
6387
  if (updated > 0) {
6388
+ // Advance the corpus sentinel so PEER processes rescan and don't overwrite
6389
+ // this process's increments (Codex P2): WorkspaceOpsCoordinator computes
6390
+ // existingCount + update.count from the cached value, so a peer serving a
6391
+ // stale count would undercount. Re-key the locally patched entries to the
6392
+ // produced version so this process stays warm — only when our bump was
6393
+ // exclusive and still the current sentinel; otherwise a peer also wrote
6394
+ // and we must let the next read rescan.
6395
+ const { produced, exclusive } = this.bumpMemoryCorpusVersionExclusive();
6396
+ // Drop the in-flight read slot after the bump (parity with
6397
+ // patchHotMemoriesCache, Cursor Medium #1902): a readAllMemories scan that
6398
+ // started before the flush would otherwise keep awaiting a pre-flush scan
6399
+ // and could republish it at the old version, clobbering our patches. New
6400
+ // readers now start a fresh scan at the bumped version.
6401
+ deleteInFlightReadsForDir(this.baseDir);
6402
+ if (warm && exclusive && produced === prevVersion + 1 && this.getMemoryCorpusVersion() === produced && this.hotCacheKeyId() === keyId) {
6403
+ const cur = getCachedMemories(this.baseDir, prevVersion, keyId);
6404
+ if (cur) {
6405
+ // Re-apply our patches on top of whatever is cached at prevVersion
6406
+ // before re-keying (Cursor Medium #1902): a concurrent readAllMemories
6407
+ // that finished in the prevVersion epoch may have republished a full
6408
+ // UNpatched corpus via its publish guard, clobbering our in-loop
6409
+ // updateCacheOnWrite patches. Re-applying is idempotent and makes the
6410
+ // re-key robust to that race; the publish guard blocks any scan
6411
+ // finishing AFTER our bump, so this is the only window.
6412
+ const reapplied = cur.map((m) => {
6413
+ const patch = appliedPatches.get(m.frontmatter.id);
6414
+ return patch
6415
+ ? { ...m, frontmatter: { ...m.frontmatter, accessCount: patch.accessCount, lastAccessed: patch.lastAccessed } }
6416
+ : m;
6417
+ });
6418
+ setCachedMemories(this.baseDir, reapplied, produced, keyId, this.hotCacheTtlMs());
6419
+ }
6420
+ }
6421
+ // Evict the derived (episode/rule) + global (QMD recall/search) layers,
6422
+ // mirroring patchHotMemoriesCache (Cursor Medium, #1902). The hot layer is
6423
+ // patched + re-keyed above; without this the derived caches linger at
6424
+ // prevVersion and the QMD recall bundle keeps pre-flush accessCount-based
6425
+ // boost ordering until an unrelated mutation invalidates them.
6426
+ invalidateDerivedAndGlobalForDir(this.baseDir);
5959
6427
  log.debug(`flushed access tracking for ${updated} memories`);
5960
6428
  }
5961
6429
  return updated;
@@ -6152,6 +6620,9 @@ export class StorageManager {
6152
6620
  const filePath = await this.resolveCategoryWritePath(category, id, today);
6153
6621
 
6154
6622
  await this.writeStorageSecureFile(filePath, fileContent);
6623
+ // Keep the version-keyed hot-memories cache coherent with the new chunk
6624
+ // file (issue #1902) — same single-file patch path writeMemory uses.
6625
+ await this.patchHotMemoriesCache({ addedPath: filePath });
6155
6626
  log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
6156
6627
  return id;
6157
6628
  }
@@ -6193,6 +6664,12 @@ export class StorageManager {
6193
6664
 
6194
6665
  try {
6195
6666
  await this.writeStorageSecureFile(oldMemory.path, fileContent);
6667
+ // Advance the corpus sentinel immediately after the on-disk write, BEFORE
6668
+ // the awaited lifecycle append (Cursor Medium, #1902). Otherwise a warm
6669
+ // hot-memories cache keeps serving the pre-supersede snapshot during the
6670
+ // await window; bumpMemoryStatusVersion() below additionally invalidates
6671
+ // the entity cache for the status change.
6672
+ this.bumpMemoryCorpusVersion();
6196
6673
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
6197
6674
  memoryId: oldMemoryId,
6198
6675
  eventType: "superseded",
@@ -6312,6 +6789,11 @@ export class StorageManager {
6312
6789
  try {
6313
6790
  const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
6314
6791
  await this.writeStorageSecureFile(memory.path, fileContent);
6792
+ // Corpus sentinel bump per file write, BEFORE the awaited lifecycle
6793
+ // append (Cursor Medium, #1902): the end-of-loop bumpMemoryStatusVersion
6794
+ // fires only after the whole batch, so without this a warm cache serves
6795
+ // pre-archive state for every file during the loop's await windows.
6796
+ this.bumpMemoryCorpusVersion();
6315
6797
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
6316
6798
  memoryId: id,
6317
6799
  eventType: "archived",