@remnic/core 9.3.742 → 9.3.744

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 (262) hide show
  1. package/dist/access-boundary.d.ts +6 -6
  2. package/dist/access-boundary.js +10 -10
  3. package/dist/access-cli.js +29 -29
  4. package/dist/access-http.d.ts +6 -6
  5. package/dist/access-http.js +15 -15
  6. package/dist/access-mcp.d.ts +6 -6
  7. package/dist/access-mcp.js +14 -14
  8. package/dist/access-operations-batch.js +12 -12
  9. package/dist/access-operations.d.ts +6 -6
  10. package/dist/access-operations.js +13 -13
  11. package/dist/{access-service-K8n12dXu.d.ts → access-service-OT3Gk79W.d.ts} +3 -3
  12. package/dist/access-service.d.ts +6 -6
  13. package/dist/access-service.js +9 -9
  14. package/dist/access-surface-catalog.d.ts +6 -6
  15. package/dist/action-confidence.d.ts +1 -1
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-memory-bridge.js +3 -3
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/behavior-learner.d.ts +1 -1
  20. package/dist/behavior-signals.d.ts +1 -1
  21. package/dist/bootstrap.d.ts +5 -4
  22. package/dist/briefing.d.ts +1 -1
  23. package/dist/buffer-surprise-report.d.ts +1 -1
  24. package/dist/buffer.d.ts +1 -1
  25. package/dist/buffer.js +2 -2
  26. package/dist/calibration.d.ts +1 -1
  27. package/dist/capabilities.d.ts +10 -5
  28. package/dist/capabilities.js +1 -1
  29. package/dist/{catalog-_Bap6vfD.d.ts → catalog-CX0CjOaf.d.ts} +1 -1
  30. package/dist/causal-behavior.d.ts +1 -1
  31. package/dist/causal-consolidation.d.ts +1 -1
  32. package/dist/causal-consolidation.js +2 -2
  33. package/dist/causal-trajectory-graph.d.ts +1 -1
  34. package/dist/{chunk-X5PMDVY4.js → chunk-3EA5LIFJ.js} +4 -4
  35. package/dist/{chunk-YNHRWFAP.js → chunk-4DLFJJOQ.js} +2 -2
  36. package/dist/{chunk-WIMKOTNY.js → chunk-5QANTTY2.js} +2 -2
  37. package/dist/{chunk-AM7YKD7V.js → chunk-5UNSGRCR.js} +5 -5
  38. package/dist/{chunk-Y6PNYJVO.js → chunk-A2KKLCOS.js} +2 -2
  39. package/dist/{chunk-O4IWVWSW.js → chunk-AVBT5WEW.js} +3 -3
  40. package/dist/{chunk-QUNH5UZH.js → chunk-BYLC4DKI.js} +2 -2
  41. package/dist/{chunk-UOVDO6TC.js → chunk-CHELXDTO.js} +3 -3
  42. package/dist/{chunk-TQQN64J2.js → chunk-DBSMLRTO.js} +2 -2
  43. package/dist/{chunk-EZZUNKMJ.js → chunk-DYK27X7V.js} +3 -3
  44. package/dist/{chunk-K2N6WIZM.js → chunk-E7RGJQWP.js} +2 -2
  45. package/dist/{chunk-M4WI23X3.js → chunk-EEKDRAWX.js} +2 -2
  46. package/dist/{chunk-GFJ5ABKS.js → chunk-G5FP7VSQ.js} +11 -11
  47. package/dist/{chunk-BE6NEQLF.js → chunk-HAZVVOYT.js} +3 -3
  48. package/dist/{chunk-OT5K26NG.js → chunk-HM6UJPPO.js} +2 -2
  49. package/dist/{chunk-PI4JHLDD.js → chunk-IP5SANO2.js} +2 -2
  50. package/dist/{chunk-WHEF76BQ.js → chunk-JRZV3767.js} +2 -2
  51. package/dist/{chunk-OS5RKJOH.js → chunk-JZ7P26GF.js} +2 -2
  52. package/dist/{chunk-Y62QPRKV.js → chunk-K5LLP4CY.js} +2 -2
  53. package/dist/{chunk-C3Z5YJSM.js → chunk-LOPZ2DWR.js} +2 -2
  54. package/dist/{chunk-BJPL5WAL.js → chunk-MXDG6SRH.js} +3 -3
  55. package/dist/{chunk-UYYSOMS2.js → chunk-NBP3YHHS.js} +4 -4
  56. package/dist/{chunk-USHVHI6J.js → chunk-NNL4LJAD.js} +3 -3
  57. package/dist/{chunk-GPX5E36D.js → chunk-OSMBC3JU.js} +2 -2
  58. package/dist/{chunk-OUSOMUFV.js → chunk-PE6XR4GG.js} +2 -2
  59. package/dist/{chunk-CMPXLMC3.js → chunk-PI23H7FV.js} +3 -3
  60. package/dist/{chunk-WGDE2TJP.js → chunk-QOGXUNWL.js} +4 -4
  61. package/dist/{chunk-ELL5VYXC.js → chunk-RNWWAS5V.js} +3 -3
  62. package/dist/{chunk-7HUGLMSW.js → chunk-SODQQCAZ.js} +1013 -821
  63. package/dist/chunk-SODQQCAZ.js.map +1 -0
  64. package/dist/{chunk-JMMSVRF4.js → chunk-TQPSOTC7.js} +7 -7
  65. package/dist/{chunk-AX4TRSRE.js → chunk-U5WVRMCA.js} +2 -2
  66. package/dist/{chunk-7ADHJ6SU.js → chunk-VE54E6RP.js} +4 -4
  67. package/dist/{chunk-G7Q4D2G3.js → chunk-VSF7AAGE.js} +1 -3
  68. package/dist/chunk-VSF7AAGE.js.map +1 -0
  69. package/dist/{chunk-OVYBENIJ.js → chunk-W3M34XT4.js} +3 -3
  70. package/dist/{chunk-NHJYUIII.js → chunk-WPLRJ4XM.js} +7 -7
  71. package/dist/{chunk-UEIF7RWQ.js → chunk-XEADGIAA.js} +6 -6
  72. package/dist/{cli-BEOLy1xo.d.ts → cli-BQiTZ7Zs.d.ts} +3 -3
  73. package/dist/cli.d.ts +6 -6
  74. package/dist/cli.js +21 -21
  75. package/dist/compounding/engine.d.ts +1 -1
  76. package/dist/compounding/engine.js +3 -3
  77. package/dist/compounding/preference-consolidator.d.ts +1 -1
  78. package/dist/compression-optimizer.d.ts +1 -1
  79. package/dist/config.d.ts +1 -1
  80. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  81. package/dist/connectors/codex-materialize.d.ts +1 -1
  82. package/dist/connectors/index.d.ts +1 -1
  83. package/dist/connectors/index.js +2 -2
  84. package/dist/consolidation-provenance-check.d.ts +1 -1
  85. package/dist/consolidation-undo.d.ts +1 -1
  86. package/dist/contradiction/index.d.ts +1 -1
  87. package/dist/contradiction/index.js +2 -2
  88. package/dist/{contradiction-scan-UERKJESC.js → contradiction-scan-3FJYWS3G.js} +3 -3
  89. package/dist/conversation-index/backend.d.ts +1 -1
  90. package/dist/conversation-index/chunker.d.ts +1 -1
  91. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  92. package/dist/conversation-index/indexer.d.ts +1 -1
  93. package/dist/conversation-index/search.d.ts +1 -1
  94. package/dist/day-summary.d.ts +1 -1
  95. package/dist/day-summary.js +2 -2
  96. package/dist/delinearize.d.ts +1 -1
  97. package/dist/direct-answer-wiring.d.ts +1 -1
  98. package/dist/direct-answer.d.ts +1 -1
  99. package/dist/embedding-fallback.d.ts +1 -1
  100. package/dist/embedding-fallback.js +2 -2
  101. package/dist/enrichment/index.d.ts +1 -1
  102. package/dist/entity-retrieval.d.ts +1 -1
  103. package/dist/entity-retrieval.js +2 -2
  104. package/dist/entity-schema.d.ts +1 -1
  105. package/dist/explicit-capture.d.ts +5 -4
  106. package/dist/explicit-capture.js +2 -2
  107. package/dist/extraction-faithfulness.d.ts +1 -1
  108. package/dist/extraction-judge-telemetry.d.ts +1 -1
  109. package/dist/extraction-judge-training.d.ts +1 -1
  110. package/dist/extraction-judge.d.ts +1 -1
  111. package/dist/extraction.d.ts +1 -1
  112. package/dist/extraction.js +4 -4
  113. package/dist/fallback-llm.d.ts +1 -1
  114. package/dist/{first-start-migration-GQ3UQNIL.js → first-start-migration-4I3VDSYS.js} +2 -2
  115. package/dist/graph-dashboard-diff.d.ts +1 -1
  116. package/dist/graph-dashboard-key.d.ts +1 -1
  117. package/dist/graph-dashboard-parser.d.ts +1 -1
  118. package/dist/graph-edge-reinforcement.d.ts +1 -1
  119. package/dist/graph-snapshot.d.ts +1 -1
  120. package/dist/graph.d.ts +1 -1
  121. package/dist/identity-continuity.d.ts +1 -1
  122. package/dist/importance.d.ts +1 -1
  123. package/dist/index.d.ts +10 -10
  124. package/dist/index.js +36 -36
  125. package/dist/intent.d.ts +1 -1
  126. package/dist/lcm/engine.d.ts +1 -1
  127. package/dist/lcm/index.d.ts +1 -1
  128. package/dist/lcm/tools.d.ts +1 -1
  129. package/dist/lifecycle.d.ts +1 -1
  130. package/dist/live-connectors-runner.d.ts +1 -1
  131. package/dist/local-llm.d.ts +1 -1
  132. package/dist/local-llm.js +2 -2
  133. package/dist/local-model-endpoint.d.ts +1 -1
  134. package/dist/maintenance/memory-governance.d.ts +1 -1
  135. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  136. package/dist/memory-action-policy.d.ts +1 -1
  137. package/dist/memory-cache.d.ts +1 -1
  138. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  139. package/dist/memory-projection-store.d.ts +1 -1
  140. package/dist/memory-provenance.d.ts +1 -1
  141. package/dist/memory-worth-outcomes.d.ts +1 -1
  142. package/dist/models-json.d.ts +1 -1
  143. package/dist/namespaces/migrate.d.ts +2 -2
  144. package/dist/namespaces/migrate.js +6 -6
  145. package/dist/namespaces/principal.d.ts +1 -1
  146. package/dist/namespaces/principal.js +2 -2
  147. package/dist/namespaces/search.d.ts +1 -1
  148. package/dist/namespaces/search.js +4 -4
  149. package/dist/namespaces/storage.d.ts +2 -2
  150. package/dist/namespaces/storage.js +2 -2
  151. package/dist/native-knowledge.d.ts +1 -1
  152. package/dist/operator-toolkit.d.ts +1 -1
  153. package/dist/operator-toolkit.js +11 -11
  154. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  155. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  156. package/dist/orchestration/maintenance.d.ts +2 -2
  157. package/dist/orchestration/maintenance.js +6 -6
  158. package/dist/{orchestrator-Dr-1v5EL.d.ts → orchestrator-BPQGHpJz.d.ts} +263 -31
  159. package/dist/orchestrator.d.ts +5 -4
  160. package/dist/orchestrator.js +24 -24
  161. package/dist/patterns-cli.d.ts +1 -1
  162. package/dist/policy-runtime.d.ts +1 -1
  163. package/dist/provenance.d.ts +1 -1
  164. package/dist/qmd-recall-cache.d.ts +1 -1
  165. package/dist/qmd.d.ts +1 -1
  166. package/dist/recall-disclosure-escalation.d.ts +1 -1
  167. package/dist/recall-explain-renderer.d.ts +1 -1
  168. package/dist/recall-planner-llm.d.ts +1 -1
  169. package/dist/recall-state.d.ts +1 -1
  170. package/dist/recall-tag-filter.d.ts +1 -1
  171. package/dist/recall-xray-cli.d.ts +1 -1
  172. package/dist/recall-xray-renderer.d.ts +1 -1
  173. package/dist/recall-xray.d.ts +1 -1
  174. package/dist/resolve-auth-token.d.ts +1 -1
  175. package/dist/retrieval-agents.d.ts +1 -1
  176. package/dist/retrieval-tiers.d.ts +1 -1
  177. package/dist/routing/engine.d.ts +1 -1
  178. package/dist/routing/store.d.ts +1 -1
  179. package/dist/search/embed-helper.d.ts +1 -1
  180. package/dist/search/embed-helper.js +2 -2
  181. package/dist/search/factory.d.ts +1 -1
  182. package/dist/search/factory.js +3 -3
  183. package/dist/search/index.d.ts +1 -1
  184. package/dist/search/index.js +3 -3
  185. package/dist/search/lancedb-backend.d.ts +1 -1
  186. package/dist/search/meilisearch-backend.d.ts +1 -1
  187. package/dist/search/noop-backend.d.ts +1 -1
  188. package/dist/search/orama-backend.d.ts +1 -1
  189. package/dist/search/port.d.ts +1 -1
  190. package/dist/search/remote-backend.d.ts +1 -1
  191. package/dist/{semantic-consolidation-DsIEiCka.d.ts → semantic-consolidation-ByzGwC9c.d.ts} +1 -1
  192. package/dist/semantic-consolidation.d.ts +2 -2
  193. package/dist/semantic-consolidation.js +2 -2
  194. package/dist/semantic-rule-verifier.d.ts +1 -1
  195. package/dist/session-observer-bands.d.ts +1 -1
  196. package/dist/session-observer-state.d.ts +1 -1
  197. package/dist/shared-context/manager.d.ts +1 -1
  198. package/dist/shared-context/manager.js +2 -2
  199. package/dist/signal.d.ts +1 -1
  200. package/dist/storage.d.ts +1 -1
  201. package/dist/summarizer.d.ts +1 -1
  202. package/dist/summarizer.js +3 -3
  203. package/dist/summary-snapshot.d.ts +1 -1
  204. package/dist/temporal-supersession.d.ts +1 -1
  205. package/dist/temporal-validity.d.ts +1 -1
  206. package/dist/threading.d.ts +1 -1
  207. package/dist/tier-migration.d.ts +1 -1
  208. package/dist/tier-routing.d.ts +1 -1
  209. package/dist/{tier-stats-CSKL7BDG.js → tier-stats-5XF4UBRQ.js} +2 -2
  210. package/dist/topics.d.ts +1 -1
  211. package/dist/transcript.d.ts +1 -1
  212. package/dist/trust-score-stage.d.ts +1 -1
  213. package/dist/trust-score.d.ts +1 -1
  214. package/dist/{types-CglNn2V-.d.ts → types-5Jik3r4G.d.ts} +1 -1
  215. package/dist/types.d.ts +1 -1
  216. package/dist/utility-runtime.d.ts +1 -1
  217. package/package.json +2 -2
  218. package/src/capabilities.test.ts +0 -2
  219. package/src/capabilities.ts +7 -6
  220. package/src/orchestration/contradiction-linking-coordinator.ts +336 -0
  221. package/src/orchestration/graph-recall-coordinator.ts +471 -0
  222. package/src/orchestration/recall-section-coordinator.ts +349 -0
  223. package/src/orchestrator.ts +97 -741
  224. package/dist/chunk-7HUGLMSW.js.map +0 -1
  225. package/dist/chunk-G7Q4D2G3.js.map +0 -1
  226. /package/dist/{chunk-X5PMDVY4.js.map → chunk-3EA5LIFJ.js.map} +0 -0
  227. /package/dist/{chunk-YNHRWFAP.js.map → chunk-4DLFJJOQ.js.map} +0 -0
  228. /package/dist/{chunk-WIMKOTNY.js.map → chunk-5QANTTY2.js.map} +0 -0
  229. /package/dist/{chunk-AM7YKD7V.js.map → chunk-5UNSGRCR.js.map} +0 -0
  230. /package/dist/{chunk-Y6PNYJVO.js.map → chunk-A2KKLCOS.js.map} +0 -0
  231. /package/dist/{chunk-O4IWVWSW.js.map → chunk-AVBT5WEW.js.map} +0 -0
  232. /package/dist/{chunk-QUNH5UZH.js.map → chunk-BYLC4DKI.js.map} +0 -0
  233. /package/dist/{chunk-UOVDO6TC.js.map → chunk-CHELXDTO.js.map} +0 -0
  234. /package/dist/{chunk-TQQN64J2.js.map → chunk-DBSMLRTO.js.map} +0 -0
  235. /package/dist/{chunk-EZZUNKMJ.js.map → chunk-DYK27X7V.js.map} +0 -0
  236. /package/dist/{chunk-K2N6WIZM.js.map → chunk-E7RGJQWP.js.map} +0 -0
  237. /package/dist/{chunk-M4WI23X3.js.map → chunk-EEKDRAWX.js.map} +0 -0
  238. /package/dist/{chunk-GFJ5ABKS.js.map → chunk-G5FP7VSQ.js.map} +0 -0
  239. /package/dist/{chunk-BE6NEQLF.js.map → chunk-HAZVVOYT.js.map} +0 -0
  240. /package/dist/{chunk-OT5K26NG.js.map → chunk-HM6UJPPO.js.map} +0 -0
  241. /package/dist/{chunk-PI4JHLDD.js.map → chunk-IP5SANO2.js.map} +0 -0
  242. /package/dist/{chunk-WHEF76BQ.js.map → chunk-JRZV3767.js.map} +0 -0
  243. /package/dist/{chunk-OS5RKJOH.js.map → chunk-JZ7P26GF.js.map} +0 -0
  244. /package/dist/{chunk-Y62QPRKV.js.map → chunk-K5LLP4CY.js.map} +0 -0
  245. /package/dist/{chunk-C3Z5YJSM.js.map → chunk-LOPZ2DWR.js.map} +0 -0
  246. /package/dist/{chunk-BJPL5WAL.js.map → chunk-MXDG6SRH.js.map} +0 -0
  247. /package/dist/{chunk-UYYSOMS2.js.map → chunk-NBP3YHHS.js.map} +0 -0
  248. /package/dist/{chunk-USHVHI6J.js.map → chunk-NNL4LJAD.js.map} +0 -0
  249. /package/dist/{chunk-GPX5E36D.js.map → chunk-OSMBC3JU.js.map} +0 -0
  250. /package/dist/{chunk-OUSOMUFV.js.map → chunk-PE6XR4GG.js.map} +0 -0
  251. /package/dist/{chunk-CMPXLMC3.js.map → chunk-PI23H7FV.js.map} +0 -0
  252. /package/dist/{chunk-WGDE2TJP.js.map → chunk-QOGXUNWL.js.map} +0 -0
  253. /package/dist/{chunk-ELL5VYXC.js.map → chunk-RNWWAS5V.js.map} +0 -0
  254. /package/dist/{chunk-JMMSVRF4.js.map → chunk-TQPSOTC7.js.map} +0 -0
  255. /package/dist/{chunk-AX4TRSRE.js.map → chunk-U5WVRMCA.js.map} +0 -0
  256. /package/dist/{chunk-7ADHJ6SU.js.map → chunk-VE54E6RP.js.map} +0 -0
  257. /package/dist/{chunk-OVYBENIJ.js.map → chunk-W3M34XT4.js.map} +0 -0
  258. /package/dist/{chunk-NHJYUIII.js.map → chunk-WPLRJ4XM.js.map} +0 -0
  259. /package/dist/{chunk-UEIF7RWQ.js.map → chunk-XEADGIAA.js.map} +0 -0
  260. /package/dist/{contradiction-scan-UERKJESC.js.map → contradiction-scan-3FJYWS3G.js.map} +0 -0
  261. /package/dist/{first-start-migration-GQ3UQNIL.js.map → first-start-migration-4I3VDSYS.js.map} +0 -0
  262. /package/dist/{tier-stats-CSKL7BDG.js.map → tier-stats-5XF4UBRQ.js.map} +0 -0
@@ -104,8 +104,20 @@ import { EntitySynthesisCoordinator } from "./orchestration/entity-synthesis-coo
104
104
  import { RecallResultFormatter } from "./orchestration/recall-result-formatter.js";
105
105
  import { ConversationIndexCoordinator } from "./orchestration/conversation-index-coordinator.js";
106
106
  import { RecallRerankCoordinator } from "./orchestration/recall-rerank-coordinator.js";
107
+ import { RecallSectionCoordinator } from "./orchestration/recall-section-coordinator.js";
107
108
  import { QmdResultResolver, qmdCollectionPathParts, qmdResultPathCandidates } from "./orchestration/qmd-result-resolver.js";
109
+ import { ContradictionLinkingCoordinator } from "./orchestration/contradiction-linking-coordinator.js";
108
110
  export { hasIdentityRecoveryIntent, resolveEffectiveIdentityInjectionMode } from "./orchestration/recall-result-formatter.js";
111
+ import {
112
+ GraphRecallCoordinator,
113
+ mergeGraphExpandedResults,
114
+ graphPathRelativeToStorage,
115
+ blendGraphExpandedRecallScore,
116
+ type GraphRecallRankedResult,
117
+ type GraphRecallShadowComparison,
118
+ } from "./orchestration/graph-recall-coordinator.js";
119
+ export { mergeGraphExpandedResults, graphPathRelativeToStorage, blendGraphExpandedRecallScore } from "./orchestration/graph-recall-coordinator.js";
120
+ export type { GraphRecallRankedResult, GraphRecallShadowComparison } from "./orchestration/graph-recall-coordinator.js";
109
121
  import {
110
122
  runLiveConnectorsOnce,
111
123
  type LiveConnectorsRunSummary,
@@ -504,21 +516,6 @@ export interface GraphRecallSnapshot {
504
516
  shadowComparison?: GraphRecallShadowComparison;
505
517
  }
506
518
 
507
- export interface GraphRecallRankedResult {
508
- path: string;
509
- score: number;
510
- docid?: string;
511
- sourceLabels: string[];
512
- }
513
-
514
- export interface GraphRecallShadowComparison {
515
- baselineCount: number;
516
- graphCount: number;
517
- overlapCount: number;
518
- overlapRatio: number;
519
- averageOverlapDelta: number;
520
- }
521
-
522
519
  export interface IntentDebugSnapshot {
523
520
  recordedAt: string;
524
521
  promptHash: string;
@@ -1341,63 +1338,6 @@ export function computeQmdHybridFetchLimit(
1341
1338
  const artifactHeadroom = Math.max(20, Math.max(0, maxArtifactRecall) * 8);
1342
1339
  return Math.min(400, cappedRecallLimit + artifactHeadroom);
1343
1340
  }
1344
-
1345
- export function mergeGraphExpandedResults(
1346
- primary: QmdSearchResult[],
1347
- expanded: QmdSearchResult[],
1348
- ): QmdSearchResult[] {
1349
- const mergedByPath = new Map<string, QmdSearchResult>();
1350
- for (const item of [...primary, ...expanded]) {
1351
- const prev = mergedByPath.get(item.path);
1352
- if (!prev) {
1353
- mergedByPath.set(item.path, item);
1354
- continue;
1355
- }
1356
- const better = item.score > prev.score ? item : prev;
1357
- const snippet = prev.snippet || item.snippet;
1358
- mergedByPath.set(item.path, { ...better, snippet });
1359
- }
1360
- return Array.from(mergedByPath.values());
1361
- }
1362
-
1363
- export function graphPathRelativeToStorage(
1364
- storageDir: string,
1365
- candidatePath: string,
1366
- ): string | null {
1367
- const absolutePath = path.isAbsolute(candidatePath)
1368
- ? candidatePath
1369
- : path.resolve(storageDir, candidatePath);
1370
- const rel = path.relative(storageDir, absolutePath);
1371
- if (!rel || rel === ".") return null;
1372
- if (rel.startsWith("..")) return null;
1373
- return rel.split(path.sep).join("/");
1374
- }
1375
-
1376
- function normalizeGraphActivationScore(score: number): number {
1377
- const bounded = Number.isFinite(score) && score > 0 ? score : 0;
1378
- return bounded / (1 + bounded);
1379
- }
1380
-
1381
- export function blendGraphExpandedRecallScore(options: {
1382
- graphActivationScore: number;
1383
- seedRecallScore: number;
1384
- activationWeight: number;
1385
- blendMin: number;
1386
- blendMax: number;
1387
- }): number {
1388
- const graphNorm = normalizeGraphActivationScore(options.graphActivationScore);
1389
- const seedScore = Number.isFinite(options.seedRecallScore)
1390
- ? Math.min(1, Math.max(0, options.seedRecallScore))
1391
- : 0;
1392
- const weight = Math.min(1, Math.max(0, options.activationWeight));
1393
- const rawMin = Math.min(1, Math.max(0, options.blendMin));
1394
- const rawMax = Math.min(1, Math.max(0, options.blendMax));
1395
- const minBound = Math.min(rawMin, rawMax);
1396
- const maxBound = Math.max(rawMin, rawMax);
1397
- const blended = graphNorm * weight + seedScore * (1 - weight);
1398
- return Math.max(minBound, Math.min(maxBound, blended));
1399
- }
1400
-
1401
1341
  export function summarizeGraphShadowComparison(
1402
1342
  baseline: QmdSearchResult[],
1403
1343
  merged: QmdSearchResult[],
@@ -1840,7 +1780,10 @@ export class Orchestrator {
1840
1780
  */
1841
1781
  readonly conversationIndexCoordinator: ConversationIndexCoordinator;
1842
1782
  readonly recallRerankCoordinator: RecallRerankCoordinator;
1783
+ readonly recallSectionCoordinator: RecallSectionCoordinator;
1843
1784
  readonly qmdResultResolver: QmdResultResolver;
1785
+ readonly contradictionLinkingCoordinator: ContradictionLinkingCoordinator;
1786
+ readonly graphRecallCoordinator: GraphRecallCoordinator;
1844
1787
  private heartbeatObserverChains = new Map<string, Promise<void>>();
1845
1788
  private recentExtractionFingerprints = new Map<string, number>();
1846
1789
  private readonly consolidationObservers = new Set<
@@ -2841,7 +2784,33 @@ export class Orchestrator {
2841
2784
  readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) =>
2842
2785
  this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces),
2843
2786
  });
2787
+ this.recallSectionCoordinator = new RecallSectionCoordinator({
2788
+ getConfig: () => this.config,
2789
+ resolveSectionEnabled: (id, def) => this.isRecallSectionEnabled(id, def),
2790
+ });
2791
+ this.contradictionLinkingCoordinator = new ContradictionLinkingCoordinator({
2792
+ getConfig: () => this.config,
2793
+ isSearchAvailable: () => this.isSearchAvailableForNamespaceRouting(),
2794
+ searchAcrossNamespaces: (options) => this.searchAcrossNamespaces(options),
2795
+ extractMemoryIdsFromResults: (results) => this.extractMemoryIdsFromResults(results),
2796
+ namespaceFromPath: (p) => this.namespaceFromPath(p),
2797
+ storageForNamespace: (namespace) => this.storageRouter.storageFor(namespace),
2798
+ getExtraction: () => this.extraction,
2799
+ });
2844
2800
  this.modelRegistry = new ModelRegistry(config.memoryDir);
2801
+ this.graphRecallCoordinator = new GraphRecallCoordinator({
2802
+ getConfig: () => this.config,
2803
+ getStorage: () => this.storage,
2804
+ storageFor: (namespace) => this.storageRouter.storageFor(namespace),
2805
+ graphIndexFor: (storage) => this.graphIndexFor(storage),
2806
+ namespaceFromPath: (p) => this.namespaceFromPath(p),
2807
+ resolveColdQmdResultForRecall: (result, fallbackStorage, recallNamespaces) =>
2808
+ this.resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces),
2809
+ storageForAbsoluteQmdResultPath: (resultPath, fallbackStorage, recallNamespaces) =>
2810
+ this.storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces),
2811
+ readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) =>
2812
+ this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces),
2813
+ });
2845
2814
  this.relevance = new RelevanceStore(config.memoryDir);
2846
2815
  this.negatives = new NegativeExampleStore(config.memoryDir);
2847
2816
  this.lastRecall = new LastRecallStore(config.memoryDir);
@@ -6018,12 +5987,14 @@ export class Orchestrator {
6018
5987
  return capped;
6019
5988
  }
6020
5989
 
5990
+ // Issue #1526 (seam 14): graph-recall expansion moved to
5991
+ // GraphRecallCoordinator. Thin delegation keeps the private API stable
5992
+ // for callers (recallInternal, cold-fallback pipeline) + tests.
6021
5993
  private async expandResultsViaGraph(options: {
6022
5994
  memoryResults: QmdSearchResult[];
6023
5995
  recallNamespaces: string[];
6024
5996
  recallResultLimit: number;
6025
5997
  deadlineAtMs?: number | null;
6026
- /** Issue #681 — when true, bypass graphTraversalConfidenceFloor. */
6027
5998
  includeLowConfidence?: boolean;
6028
5999
  }): Promise<{
6029
6000
  merged: QmdSearchResult[];
@@ -6031,198 +6002,10 @@ export class Orchestrator {
6031
6002
  expandedPaths: GraphRecallExpandedEntry[];
6032
6003
  seedResults: QmdSearchResult[];
6033
6004
  }> {
6034
- const deadlineExpired = (): boolean =>
6035
- typeof options.deadlineAtMs === "number" &&
6036
- Date.now() >= options.deadlineAtMs;
6037
- const byNamespace = new Map<string, QmdSearchResult[]>();
6038
- const addResultForNamespace = (
6039
- namespace: string,
6040
- result: QmdSearchResult,
6041
- ): void => {
6042
- const existing = byNamespace.get(namespace);
6043
- if (existing) {
6044
- existing.push(result);
6045
- } else {
6046
- byNamespace.set(namespace, [result]);
6047
- }
6048
- };
6049
- const resolvedAmbiguousSeeds = new Map<
6050
- string,
6051
- { namespace: string; result: QmdSearchResult } | null
6052
- >();
6053
- const resolveAmbiguousSeedOwner = async (
6054
- result: QmdSearchResult,
6055
- parts: { collection: string; relativePath: string } | null,
6056
- ): Promise<{ namespace: string; result: QmdSearchResult } | null> => {
6057
- const cached = resolvedAmbiguousSeeds.get(result.path);
6058
- if (cached !== undefined) return cached;
6059
- if (deadlineExpired()) {
6060
- resolvedAmbiguousSeeds.set(result.path, null);
6061
- return null;
6062
- }
6063
-
6064
- let resolvedPath = result.path;
6065
- let resolvedResult = result;
6066
- if (parts) {
6067
- const resolvedCold = await this.resolveColdQmdResultForRecall(
6068
- result,
6069
- this.storage,
6070
- options.recallNamespaces,
6071
- );
6072
- if (!resolvedCold || deadlineExpired()) {
6073
- resolvedAmbiguousSeeds.set(result.path, null);
6074
- return null;
6075
- }
6076
- resolvedPath = resolvedCold.result.path;
6077
- resolvedResult = resolvedCold.result;
6078
- }
6079
-
6080
- if (!path.isAbsolute(resolvedPath)) {
6081
- resolvedAmbiguousSeeds.set(result.path, null);
6082
- return null;
6083
- }
6084
- const ownerStorage = await this.storageForAbsoluteQmdResultPath(
6085
- resolvedPath,
6086
- this.storage,
6087
- options.recallNamespaces,
6088
- );
6089
- const ownerNamespace = ownerStorage?.namespace ?? null;
6090
- const resolved =
6091
- ownerNamespace && options.recallNamespaces.includes(ownerNamespace)
6092
- ? { namespace: ownerNamespace, result: resolvedResult }
6093
- : null;
6094
- resolvedAmbiguousSeeds.set(result.path, resolved);
6095
- return resolved;
6096
- };
6097
- const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
6098
- for (const result of options.memoryResults) {
6099
- if (deadlineExpired()) break;
6100
- const parts = qmdCollectionPathParts(result.path);
6101
- if (parts?.collection === coldCollection) {
6102
- const resolved = await resolveAmbiguousSeedOwner(result, parts);
6103
- if (resolved) {
6104
- addResultForNamespace(resolved.namespace, resolved.result);
6105
- }
6106
- continue;
6107
- }
6108
- if (path.isAbsolute(result.path)) {
6109
- const resolved = await resolveAmbiguousSeedOwner(result, null);
6110
- if (resolved) {
6111
- addResultForNamespace(resolved.namespace, resolved.result);
6112
- }
6113
- continue;
6114
- }
6115
- const ns = this.namespaceFromPath(result.path);
6116
- if (!options.recallNamespaces.includes(ns)) continue;
6117
- addResultForNamespace(ns, result);
6118
- }
6119
-
6120
- const perNamespaceSeedCap = Math.max(3, options.recallResultLimit);
6121
- const perNamespaceExpandedCap = Math.max(8, options.recallResultLimit * 2);
6122
- const seedPaths: string[] = [];
6123
- const seedResults: QmdSearchResult[] = [];
6124
- const expandedPaths: GraphRecallExpandedEntry[] = [];
6125
- const expandedResults: QmdSearchResult[] = [];
6126
-
6127
- for (const [namespace, nsResults] of byNamespace.entries()) {
6128
- if (deadlineExpired()) break;
6129
- const storage = await this.storageRouter.storageFor(namespace);
6130
- const seedCandidates = nsResults.slice(0, perNamespaceSeedCap);
6131
- seedResults.push(...seedCandidates);
6132
- const seedRelativePaths =
6133
- typeof options.deadlineAtMs === "number"
6134
- ? await this.graphSeedPathsWithinDeadline(
6135
- storage,
6136
- seedCandidates,
6137
- options.deadlineAtMs,
6138
- [namespace],
6139
- )
6140
- : (
6141
- await Promise.all(
6142
- seedCandidates.map((result) =>
6143
- this.graphSeedPathRelativeToStorage(storage, result, [
6144
- namespace,
6145
- ]),
6146
- ),
6147
- )
6148
- ).filter(
6149
- (value): value is string =>
6150
- typeof value === "string" && value.length > 0,
6151
- );
6152
- if (deadlineExpired()) break;
6153
- if (seedRelativePaths.length === 0) continue;
6154
-
6155
- const seedRecallScore = seedCandidates.reduce(
6156
- (max, item) => Math.max(max, item.score),
6157
- 0,
6158
- );
6159
- seedPaths.push(
6160
- ...seedRelativePaths.map((rel) => path.join(storage.dir, rel)),
6161
- );
6162
- const seedSet = new Set(seedRelativePaths);
6163
- const expanded = await this.graphIndexFor(storage).spreadingActivation(
6164
- seedRelativePaths,
6165
- this.config.maxGraphTraversalSteps,
6166
- {
6167
- ...(options.includeLowConfidence === true ? { includeLowConfidence: true } : {}),
6168
- ...(typeof options.deadlineAtMs === "number"
6169
- ? { deadlineAtMs: options.deadlineAtMs }
6170
- : {}),
6171
- },
6172
- );
6173
- if (expanded.length === 0) continue;
6174
- if (deadlineExpired()) break;
6175
-
6176
- for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
6177
- if (deadlineExpired()) break;
6178
- if (seedSet.has(candidate.path)) continue;
6179
- const memoryPath = path.resolve(storage.dir, candidate.path);
6180
- const memory = await storage.readMemoryByPath(memoryPath);
6181
- if (deadlineExpired()) break;
6182
- if (!memory) continue;
6183
- if (isArtifactMemoryPath(memory.path)) continue;
6184
- if (memory.frontmatter.status && memory.frontmatter.status !== "active")
6185
- continue;
6186
-
6187
- const snippet = memory.content.slice(0, 400);
6188
- const score = blendGraphExpandedRecallScore({
6189
- graphActivationScore: candidate.score,
6190
- seedRecallScore,
6191
- activationWeight: this.config.graphExpansionActivationWeight,
6192
- blendMin: this.config.graphExpansionBlendMin,
6193
- blendMax: this.config.graphExpansionBlendMax,
6194
- });
6195
- expandedResults.push({
6196
- docid: memory.frontmatter.id,
6197
- path: memory.path,
6198
- snippet,
6199
- score,
6200
- });
6201
- expandedPaths.push({
6202
- path: memory.path,
6203
- score,
6204
- namespace,
6205
- seed: path.resolve(storage.dir, candidate.seed),
6206
- hopDepth: candidate.hopDepth,
6207
- decayedWeight: candidate.decayedWeight,
6208
- graphType: candidate.graphType,
6209
- // Issue #681 PR 3/3 — surface the per-edge confidence used for
6210
- // PageRank weighting / floor pruning so downstream observability
6211
- // (recall_xray, memory_graph_explain) can attribute ranking and
6212
- // pruning decisions to specific edges.
6213
- edgeConfidence: candidate.edgeConfidence,
6214
- });
6215
- }
6216
- }
6217
-
6218
- return {
6219
- merged: mergeGraphExpandedResults(options.memoryResults, expandedResults),
6220
- seedPaths,
6221
- expandedPaths,
6222
- seedResults,
6223
- };
6005
+ return this.graphRecallCoordinator.expandResultsViaGraph(options);
6224
6006
  }
6225
-
6007
+ // Issue #1526 (seam 14): graph-recall snapshot moved to
6008
+ // GraphRecallCoordinator. Thin delegation keeps the private API stable.
6226
6009
  private async recordLastGraphRecallSnapshot(options: {
6227
6010
  storage: StorageManager;
6228
6011
  prompt: string;
@@ -6238,45 +6021,8 @@ export class Orchestrator {
6238
6021
  finalResults?: GraphRecallRankedResult[];
6239
6022
  shadowComparison?: GraphRecallShadowComparison;
6240
6023
  }): Promise<void> {
6241
- try {
6242
- const snapshotPath = path.join(
6243
- options.storage.dir,
6244
- "state",
6245
- "last_graph_recall.json",
6246
- );
6247
- await mkdir(path.dirname(snapshotPath), { recursive: true });
6248
- const now = new Date().toISOString();
6249
- const totalSeedCount = options.seedPaths.length;
6250
- const totalExpandedCount = options.expandedPaths.length;
6251
- const seeds = options.seedPaths.slice(0, 64);
6252
- const expanded = clampGraphRecallExpandedEntries(
6253
- options.expandedPaths,
6254
- 64,
6255
- );
6256
- const payload = {
6257
- recordedAt: now,
6258
- mode: options.recallMode,
6259
- queryHash: createHash("sha256").update(options.prompt).digest("hex"),
6260
- queryLength: options.prompt.length,
6261
- namespaces: options.recallNamespaces,
6262
- seedCount: totalSeedCount,
6263
- expandedCount: totalExpandedCount,
6264
- seeds,
6265
- expanded,
6266
- status: options.status,
6267
- reason: options.reason,
6268
- shadowMode: options.shadowMode === true,
6269
- queryIntent: options.queryIntent,
6270
- seedResults: (options.seedResults ?? []).slice(0, 64),
6271
- finalResults: (options.finalResults ?? []).slice(0, 64),
6272
- shadowComparison: options.shadowComparison,
6273
- };
6274
- await writeFile(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
6275
- } catch (err) {
6276
- log.debug(`last graph recall write failed: ${err}`);
6277
- }
6024
+ return this.graphRecallCoordinator.recordLastGraphRecallSnapshot(options);
6278
6025
  }
6279
-
6280
6026
  private async recordLastIntentSnapshot(options: {
6281
6027
  storage: StorageManager;
6282
6028
  snapshot: IntentDebugSnapshot;
@@ -6377,52 +6123,46 @@ export class Orchestrator {
6377
6123
  }));
6378
6124
  }
6379
6125
 
6126
+ // Issue #1526 (seam 13): recall section budgeting/assembly moved to
6127
+ // RecallSectionCoordinator. Thin delegation keeps the orchestrator's
6128
+ // recallInternal call sites stable.
6380
6129
  private getRecallSectionEntry(
6381
6130
  sectionId: string,
6382
6131
  ): RecallSectionConfig | undefined {
6383
- const pipeline = Array.isArray(this.config.recallPipeline)
6384
- ? this.config.recallPipeline
6385
- : [];
6386
- return pipeline.find((entry) => entry.id === sectionId);
6132
+ return this.recallSectionCoordinator.getRecallSectionEntry(sectionId);
6387
6133
  }
6388
6134
 
6389
6135
  private isRecallSectionEnabled(
6390
6136
  sectionId: string,
6391
6137
  defaultEnabled: boolean = true,
6392
6138
  ): boolean {
6393
- const entry = this.getRecallSectionEntry(sectionId);
6394
- if (!entry) return defaultEnabled;
6395
- return entry.enabled !== false;
6139
+ return this.recallSectionCoordinator.isRecallSectionEnabled(
6140
+ sectionId,
6141
+ defaultEnabled,
6142
+ );
6396
6143
  }
6397
6144
 
6398
6145
  private isSpecializedRecallSectionEnabled(
6399
6146
  sectionId: string,
6400
6147
  topLevelEnabled: boolean,
6401
6148
  ): boolean {
6402
- const entry = this.getRecallSectionEntry(sectionId);
6403
- if (!entry) return topLevelEnabled;
6404
- return entry.enabled === true || (topLevelEnabled && entry.enabled !== false);
6149
+ return this.recallSectionCoordinator.isSpecializedRecallSectionEnabled(
6150
+ sectionId,
6151
+ topLevelEnabled,
6152
+ );
6405
6153
  }
6406
6154
 
6407
6155
  private getRecallSectionMaxChars(
6408
6156
  sectionId: string,
6409
6157
  ): number | null | undefined {
6410
- const entry = this.getRecallSectionEntry(sectionId);
6411
- if (!entry) return undefined;
6412
- if (entry.maxChars === null) return null;
6413
- if (typeof entry.maxChars !== "number") return undefined;
6414
- return Math.max(0, Math.floor(entry.maxChars));
6158
+ return this.recallSectionCoordinator.getRecallSectionMaxChars(sectionId);
6415
6159
  }
6416
6160
 
6417
6161
  private getRecallSectionNumber(
6418
6162
  sectionId: string,
6419
6163
  key: keyof RecallSectionConfig,
6420
6164
  ): number | undefined {
6421
- const entry = this.getRecallSectionEntry(sectionId);
6422
- if (!entry) return undefined;
6423
- const value = entry[key];
6424
- if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
6425
- return Math.max(0, Math.floor(value));
6165
+ return this.recallSectionCoordinator.getRecallSectionNumber(sectionId, key);
6426
6166
  }
6427
6167
 
6428
6168
  private appendRecallSection(
@@ -6430,104 +6170,25 @@ export class Orchestrator {
6430
6170
  sectionId: string,
6431
6171
  content: string,
6432
6172
  ): boolean {
6433
- // Returns true when the section was actually appended to sectionBuckets,
6434
- // false when it was dropped (disabled, empty, or maxChars===0). Callers
6435
- // that need to know whether injection occurred (e.g. xray annotation for
6436
- // peer-profile) must gate on this return value rather than on whether the
6437
- // section text was computed (Codex P2 finding, PR #764).
6438
- if (!this.isRecallSectionEnabled(sectionId)) return false;
6439
- const trimmed = content.trim();
6440
- if (trimmed.length === 0) return false;
6441
-
6442
- const maxChars = this.getRecallSectionMaxChars(sectionId);
6443
- let finalContent = trimmed;
6444
- if (maxChars === 0) return false;
6445
- if (typeof maxChars === "number" && finalContent.length > maxChars) {
6446
- finalContent = `${finalContent.slice(0, maxChars)}\n\n...(trimmed)\n`;
6447
- }
6448
-
6449
- const existing = sectionBuckets.get(sectionId) ?? [];
6450
- existing.push(finalContent);
6451
- sectionBuckets.set(sectionId, existing);
6452
- return true;
6173
+ return this.recallSectionCoordinator.appendRecallSection(
6174
+ sectionBuckets,
6175
+ sectionId,
6176
+ content,
6177
+ );
6453
6178
  }
6454
6179
 
6455
6180
  private truncateRecallSectionToBudget(
6456
6181
  content: string,
6457
6182
  maxChars: number,
6458
6183
  ): string {
6459
- if (maxChars <= 0) return "";
6460
- if (content.length <= maxChars) return content;
6461
- const suffix = "\n\n...(memory context trimmed)";
6462
- if (maxChars <= suffix.length) {
6463
- return content.slice(0, maxChars);
6464
- }
6465
- return `${content.slice(0, maxChars - suffix.length)}${suffix}`;
6466
- }
6467
-
6468
- private protectedRecallSectionIds(
6469
- sectionBuckets: Map<string, string[]>,
6470
- ): Set<string> {
6471
- const protectedIds = new Set<string>();
6472
- if ((sectionBuckets.get("memories")?.length ?? 0) > 0) {
6473
- protectedIds.add("memories");
6474
- }
6475
- return protectedIds;
6476
- }
6477
-
6478
- private protectedRecallReservationChars(content: string): number {
6479
- const headingBoundary = content.indexOf("\n\n");
6480
- const headingChars =
6481
- headingBoundary >= 0 ? headingBoundary + 2 : Math.min(content.length, 24);
6482
- return Math.min(content.length, Math.max(headingChars, 24));
6483
- }
6484
-
6485
- private estimateReservedRecallBudget(
6486
- entries: Array<{ id: string; content: string }>,
6487
- startIndex: number,
6488
- protectedIds: Set<string>,
6489
- alreadyIncludedCount: number,
6490
- ): number {
6491
- const separatorLength = "\n\n---\n\n".length;
6492
- let reserved = 0;
6493
- let simulatedIncluded = alreadyIncludedCount;
6494
- for (let i = startIndex; i < entries.length; i += 1) {
6495
- const entry = entries[i];
6496
- if (!entry || !protectedIds.has(entry.id)) continue;
6497
- if (simulatedIncluded > 0) {
6498
- reserved += separatorLength;
6499
- }
6500
- reserved += this.protectedRecallReservationChars(entry.content);
6501
- simulatedIncluded += 1;
6502
- }
6503
- return reserved;
6184
+ return this.recallSectionCoordinator.truncateRecallSectionToBudget(
6185
+ content,
6186
+ maxChars,
6187
+ );
6504
6188
  }
6505
6189
 
6506
6190
  private getRecallBudgetChars(override?: number): number {
6507
- if (
6508
- typeof override === "number" &&
6509
- Number.isFinite(override) &&
6510
- override >= 0
6511
- ) {
6512
- return Math.floor(override);
6513
- }
6514
- const configuredBudget = this.config.recallBudgetChars;
6515
- if (
6516
- typeof configuredBudget === "number" &&
6517
- Number.isFinite(configuredBudget) &&
6518
- configuredBudget >= 0
6519
- ) {
6520
- return Math.floor(configuredBudget);
6521
- }
6522
- const tokenBudget = this.config.maxMemoryTokens;
6523
- if (
6524
- typeof tokenBudget === "number" &&
6525
- Number.isFinite(tokenBudget) &&
6526
- tokenBudget >= 0
6527
- ) {
6528
- return Math.floor(tokenBudget * 4);
6529
- }
6530
- return 0;
6191
+ return this.recallSectionCoordinator.getRecallBudgetChars(override);
6531
6192
  }
6532
6193
 
6533
6194
  private assembleRecallSections(
@@ -6540,89 +6201,13 @@ export class Orchestrator {
6540
6201
  truncated: boolean;
6541
6202
  finalChars: number;
6542
6203
  } {
6543
- const orderedEntries: Array<{ id: string; content: string }> = [];
6544
- const pipeline = Array.isArray(this.config.recallPipeline)
6545
- ? this.config.recallPipeline
6546
- : [];
6547
- const orderedIds = pipeline
6548
- .filter((entry) => entry.enabled !== false)
6549
- .map((entry) => entry.id);
6550
- const seen = new Set<string>();
6551
-
6552
- for (const id of orderedIds) {
6553
- const chunks = sectionBuckets.get(id);
6554
- if (!chunks || chunks.length === 0) continue;
6555
- orderedEntries.push({ id, content: chunks.join("\n\n") });
6556
- seen.add(id);
6557
- }
6558
-
6559
- for (const [id, chunks] of sectionBuckets.entries()) {
6560
- if (seen.has(id)) continue;
6561
- if (chunks.length === 0) continue;
6562
- orderedEntries.push({ id, content: chunks.join("\n\n") });
6563
- }
6564
-
6565
- const budget = this.getRecallBudgetChars(budgetOverride);
6566
- if (budget === 0) {
6567
- return {
6568
- sections: [],
6569
- includedIds: [],
6570
- omittedIds: orderedEntries.map((entry) => entry.id),
6571
- truncated: orderedEntries.length > 0,
6572
- finalChars: 0,
6573
- };
6574
- }
6575
-
6576
- const separator = "\n\n---\n\n";
6577
- const protectedIds = this.protectedRecallSectionIds(sectionBuckets);
6578
- const sections: string[] = [];
6579
- const includedIds: string[] = [];
6580
- const omittedIds: string[] = [];
6581
- let usedChars = 0;
6582
- let truncated = false;
6204
+ return this.recallSectionCoordinator.assembleRecallSections(
6205
+ sectionBuckets,
6206
+ budgetOverride,
6207
+ );
6208
+ }
6583
6209
 
6584
- for (let index = 0; index < orderedEntries.length; index += 1) {
6585
- const entry = orderedEntries[index]!;
6586
- const separatorChars = sections.length > 0 ? separator.length : 0;
6587
- const reserve = protectedIds.has(entry.id)
6588
- ? 0
6589
- : this.estimateReservedRecallBudget(
6590
- orderedEntries,
6591
- index + 1,
6592
- protectedIds,
6593
- sections.length + 1,
6594
- );
6595
- const availableForEntry = budget - usedChars - separatorChars - reserve;
6596
- if (availableForEntry <= 0) {
6597
- omittedIds.push(entry.id);
6598
- truncated = true;
6599
- continue;
6600
- }
6601
- const finalContent = this.truncateRecallSectionToBudget(
6602
- entry.content,
6603
- availableForEntry,
6604
- );
6605
- if (!finalContent) {
6606
- omittedIds.push(entry.id);
6607
- truncated = true;
6608
- continue;
6609
- }
6610
- if (finalContent.length < entry.content.length) {
6611
- truncated = true;
6612
- }
6613
- sections.push(finalContent);
6614
- includedIds.push(entry.id);
6615
- usedChars += separatorChars + finalContent.length;
6616
- }
6617
6210
 
6618
- return {
6619
- sections,
6620
- includedIds,
6621
- omittedIds,
6622
- truncated,
6623
- finalChars: usedChars,
6624
- };
6625
- }
6626
6211
 
6627
6212
  /**
6628
6213
  * Clock source for the shared post-retrieval assembly/enrichment budget. The
@@ -16946,18 +16531,7 @@ export class Orchestrator {
16946
16531
  includedSections?: string[];
16947
16532
  omittedSections?: string[];
16948
16533
  }): LastRecallBudgetSummary {
16949
- return {
16950
- requestedTopK: options.requestedTopK,
16951
- appliedTopK: options.recallResultLimit,
16952
- recallBudgetChars: this.getRecallBudgetChars(),
16953
- maxMemoryTokens: this.config.maxMemoryTokens,
16954
- qmdFetchLimit: options.qmdFetchLimit,
16955
- qmdHybridFetchLimit: options.qmdHybridFetchLimit,
16956
- finalContextChars: options.finalContextChars,
16957
- truncated: options.truncated,
16958
- includedSections: [...(options.includedSections ?? [])],
16959
- omittedSections: [...(options.omittedSections ?? [])],
16960
- };
16534
+ return this.recallSectionCoordinator.buildLastRecallBudgetSummary(options);
16961
16535
  }
16962
16536
 
16963
16537
  private collectLastRecallSources(
@@ -16969,16 +16543,10 @@ export class Orchestrator {
16969
16543
  | "cold_fallback"
16970
16544
  | "recent_scan",
16971
16545
  ): string[] {
16972
- const used = new Set<string>();
16973
- if (recallSource !== "none") {
16974
- used.add(recallSource);
16975
- }
16976
- for (const [sectionId, chunks] of sectionBuckets.entries()) {
16977
- if (chunks.length > 0) {
16978
- used.add(sectionId);
16979
- }
16980
- }
16981
- return [...used];
16546
+ return this.recallSectionCoordinator.collectLastRecallSources(
16547
+ sectionBuckets,
16548
+ recallSource,
16549
+ );
16982
16550
  }
16983
16551
 
16984
16552
  /**
@@ -18338,99 +17906,11 @@ export class Orchestrator {
18338
17906
  supersededCreated: string;
18339
17907
  supersededTags: string[];
18340
17908
  } | null> {
18341
- if (!this.isSearchAvailableForNamespaceRouting()) return null;
18342
-
18343
- // Search for similar memories
18344
- const results = await this.searchAcrossNamespaces({
18345
- query: content,
18346
- namespaces: [namespaceScope],
18347
- maxResults: 5,
18348
- mode: "search",
18349
- });
18350
-
18351
- for (const result of results) {
18352
- // Check similarity threshold
18353
- if (result.score < this.config.contradictionSimilarityThreshold) {
18354
- continue;
18355
- }
18356
-
18357
- // Get the existing memory
18358
- const memoryId = this.extractMemoryIdsFromResults([result])[0];
18359
- if (!memoryId) continue;
18360
-
18361
- const resultNamespace = this.namespaceFromPath(result.path);
18362
- if (resultNamespace !== namespaceScope) continue;
18363
- const resultStorage =
18364
- await this.storageRouter.storageFor(resultNamespace);
18365
- const existingMemory = await resultStorage.getMemoryById(memoryId);
18366
- if (!existingMemory) continue;
18367
-
18368
- // Skip memories already resolved or explicitly forgotten. Other
18369
- // non-active statuses remain valid contradiction candidates.
18370
- if (
18371
- existingMemory.frontmatter.status === "superseded" ||
18372
- existingMemory.frontmatter.status === "forgotten"
18373
- ) {
18374
- continue;
18375
- }
18376
-
18377
- // Verify contradiction with LLM
18378
- const verification = await this.extraction.verifyContradiction(
18379
- { content, category },
18380
- {
18381
- id: existingMemory.frontmatter.id,
18382
- content: existingMemory.content,
18383
- category: existingMemory.frontmatter.category,
18384
- created: existingMemory.frontmatter.created,
18385
- },
18386
- );
18387
-
18388
- if (!verification) continue;
18389
-
18390
- // Check if it's a real contradiction with high confidence
18391
- if (
18392
- verification.isContradiction &&
18393
- verification.confidence >= this.config.contradictionMinConfidence
18394
- ) {
18395
- // When the LLM says the existing memory is newer (whichIsNewer ===
18396
- // "first") the incoming fact is the stale one in both resolve modes —
18397
- // log and continue so the caller never marks contradictionDetected and
18398
- // the semantic-skip gate can discard the outdated write normally.
18399
- if (verification.whichIsNewer === "first") {
18400
- log.info(
18401
- `detected contradiction (confidence: ${verification.confidence}): ${existingMemory.frontmatter.id} vs new memory — existing is newer, incoming fact is stale`,
18402
- );
18403
- continue;
18404
- }
18405
-
18406
- // The new fact is newer than the existing one. When auto-resolve is
18407
- // enabled, the caller retires the old memory AFTER the new write lands
18408
- // and its tombstone status is known (#1645: defer auto-resolve until
18409
- // tombstone status is known — a tombstone-blocked new write must not
18410
- // retire the only active copy, leaving neither memory active). When
18411
- // disabled, the old memory stays active for manual review. The
18412
- // contradiction info is returned in both cases so the caller can apply
18413
- // the deferred retire post-write.
18414
-
18415
- // Return the contradiction info regardless of auto-resolve setting.
18416
- // The caller uses this to set `contradictionDetected=true` which
18417
- // prevents the semantic-skip guard from silently dropping a
18418
- // legitimately contradictory update (the regression this fixes).
18419
- log.info(
18420
- `detected contradiction (confidence: ${verification.confidence}): ${existingMemory.frontmatter.id} vs new memory${this.config.contradictionAutoResolve ? " (auto-resolved)" : " (queued for manual review)"}`,
18421
- );
18422
- return {
18423
- supersededId: existingMemory.frontmatter.id,
18424
- confidence: verification.confidence,
18425
- reason: verification.reasoning,
18426
- supersededPath: existingMemory.path,
18427
- supersededCreated: existingMemory.frontmatter.created,
18428
- supersededTags: existingMemory.frontmatter.tags ?? [],
18429
- };
18430
- }
18431
- }
18432
-
18433
- return null;
17909
+ return this.contradictionLinkingCoordinator.checkForContradiction(
17910
+ content,
17911
+ category,
17912
+ namespaceScope,
17913
+ );
18434
17914
  }
18435
17915
 
18436
17916
  /**
@@ -18453,49 +17933,12 @@ export class Orchestrator {
18453
17933
  newMemoryId: string,
18454
17934
  postWriteGuard: boolean,
18455
17935
  ): Promise<void> {
18456
- // #1645 yG2: clear the pre-write `supersedes` link from a blocked row so
18457
- // it doesn't claim to supersede a still-active memory (best-effort).
18458
- if (postWriteGuard && contradiction && this.config.contradictionAutoResolve) {
18459
- try {
18460
- const blockedRow = await storage.getMemoryById(newMemoryId);
18461
- if (blockedRow?.frontmatter?.supersedes) {
18462
- await storage.writeMemoryFrontmatter(blockedRow, { supersedes: undefined });
18463
- }
18464
- } catch (err) {
18465
- log.warn(
18466
- `contradiction auto-resolve supersedes clear failed for blocked ${newMemoryId}: ${err}`,
18467
- );
18468
- }
18469
- }
18470
- if (
18471
- !contradiction ||
18472
- !this.config.contradictionAutoResolve ||
18473
- postWriteGuard
18474
- ) {
18475
- return;
18476
- }
18477
- try {
18478
- await storage.supersedeMemory(
18479
- contradiction.supersededId,
18480
- newMemoryId,
18481
- contradiction.reason,
18482
- );
18483
- if (
18484
- resolveIndexingCapabilities(this.config).queryAwareIndexing &&
18485
- contradiction.supersededPath
18486
- ) {
18487
- deindexMemory(
18488
- this.config.memoryDir,
18489
- contradiction.supersededPath,
18490
- contradiction.supersededCreated,
18491
- contradiction.supersededTags,
18492
- );
18493
- }
18494
- } catch (err) {
18495
- log.warn(
18496
- `contradiction auto-resolve supersede failed for ${contradiction.supersededId}: ${err}`,
18497
- );
18498
- }
17936
+ return this.contradictionLinkingCoordinator.applyDeferredContradictionResolve(
17937
+ contradiction,
17938
+ storage,
17939
+ newMemoryId,
17940
+ postWriteGuard,
17941
+ );
18499
17942
  }
18500
17943
 
18501
17944
  // ---------------------------------------------------------------------------
@@ -18510,98 +17953,11 @@ export class Orchestrator {
18510
17953
  category: string,
18511
17954
  namespaceScope: string,
18512
17955
  ): Promise<MemoryLink[]> {
18513
- if (!this.isSearchAvailableForNamespaceRouting()) return [];
18514
-
18515
- // Search for related memories
18516
- const results = await this.searchAcrossNamespaces({
18517
- query: content,
18518
- namespaces: [namespaceScope],
18519
- maxResults: 5,
18520
- mode: "search",
18521
- });
18522
- if (results.length === 0) return [];
18523
-
18524
- // Get full memory details for candidates
18525
- const candidates: Array<{ id: string; content: string; category: string }> =
18526
- [];
18527
- for (const result of results) {
18528
- const memoryId = this.extractMemoryIdsFromResults([result])[0];
18529
- if (!memoryId) continue;
18530
-
18531
- const resultNamespace = this.namespaceFromPath(result.path);
18532
- if (resultNamespace !== namespaceScope) continue;
18533
- const resultStorage =
18534
- await this.storageRouter.storageFor(resultNamespace);
18535
- const memory = await resultStorage.getMemoryById(memoryId);
18536
- if (
18537
- memory &&
18538
- memory.frontmatter.status !== "superseded" &&
18539
- memory.frontmatter.status !== "forgotten"
18540
- ) {
18541
- candidates.push({
18542
- id: memory.frontmatter.id,
18543
- content: memory.content,
18544
- category: memory.frontmatter.category,
18545
- });
18546
- }
18547
- }
18548
-
18549
- if (candidates.length === 0) return [];
18550
-
18551
- // Ask LLM for link suggestions
18552
- const suggestions = await this.extraction.suggestLinks(
18553
- { content, category },
18554
- candidates,
17956
+ return this.contradictionLinkingCoordinator.suggestLinksForMemory(
17957
+ content,
17958
+ category,
17959
+ namespaceScope,
18555
17960
  );
18556
-
18557
- if (!suggestions || suggestions.links.length === 0) return [];
18558
-
18559
- // Convert to MemoryLink format
18560
- return suggestions.links.map((link) => ({
18561
- targetId: link.targetId,
18562
- linkType: link.linkType,
18563
- strength: link.strength,
18564
- reason: link.reason || undefined,
18565
- }));
18566
- }
18567
-
18568
- private async graphSeedPathRelativeToStorage(
18569
- storage: StorageManager,
18570
- result: QmdSearchResult,
18571
- recallNamespaces: readonly string[] = [],
18572
- ): Promise<string | null> {
18573
- const parts = qmdCollectionPathParts(result.path);
18574
- if (parts) {
18575
- const memory = await this.readQmdResultMemory(
18576
- result.path,
18577
- storage,
18578
- recallNamespaces,
18579
- );
18580
- return memory
18581
- ? graphPathRelativeToStorage(storage.dir, memory.path)
18582
- : null;
18583
- }
18584
- return graphPathRelativeToStorage(storage.dir, result.path);
18585
- }
18586
-
18587
- private async graphSeedPathsWithinDeadline(
18588
- storage: StorageManager,
18589
- results: QmdSearchResult[],
18590
- deadlineAtMs: number,
18591
- recallNamespaces: readonly string[] = [],
18592
- ): Promise<string[]> {
18593
- const resolved: string[] = [];
18594
- for (const result of results) {
18595
- if (Date.now() >= deadlineAtMs) break;
18596
- const seedPath = await this.graphSeedPathRelativeToStorage(
18597
- storage,
18598
- result,
18599
- recallNamespaces,
18600
- );
18601
- if (Date.now() >= deadlineAtMs) break;
18602
- if (seedPath) resolved.push(seedPath);
18603
- }
18604
- return resolved;
18605
17961
  }
18606
17962
 
18607
17963
  private namespaceFromPath(p: string): string {