@remnic/core 9.3.738 → 9.3.740

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 (200) hide show
  1. package/dist/access-boundary.d.ts +6 -6
  2. package/dist/access-boundary.js +13 -13
  3. package/dist/access-cli.js +35 -35
  4. package/dist/access-http.d.ts +6 -6
  5. package/dist/access-http.js +18 -18
  6. package/dist/access-mcp.d.ts +6 -6
  7. package/dist/access-mcp.js +17 -17
  8. package/dist/access-operations-batch.js +15 -15
  9. package/dist/access-operations.d.ts +6 -6
  10. package/dist/access-operations.js +16 -16
  11. package/dist/{access-service-DwQ6BIQw.d.ts → access-service-Cbk-Kv_A.d.ts} +1 -1
  12. package/dist/access-service.d.ts +6 -6
  13. package/dist/access-service.js +12 -12
  14. package/dist/access-surface-catalog.d.ts +6 -6
  15. package/dist/active-memory-bridge.js +3 -3
  16. package/dist/bootstrap.d.ts +5 -5
  17. package/dist/briefing.js +2 -2
  18. package/dist/buffer.js +2 -2
  19. package/dist/capabilities.d.ts +97 -1
  20. package/dist/capabilities.js +7 -1
  21. package/dist/causal-consolidation.js +4 -3
  22. package/dist/causal-consolidation.js.map +1 -1
  23. package/dist/{chunk-IFCM6OLK.js → chunk-3TUDKDTQ.js} +7 -7
  24. package/dist/{chunk-A7INTIC5.js → chunk-4J53ZVPQ.js} +3 -3
  25. package/dist/{chunk-XYUFGHUW.js → chunk-56F6EAQ6.js} +2 -2
  26. package/dist/{chunk-F6OEXJQQ.js → chunk-6XG3NQRC.js} +2 -2
  27. package/dist/{chunk-24ZLC5QN.js → chunk-7N7S2RKF.js} +2 -2
  28. package/dist/{chunk-Y5OOHER4.js → chunk-7Y5KKMIP.js} +12 -11
  29. package/dist/chunk-7Y5KKMIP.js.map +1 -0
  30. package/dist/{chunk-VRBBZH4U.js → chunk-AX4TRSRE.js} +3 -3
  31. package/dist/{chunk-66RZ4EIT.js → chunk-BE6NEQLF.js} +3 -3
  32. package/dist/{chunk-YF2MMBKA.js → chunk-BJPL5WAL.js} +3 -3
  33. package/dist/{chunk-EO5QWINU.js → chunk-C3Z5YJSM.js} +5 -2
  34. package/dist/chunk-C3Z5YJSM.js.map +1 -0
  35. package/dist/{chunk-GEXI357K.js → chunk-C7C5WIIF.js} +2 -2
  36. package/dist/{chunk-L3TYGUTH.js → chunk-CMPXLMC3.js} +3 -3
  37. package/dist/{chunk-W2FBVBQ4.js → chunk-DBVBV2RF.js} +4 -4
  38. package/dist/{chunk-JQSP7YBJ.js → chunk-ELL5VYXC.js} +5 -4
  39. package/dist/chunk-ELL5VYXC.js.map +1 -0
  40. package/dist/{chunk-IQOEW5YV.js → chunk-FCQ7ZYCQ.js} +2 -2
  41. package/dist/{chunk-IIBFCZA7.js → chunk-FWXLKH2H.js} +3 -3
  42. package/dist/{chunk-RJBW4EOU.js → chunk-G7Q4D2G3.js} +74 -2
  43. package/dist/{chunk-RJBW4EOU.js.map → chunk-G7Q4D2G3.js.map} +1 -1
  44. package/dist/{chunk-WVAEIKUY.js → chunk-GPX5E36D.js} +5 -4
  45. package/dist/chunk-GPX5E36D.js.map +1 -0
  46. package/dist/{chunk-23PLOBWT.js → chunk-H3WSZWHK.js} +22 -20
  47. package/dist/chunk-H3WSZWHK.js.map +1 -0
  48. package/dist/{chunk-Q6BAA57B.js → chunk-JFCS6PQU.js} +2 -2
  49. package/dist/chunk-JFCS6PQU.js.map +1 -0
  50. package/dist/{chunk-CKHPUOAP.js → chunk-K2N6WIZM.js} +3 -3
  51. package/dist/{chunk-BHJAMSVC.js → chunk-LD6A7XG5.js} +7 -7
  52. package/dist/{chunk-AKKFHN3I.js → chunk-M4WI23X3.js} +2 -2
  53. package/dist/{chunk-N2O23WYA.js → chunk-M5KLYHHI.js} +2 -2
  54. package/dist/{chunk-TNLXCSXV.js → chunk-O4IWVWSW.js} +5 -4
  55. package/dist/chunk-O4IWVWSW.js.map +1 -0
  56. package/dist/{chunk-OQQRWPFZ.js → chunk-OS5RKJOH.js} +2 -2
  57. package/dist/{chunk-MAU7DFCJ.js → chunk-OT5K26NG.js} +5 -4
  58. package/dist/chunk-OT5K26NG.js.map +1 -0
  59. package/dist/{chunk-DS3FZ7GS.js → chunk-OUSOMUFV.js} +2 -2
  60. package/dist/{chunk-7B7IPPN5.js → chunk-OVYBENIJ.js} +3 -3
  61. package/dist/{chunk-5RZOFIDL.js → chunk-PI4JHLDD.js} +6 -3
  62. package/dist/chunk-PI4JHLDD.js.map +1 -0
  63. package/dist/{chunk-EU543UDC.js → chunk-QCO2QFLZ.js} +7 -7
  64. package/dist/chunk-QCO2QFLZ.js.map +1 -0
  65. package/dist/{chunk-YXOXAQ5Y.js → chunk-TQQN64J2.js} +5 -4
  66. package/dist/chunk-TQQN64J2.js.map +1 -0
  67. package/dist/{chunk-WDJCZQB4.js → chunk-UGFPGWJB.js} +2 -2
  68. package/dist/{chunk-75PDC7VT.js → chunk-UOVDO6TC.js} +6 -5
  69. package/dist/chunk-UOVDO6TC.js.map +1 -0
  70. package/dist/{chunk-BVG2IZOB.js → chunk-USHVHI6J.js} +7 -6
  71. package/dist/chunk-USHVHI6J.js.map +1 -0
  72. package/dist/{chunk-56LP72AD.js → chunk-UYYSOMS2.js} +4 -4
  73. package/dist/{chunk-EKQ3LWBM.js → chunk-VC7QELR6.js} +659 -584
  74. package/dist/chunk-VC7QELR6.js.map +1 -0
  75. package/dist/{chunk-XIQAOGAA.js → chunk-WGDE2TJP.js} +7 -6
  76. package/dist/chunk-WGDE2TJP.js.map +1 -0
  77. package/dist/{chunk-UFS7OXGL.js → chunk-WHEF76BQ.js} +5 -2
  78. package/dist/chunk-WHEF76BQ.js.map +1 -0
  79. package/dist/{chunk-QTJOORGN.js → chunk-WIMKOTNY.js} +2 -2
  80. package/dist/{chunk-GXTYEG7Q.js → chunk-WO22UEM3.js} +2 -2
  81. package/dist/{chunk-BHQQVUEQ.js → chunk-X5PMDVY4.js} +6 -5
  82. package/dist/{chunk-BHQQVUEQ.js.map → chunk-X5PMDVY4.js.map} +1 -1
  83. package/dist/{chunk-C53JDEH3.js → chunk-Y62QPRKV.js} +2 -2
  84. package/dist/{chunk-WFNH4UDF.js → chunk-Y6PNYJVO.js} +3 -3
  85. package/dist/{chunk-6YKIYQU7.js → chunk-YNHRWFAP.js} +5 -2
  86. package/dist/{chunk-6YKIYQU7.js.map → chunk-YNHRWFAP.js.map} +1 -1
  87. package/dist/{cli-BlEIWDQw.d.ts → cli-BQW2xKdb.d.ts} +2 -2
  88. package/dist/cli.d.ts +7 -7
  89. package/dist/cli.js +29 -29
  90. package/dist/compounding/engine.js +4 -4
  91. package/dist/connectors/codex-materialize-runner.js +2 -2
  92. package/dist/connectors/index.js +4 -3
  93. package/dist/contradiction/index.js +2 -2
  94. package/dist/{contradiction-scan-CWO3LIWV.js → contradiction-scan-UERKJESC.js} +3 -3
  95. package/dist/day-summary.js +2 -1
  96. package/dist/embedding-fallback.js +2 -2
  97. package/dist/entity-retrieval.js +3 -3
  98. package/dist/explicit-capture.d.ts +5 -5
  99. package/dist/explicit-capture.js +2 -2
  100. package/dist/extraction.js +4 -4
  101. package/dist/{first-start-migration-5DXD3TF2.js → first-start-migration-GQ3UQNIL.js} +2 -2
  102. package/dist/index.d.ts +8 -8
  103. package/dist/index.js +45 -45
  104. package/dist/local-llm.js +2 -2
  105. package/dist/maintenance/memory-governance.js +2 -2
  106. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  107. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  108. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  109. package/dist/namespaces/migrate.js +7 -7
  110. package/dist/namespaces/principal.js +2 -2
  111. package/dist/namespaces/search.js +4 -4
  112. package/dist/namespaces/storage.js +3 -3
  113. package/dist/operator-toolkit.js +13 -13
  114. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  115. package/dist/orchestration/maintenance.js +7 -7
  116. package/dist/{orchestrator-CJZ21ZiB.d.ts → orchestrator-BOAFN_Yr.d.ts} +186 -56
  117. package/dist/orchestrator.d.ts +4 -4
  118. package/dist/orchestrator.js +29 -29
  119. package/dist/search/embed-helper.js +2 -2
  120. package/dist/search/factory.js +3 -3
  121. package/dist/search/index.js +3 -3
  122. package/dist/semantic-consolidation.js +4 -3
  123. package/dist/semantic-rule-promotion.js +2 -2
  124. package/dist/semantic-rule-verifier.js +2 -2
  125. package/dist/shared-context/manager.js +2 -1
  126. package/dist/storage.d.ts +1 -1
  127. package/dist/storage.js +1 -1
  128. package/dist/summarizer.js +3 -3
  129. package/dist/{tier-stats-QID73BBB.js → tier-stats-CSKL7BDG.js} +2 -2
  130. package/dist/verified-recall.js +2 -2
  131. package/package.json +2 -2
  132. package/src/access-service.ts +2 -1
  133. package/src/capabilities.test.ts +166 -0
  134. package/src/capabilities.ts +267 -0
  135. package/src/cli/research-status-commands.ts +2 -1
  136. package/src/cli.ts +3 -2
  137. package/src/compounding/engine.ts +2 -1
  138. package/src/connectors/codex-marketplace.ts +2 -1
  139. package/src/day-summary.ts +2 -1
  140. package/src/embedding-fallback.ts +2 -1
  141. package/src/extraction.ts +3 -2
  142. package/src/local-llm.ts +2 -1
  143. package/src/maintenance/namespace-maintenance-fanout.ts +2 -1
  144. package/src/maintenance/namespace-planner.ts +2 -1
  145. package/src/operator-toolkit.ts +1 -0
  146. package/src/orchestration/conversation-index-coordinator.ts +344 -0
  147. package/src/orchestration/recall-rerank-coordinator.ts +425 -0
  148. package/src/orchestration/semantic-consolidation-coordinator.ts +3 -2
  149. package/src/orchestrator.ts +89 -598
  150. package/src/search/embed-helper.ts +2 -1
  151. package/src/search/factory.ts +2 -1
  152. package/src/semantic-consolidation.ts +2 -1
  153. package/src/shared-context/manager.ts +3 -2
  154. package/src/storage.ts +1 -1
  155. package/src/summarizer.ts +3 -2
  156. package/dist/chunk-23PLOBWT.js.map +0 -1
  157. package/dist/chunk-5RZOFIDL.js.map +0 -1
  158. package/dist/chunk-75PDC7VT.js.map +0 -1
  159. package/dist/chunk-BVG2IZOB.js.map +0 -1
  160. package/dist/chunk-EKQ3LWBM.js.map +0 -1
  161. package/dist/chunk-EO5QWINU.js.map +0 -1
  162. package/dist/chunk-EU543UDC.js.map +0 -1
  163. package/dist/chunk-JQSP7YBJ.js.map +0 -1
  164. package/dist/chunk-MAU7DFCJ.js.map +0 -1
  165. package/dist/chunk-Q6BAA57B.js.map +0 -1
  166. package/dist/chunk-TNLXCSXV.js.map +0 -1
  167. package/dist/chunk-UFS7OXGL.js.map +0 -1
  168. package/dist/chunk-WVAEIKUY.js.map +0 -1
  169. package/dist/chunk-XIQAOGAA.js.map +0 -1
  170. package/dist/chunk-Y5OOHER4.js.map +0 -1
  171. package/dist/chunk-YXOXAQ5Y.js.map +0 -1
  172. /package/dist/{chunk-IFCM6OLK.js.map → chunk-3TUDKDTQ.js.map} +0 -0
  173. /package/dist/{chunk-A7INTIC5.js.map → chunk-4J53ZVPQ.js.map} +0 -0
  174. /package/dist/{chunk-XYUFGHUW.js.map → chunk-56F6EAQ6.js.map} +0 -0
  175. /package/dist/{chunk-F6OEXJQQ.js.map → chunk-6XG3NQRC.js.map} +0 -0
  176. /package/dist/{chunk-24ZLC5QN.js.map → chunk-7N7S2RKF.js.map} +0 -0
  177. /package/dist/{chunk-VRBBZH4U.js.map → chunk-AX4TRSRE.js.map} +0 -0
  178. /package/dist/{chunk-66RZ4EIT.js.map → chunk-BE6NEQLF.js.map} +0 -0
  179. /package/dist/{chunk-YF2MMBKA.js.map → chunk-BJPL5WAL.js.map} +0 -0
  180. /package/dist/{chunk-GEXI357K.js.map → chunk-C7C5WIIF.js.map} +0 -0
  181. /package/dist/{chunk-L3TYGUTH.js.map → chunk-CMPXLMC3.js.map} +0 -0
  182. /package/dist/{chunk-W2FBVBQ4.js.map → chunk-DBVBV2RF.js.map} +0 -0
  183. /package/dist/{chunk-IQOEW5YV.js.map → chunk-FCQ7ZYCQ.js.map} +0 -0
  184. /package/dist/{chunk-IIBFCZA7.js.map → chunk-FWXLKH2H.js.map} +0 -0
  185. /package/dist/{chunk-CKHPUOAP.js.map → chunk-K2N6WIZM.js.map} +0 -0
  186. /package/dist/{chunk-BHJAMSVC.js.map → chunk-LD6A7XG5.js.map} +0 -0
  187. /package/dist/{chunk-AKKFHN3I.js.map → chunk-M4WI23X3.js.map} +0 -0
  188. /package/dist/{chunk-N2O23WYA.js.map → chunk-M5KLYHHI.js.map} +0 -0
  189. /package/dist/{chunk-OQQRWPFZ.js.map → chunk-OS5RKJOH.js.map} +0 -0
  190. /package/dist/{chunk-DS3FZ7GS.js.map → chunk-OUSOMUFV.js.map} +0 -0
  191. /package/dist/{chunk-7B7IPPN5.js.map → chunk-OVYBENIJ.js.map} +0 -0
  192. /package/dist/{chunk-WDJCZQB4.js.map → chunk-UGFPGWJB.js.map} +0 -0
  193. /package/dist/{chunk-56LP72AD.js.map → chunk-UYYSOMS2.js.map} +0 -0
  194. /package/dist/{chunk-QTJOORGN.js.map → chunk-WIMKOTNY.js.map} +0 -0
  195. /package/dist/{chunk-GXTYEG7Q.js.map → chunk-WO22UEM3.js.map} +0 -0
  196. /package/dist/{chunk-C53JDEH3.js.map → chunk-Y62QPRKV.js.map} +0 -0
  197. /package/dist/{chunk-WFNH4UDF.js.map → chunk-Y6PNYJVO.js.map} +0 -0
  198. /package/dist/{contradiction-scan-CWO3LIWV.js.map → contradiction-scan-UERKJESC.js.map} +0 -0
  199. /package/dist/{first-start-migration-5DXD3TF2.js.map → first-start-migration-GQ3UQNIL.js.map} +0 -0
  200. /package/dist/{tier-stats-QID73BBB.js.map → tier-stats-CSKL7BDG.js.map} +0 -0
@@ -102,6 +102,8 @@ import { SemanticConsolidationCoordinator } from "./orchestration/semantic-conso
102
102
  import { LifecyclePolicyCoordinator } from "./orchestration/lifecycle-policy-coordinator.js";
103
103
  import { EntitySynthesisCoordinator } from "./orchestration/entity-synthesis-coordinator.js";
104
104
  import { RecallResultFormatter } from "./orchestration/recall-result-formatter.js";
105
+ import { ConversationIndexCoordinator } from "./orchestration/conversation-index-coordinator.js";
106
+ import { RecallRerankCoordinator } from "./orchestration/recall-rerank-coordinator.js";
105
107
  export { hasIdentityRecoveryIntent, resolveEffectiveIdentityInjectionMode } from "./orchestration/recall-result-formatter.js";
106
108
  import {
107
109
  runLiveConnectorsOnce,
@@ -121,16 +123,8 @@ import {
121
123
  type ParallelSearchResult,
122
124
  } from "./retrieval-agents.js";
123
125
  import { RerankCache, rerankLocalOrNoop } from "./rerank.js";
124
- import {
125
- applyMemoryWorthFilter,
126
- buildMemoryWorthCounterMap,
127
- type MemoryWorthCounters,
128
- } from "./memory-worth-filter.js";
129
- import { applyTrustScoreStage, buildTrustSignalsForRerank, projectTrustForXray } from "./trust-score-stage.js";
126
+ import { projectTrustForXray } from "./trust-score-stage.js";
130
127
  import type { TrustStageResultItem } from "./trust-score-stage.js";
131
- import { renderEpistemicHedge, type TrustSignals } from "./trust-score.js";
132
- import { reorderRecallResultsWithMmr } from "./recall-mmr.js";
133
- import { applyReasoningTraceBoost } from "./reasoning-trace-recall.js";
134
128
  import { buildRetrievedMemoryProvenance } from "./memory-provenance.js";
135
129
  import {
136
130
  applyTemporalSupersession,
@@ -281,7 +275,11 @@ import { resolveNamespaceCapabilities,
281
275
  resolveQmdCapabilities,
282
276
  resolveIdentityContinuityCapabilities,
283
277
  resolveLocalLlmCapabilities,
284
- resolveSecurityCapabilities, resolveEvalCapabilities, resolveUtilityLearningCapabilities, resolveObjectiveStateCapabilities, resolveCompressionCapabilities, resolvePresentationCapabilities, resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
278
+ resolveSecurityCapabilities, resolveEvalCapabilities, resolveUtilityLearningCapabilities, resolveObjectiveStateCapabilities, resolveCompressionCapabilities, resolvePresentationCapabilities, resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities ,
279
+ resolveRecallEnhancementCapabilities,
280
+ resolvePipelineProcessingCapabilities,
281
+ resolveConversationContextCapabilities,
282
+ } from "./capabilities.js";
285
283
  import { DEFAULT_TAXONOMY } from "./taxonomy/index.js";
286
284
  import {
287
285
  searchHarmonicRetrieval,
@@ -330,9 +328,6 @@ import {
330
328
  type SemanticConsolidationLlmOperator,
331
329
  type SemanticConsolidationResult,
332
330
  } from "./semantic-consolidation.js";
333
- import { chunkTranscriptEntries } from "./conversation-index/chunker.js";
334
- import { writeConversationChunks } from "./conversation-index/indexer.js";
335
- import { cleanupConversationChunks } from "./conversation-index/cleanup.js";
336
331
  import {
337
332
  type ConversationIndexBackend,
338
333
  type ConversationIndexBackendInspection,
@@ -1817,31 +1812,6 @@ export class Orchestrator {
1817
1812
  readonly lcmEngine: LcmEngine | null = null;
1818
1813
  private readonly rerankCache = new RerankCache();
1819
1814
 
1820
- /**
1821
- * Short-TTL cache for Memory Worth counter lookups so interactive recall
1822
- * doesn't trigger a full `readAllMemories` scan per query. Keyed by
1823
- * namespace; the filter unions across namespaces at query time. The TTL
1824
- * is intentionally short (seconds, not minutes) because counters are
1825
- * mutated by `recordMemoryOutcome` asynchronously and we'd rather serve
1826
- * a 30-second-stale worth score than a stable-but-wrong one.
1827
- */
1828
- private readonly memoryWorthCounterCache = new Map<
1829
- string,
1830
- { at: number; counters: ReadonlyMap<string, MemoryWorthCounters> }
1831
- >();
1832
- private static readonly MEMORY_WORTH_CACHE_TTL_MS = 30_000;
1833
- /**
1834
- * Issue #1577 — per-namespace TrustScore signal map cache. Same TTL/shape
1835
- * discipline as {@link memoryWorthCounterCache}: seconds-scale, evicted on
1836
- * every call, keyed by namespace. Holds the frontmatter-derived signals
1837
- * (worth, provenance, faithfulness, corroboration, recency) so the trust
1838
- * stage doesn't trigger a fresh `readAllMemories` scan per query.
1839
- */
1840
- private readonly trustSignalCache = new Map<
1841
- string,
1842
- { at: number; signals: ReadonlyMap<string, TrustSignals> }
1843
- >();
1844
- private static readonly TRUST_SIGNAL_CACHE_TTL_MS = 30_000;
1845
1815
  /**
1846
1816
  * Per-session workspace selections keyed by sessionKey.
1847
1817
  * Set by the before_agent_start hook so recall() uses the correct
@@ -1909,6 +1879,12 @@ export class Orchestrator {
1909
1879
  * to RecallResultFormatter.
1910
1880
  */
1911
1881
  readonly recallResultFormatter: RecallResultFormatter;
1882
+ /**
1883
+ * Issue #1526: conversation-index subsystem moved to
1884
+ * ConversationIndexCoordinator.
1885
+ */
1886
+ readonly conversationIndexCoordinator: ConversationIndexCoordinator;
1887
+ readonly recallRerankCoordinator: RecallRerankCoordinator;
1912
1888
  private heartbeatObserverChains = new Map<string, Promise<void>>();
1913
1889
  private recentExtractionFingerprints = new Map<string, number>();
1914
1890
  private readonly consolidationObservers = new Set<
@@ -1917,7 +1893,6 @@ export class Orchestrator {
1917
1893
  private wearablesServiceInstance: WearablesService | null = null;
1918
1894
  private wearablesAutoSyncHandle: { stop(): Promise<void> } | null = null;
1919
1895
  private lastQmdReprobeAtMs = 0;
1920
- private readonly conversationIndexLastUpdateAtMs = new Map<string, number>();
1921
1896
  private lastFileHygieneRunAtMs = 0;
1922
1897
  // Pattern-reinforcement cadence gate (issue #687 PR 2/4). Tracks the
1923
1898
  // last successful run so `runPatternReinforcement` can short-circuit
@@ -2796,7 +2771,7 @@ export class Orchestrator {
2796
2771
  constructor(config: PluginConfig) {
2797
2772
  this.config = config;
2798
2773
  this.profiler = new ProfilingCollector({
2799
- enabled: config.profilingEnabled,
2774
+ enabled: resolvePipelineProcessingCapabilities(this.config).profiling,
2800
2775
  storageDir: config.profilingStorageDir || path.join(config.memoryDir, "profiling"),
2801
2776
  maxTraces: config.profilingMaxTraces,
2802
2777
  });
@@ -2885,7 +2860,7 @@ export class Orchestrator {
2885
2860
  this.conversationQmd = conversationIndexRuntime.qmd;
2886
2861
  this.conversationFaiss = conversationIndexRuntime.faiss;
2887
2862
  this.conversationIndexBackend = conversationIndexRuntime.backend;
2888
- this.sharedContext = config.sharedContextEnabled
2863
+ this.sharedContext = resolveConversationContextCapabilities(this.config).sharedContext
2889
2864
  ? new SharedContextManager(config)
2890
2865
  : undefined;
2891
2866
  this.compounding = resolveConsolidationCapabilities(config).compounding
@@ -2898,6 +2873,18 @@ export class Orchestrator {
2898
2873
  "conversation-index",
2899
2874
  "chunks",
2900
2875
  );
2876
+ this.conversationIndexCoordinator = new ConversationIndexCoordinator({
2877
+ config,
2878
+ getTranscript: () => this.transcript,
2879
+ getBackend: () => this.conversationIndexBackend,
2880
+ indexDir: this.conversationIndexDir,
2881
+ });
2882
+ this.recallRerankCoordinator = new RecallRerankCoordinator({
2883
+ getConfig: () => this.config,
2884
+ getStorage: (namespace) => this.getStorage(namespace),
2885
+ readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) =>
2886
+ this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces),
2887
+ });
2901
2888
  this.modelRegistry = new ModelRegistry(config.memoryDir);
2902
2889
  this.relevance = new RelevanceStore(config.memoryDir);
2903
2890
  this.negatives = new NegativeExampleStore(config.memoryDir);
@@ -2951,7 +2938,7 @@ export class Orchestrator {
2951
2938
  // timeout. Operators can set `localLlmDisableThinking: false`
2952
2939
  // when they want thinking enabled for narrative paths.
2953
2940
  this.localLlm.disableThinking = config.localLlmDisableThinking;
2954
- this.fastLlm = config.localLlmFastEnabled
2941
+ this.fastLlm = resolvePipelineProcessingCapabilities(this.config).localLlmFast
2955
2942
  ? (() => {
2956
2943
  const client = new LocalLlmClient(
2957
2944
  {
@@ -3024,7 +3011,7 @@ export class Orchestrator {
3024
3011
  });
3025
3012
 
3026
3013
  // Lossless Context Management (LCM) — proactive session archive + DAG summarization
3027
- if (config.lcmEnabled) {
3014
+ if (resolvePipelineProcessingCapabilities(this.config).lcm) {
3028
3015
  const summarizeFn = async (
3029
3016
  text: string,
3030
3017
  targetTokens: number,
@@ -3081,7 +3068,7 @@ export class Orchestrator {
3081
3068
  dir,
3082
3069
  new BoxBuilder(dir, {
3083
3070
  memoryBoxesEnabled: resolvePresentationCapabilities(this.config).memoryBoxes,
3084
- traceWeaverEnabled: this.config.traceWeaverEnabled,
3071
+ traceWeaverEnabled: resolvePipelineProcessingCapabilities(this.config).traceWeaver,
3085
3072
  boxTopicShiftThreshold: this.config.boxTopicShiftThreshold,
3086
3073
  boxTimeGapMs: this.config.boxTimeGapMs,
3087
3074
  boxMaxMemories: this.config.boxMaxMemories,
@@ -3174,7 +3161,7 @@ export class Orchestrator {
3174
3161
  }
3175
3162
 
3176
3163
  private async loadRoutingRules(): Promise<RouteRule[]> {
3177
- if (!this.config.routingRulesEnabled) return [];
3164
+ if (!resolvePipelineProcessingCapabilities(this.config).routingRules) return [];
3178
3165
  try {
3179
3166
  return await this.getRoutingRulesStore().read(this.routeEngineOptions());
3180
3167
  } catch (err) {
@@ -5006,63 +4993,21 @@ export class Orchestrator {
5006
4993
  retrievalQuery: string,
5007
4994
  topK: number,
5008
4995
  ): Promise<Array<{ path: string; snippet: string; score: number }>> {
5009
- if (this.conversationIndexBackend) {
5010
- return this.conversationIndexBackend.search(retrievalQuery, topK);
5011
- }
5012
- return [];
4996
+ return this.conversationIndexCoordinator.search(retrievalQuery, topK);
5013
4997
  }
5014
4998
 
5015
4999
  private formatConversationRecallSection(
5016
5000
  results: Array<{ path: string; snippet: string; score: number }>,
5017
5001
  maxChars: number,
5018
5002
  ): string | null {
5019
- if (!Array.isArray(results) || results.length === 0) return null;
5020
- const lines: string[] = ["## Semantic Recall (Past Conversations)", ""];
5021
- let used = 0;
5022
- for (const r of results) {
5023
- if (!r?.snippet) continue;
5024
- const chunk =
5025
- `### ${r.path}\n` +
5026
- `Score: ${r.score.toFixed(3)}\n\n` +
5027
- `${r.snippet.trim()}\n`;
5028
- if (used + chunk.length > maxChars) break;
5029
- lines.push(chunk);
5030
- used += chunk.length;
5031
- }
5032
- return used > 0 ? lines.join("\n") : null;
5033
- }
5034
-
5035
- private async countConversationChunkDocs(dir: string): Promise<number> {
5036
- try {
5037
- const entries = await readdir(dir, { withFileTypes: true });
5038
- let total = 0;
5039
- for (const entry of entries) {
5040
- const fullPath = path.join(dir, entry.name);
5041
- if (entry.isDirectory()) {
5042
- total += await this.countConversationChunkDocs(fullPath);
5043
- continue;
5044
- }
5045
- if (entry.isFile() && entry.name.endsWith(".md")) {
5046
- total += 1;
5047
- }
5048
- }
5049
- return total;
5050
- } catch {
5051
- return 0;
5052
- }
5003
+ return this.conversationIndexCoordinator.formatRecallSection(
5004
+ results,
5005
+ maxChars,
5006
+ );
5053
5007
  }
5054
5008
 
5055
- private async buildConversationIndexChunks(
5056
- sessionKey?: string,
5057
- hours: number = 24,
5058
- ): Promise<ReturnType<typeof chunkTranscriptEntries>> {
5059
- const entries = await this.transcript.readRecent(hours, sessionKey);
5060
- const effectiveSessionKey = sessionKey ?? "all-sessions";
5061
- return chunkTranscriptEntries(effectiveSessionKey, entries, {
5062
- maxChars: this.config.conversationRecallMaxChars * 2,
5063
- maxTurns: Math.max(10, this.config.hourlySummariesMaxTurnsPerRun),
5064
- });
5065
- }
5009
+ // Issue #1526: countConversationChunkDocs / buildConversationIndexChunks moved
5010
+ // to ConversationIndexCoordinator (internal helpers, no orchestrator callers).
5066
5011
 
5067
5012
  async getConversationIndexHealth(): Promise<{
5068
5013
  enabled: boolean;
@@ -5087,37 +5032,7 @@ export class Orchestrator {
5087
5032
  };
5088
5033
  };
5089
5034
  }> {
5090
- const chunkDocCount = await this.countConversationChunkDocs(
5091
- this.conversationIndexDir,
5092
- );
5093
- const lastUpdateAtMs = Math.max(
5094
- 0,
5095
- ...this.conversationIndexLastUpdateAtMs.values(),
5096
- );
5097
- const lastUpdateAt =
5098
- lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
5099
-
5100
- if (!resolveIndexingCapabilities(this.config).conversationIndex) {
5101
- return {
5102
- enabled: false,
5103
- backend: this.config.conversationIndexBackend,
5104
- status: "disabled",
5105
- chunkDocCount,
5106
- lastUpdateAt,
5107
- };
5108
- }
5109
- const backendHealth = this.conversationIndexBackend
5110
- ? await this.conversationIndexBackend.health()
5111
- : {
5112
- backend: this.config.conversationIndexBackend,
5113
- status: "degraded" as const,
5114
- };
5115
- return {
5116
- enabled: true,
5117
- chunkDocCount,
5118
- lastUpdateAt,
5119
- ...backendHealth,
5120
- };
5035
+ return this.conversationIndexCoordinator.getHealth();
5121
5036
  }
5122
5037
 
5123
5038
  async inspectConversationIndex(): Promise<
@@ -5127,53 +5042,7 @@ export class Orchestrator {
5127
5042
  lastUpdateAt: string | null;
5128
5043
  }
5129
5044
  > {
5130
- const chunkDocCount = await this.countConversationChunkDocs(
5131
- this.conversationIndexDir,
5132
- );
5133
- const lastUpdateAtMs = Math.max(
5134
- 0,
5135
- ...this.conversationIndexLastUpdateAtMs.values(),
5136
- );
5137
- const lastUpdateAt =
5138
- lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
5139
-
5140
- if (!resolveIndexingCapabilities(this.config).conversationIndex) {
5141
- return {
5142
- enabled: false,
5143
- backend: this.config.conversationIndexBackend,
5144
- status: "disabled",
5145
- available: false,
5146
- indexPath: this.conversationIndexDir,
5147
- supportsIncrementalUpdate: true,
5148
- message: "Conversation index disabled by config",
5149
- metadata: {
5150
- chunkCount: chunkDocCount,
5151
- },
5152
- chunkDocCount,
5153
- lastUpdateAt,
5154
- };
5155
- }
5156
-
5157
- const inspection = this.conversationIndexBackend
5158
- ? await this.conversationIndexBackend.inspect()
5159
- : {
5160
- backend: this.config.conversationIndexBackend,
5161
- status: "degraded" as const,
5162
- available: false,
5163
- indexPath: this.conversationIndexDir,
5164
- supportsIncrementalUpdate: true,
5165
- message: "Conversation index backend unavailable",
5166
- metadata: {
5167
- chunkCount: chunkDocCount,
5168
- },
5169
- };
5170
-
5171
- return {
5172
- enabled: true,
5173
- chunkDocCount,
5174
- lastUpdateAt,
5175
- ...inspection,
5176
- };
5045
+ return this.conversationIndexCoordinator.inspect();
5177
5046
  }
5178
5047
 
5179
5048
  async getRecoverySummary(sessionKey?: string): Promise<{
@@ -5199,54 +5068,7 @@ export class Orchestrator {
5199
5068
  retryAfterMs?: number;
5200
5069
  embedded?: boolean;
5201
5070
  }> {
5202
- if (!resolveIndexingCapabilities(this.config).conversationIndex) {
5203
- return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
5204
- }
5205
- const enforceMinInterval = opts?.enforceMinInterval !== false;
5206
- if (enforceMinInterval) {
5207
- const minIntervalMs = Math.max(
5208
- 0,
5209
- this.config.conversationIndexMinUpdateIntervalMs,
5210
- );
5211
- const now = Date.now();
5212
- const last = this.conversationIndexLastUpdateAtMs.get(sessionKey) ?? 0;
5213
- const elapsed = now - last;
5214
- if (minIntervalMs > 0 && elapsed < minIntervalMs) {
5215
- return {
5216
- chunks: 0,
5217
- skipped: true,
5218
- reason: "min_interval",
5219
- retryAfterMs: minIntervalMs - elapsed,
5220
- embedded: false,
5221
- };
5222
- }
5223
- }
5224
- const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
5225
- await writeConversationChunks(this.conversationIndexDir, chunks);
5226
- const retentionCutoffMs =
5227
- Number.isFinite(this.config.conversationIndexRetentionDays) &&
5228
- this.config.conversationIndexRetentionDays > 0
5229
- ? Date.now() -
5230
- this.config.conversationIndexRetentionDays * 24 * 60 * 60 * 1000
5231
- : undefined;
5232
- await cleanupConversationChunks(
5233
- this.conversationIndexDir,
5234
- this.config.conversationIndexRetentionDays,
5235
- );
5236
- const shouldEmbed =
5237
- opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
5238
- let embedded = false;
5239
-
5240
- if (this.conversationIndexBackend) {
5241
- const result = await this.conversationIndexBackend.update(chunks, {
5242
- embed: shouldEmbed,
5243
- ...(retentionCutoffMs !== undefined ? { retentionCutoffMs } : {}),
5244
- });
5245
- embedded = result.embedded;
5246
- }
5247
-
5248
- this.conversationIndexLastUpdateAtMs.set(sessionKey, Date.now());
5249
- return { chunks: chunks.length, skipped: false, embedded };
5071
+ return this.conversationIndexCoordinator.update(sessionKey, hours, opts);
5250
5072
  }
5251
5073
 
5252
5074
  async rebuildConversationIndex(
@@ -5260,42 +5082,7 @@ export class Orchestrator {
5260
5082
  embedded?: boolean;
5261
5083
  rebuilt?: boolean;
5262
5084
  }> {
5263
- if (!resolveIndexingCapabilities(this.config).conversationIndex) {
5264
- return {
5265
- chunks: 0,
5266
- skipped: true,
5267
- reason: "disabled",
5268
- embedded: false,
5269
- rebuilt: false,
5270
- };
5271
- }
5272
-
5273
- const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
5274
- await writeConversationChunks(this.conversationIndexDir, chunks);
5275
- await cleanupConversationChunks(
5276
- this.conversationIndexDir,
5277
- this.config.conversationIndexRetentionDays,
5278
- );
5279
-
5280
- const shouldEmbed =
5281
- opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
5282
- let embedded = false;
5283
- let rebuilt = false;
5284
- if (this.conversationIndexBackend) {
5285
- const result = await this.conversationIndexBackend.rebuild(chunks, {
5286
- embed: shouldEmbed,
5287
- });
5288
- embedded = result.embedded;
5289
- rebuilt = result.rebuilt;
5290
- }
5291
-
5292
- const stamp = Date.now();
5293
- if (sessionKey) {
5294
- this.conversationIndexLastUpdateAtMs.set(sessionKey, stamp);
5295
- } else {
5296
- this.conversationIndexLastUpdateAtMs.set("__rebuild__", stamp);
5297
- }
5298
- return { chunks: chunks.length, skipped: false, embedded, rebuilt };
5085
+ return this.conversationIndexCoordinator.rebuild(sessionKey, hours, opts);
5299
5086
  }
5300
5087
 
5301
5088
  /**
@@ -7089,7 +6876,7 @@ export class Orchestrator {
7089
6876
  signal: options.abortSignal,
7090
6877
  });
7091
6878
  if (
7092
- this.config.recallPlannerTelemetryEnabled &&
6879
+ resolveRecallEnhancementCapabilities(this.config).recallPlannerTelemetry &&
7093
6880
  recallDecision.plannerSource &&
7094
6881
  recallDecision.plannerSource !== "heuristic"
7095
6882
  ) {
@@ -7623,7 +7410,7 @@ export class Orchestrator {
7623
7410
  if (
7624
7411
  !this.isRecallSectionEnabled(
7625
7412
  "shared-context",
7626
- this.config.sharedContextEnabled === true,
7413
+ resolveConversationContextCapabilities(this.config).sharedContext === true,
7627
7414
  )
7628
7415
  )
7629
7416
  return null;
@@ -7747,7 +7534,7 @@ export class Orchestrator {
7747
7534
  | null
7748
7535
  | undefined = undefined;
7749
7536
  const peerProfileRecallPromise = (async (): Promise<string | null> => {
7750
- if (!this.config.peerProfileRecallEnabled) return null;
7537
+ if (!resolveRecallEnhancementCapabilities(this.config).peerProfileRecall) return null;
7751
7538
  if (this.config.peerProfileRecallMaxFields <= 0) return null;
7752
7539
  const peerId = this.getPeerIdForSession(sessionKey);
7753
7540
  if (!peerId) return null;
@@ -8154,11 +7941,11 @@ export class Orchestrator {
8154
7941
  const causalTrajectoryPromise = (async (): Promise<string | null> => {
8155
7942
  const t0 = Date.now();
8156
7943
  if (
8157
- !this.config.causalTrajectoryMemoryEnabled ||
8158
- !this.config.causalTrajectoryRecallEnabled ||
7944
+ !resolveRecallEnhancementCapabilities(this.config).causalTrajectoryMemory ||
7945
+ !resolveRecallEnhancementCapabilities(this.config).causalTrajectoryRecall ||
8159
7946
  !this.isRecallSectionEnabled(
8160
7947
  "causal-trajectories",
8161
- this.config.causalTrajectoryRecallEnabled === true,
7948
+ resolveRecallEnhancementCapabilities(this.config).causalTrajectoryRecall === true,
8162
7949
  )
8163
7950
  ) {
8164
7951
  recordRecallSectionMetric({
@@ -8211,10 +7998,10 @@ export class Orchestrator {
8211
7998
  const cmcRetrievalPromise = (async (): Promise<string | null> => {
8212
7999
  const t0 = Date.now();
8213
8000
  if (
8214
- !this.config.cmcRetrievalEnabled ||
8001
+ !resolveRecallEnhancementCapabilities(this.config).cmcRetrieval ||
8215
8002
  !this.isRecallSectionEnabled(
8216
8003
  "cmc-causal-chains",
8217
- this.config.cmcRetrievalEnabled === true,
8004
+ resolveRecallEnhancementCapabilities(this.config).cmcRetrieval === true,
8218
8005
  )
8219
8006
  ) {
8220
8007
  recordRecallSectionMetric({
@@ -9212,7 +8999,7 @@ export class Orchestrator {
9212
8999
  let section: string | null = null;
9213
9000
  // Try checkpoint first (post-compaction recovery)
9214
9001
  let checkpointInjected = false;
9215
- if (this.config.checkpointEnabled) {
9002
+ if (resolvePipelineProcessingCapabilities(this.config).checkpoint) {
9216
9003
  const checkpoint = await this.transcript.loadCheckpoint(sessionKey);
9217
9004
  log.debug(
9218
9005
  `recall: checkpoint loaded, turns=${checkpoint?.turns?.length ?? 0}`,
@@ -9345,7 +9132,7 @@ export class Orchestrator {
9345
9132
  const summariesPromise = (async (): Promise<string | null> => {
9346
9133
  const t0 = Date.now();
9347
9134
  if (
9348
- !this.config.hourlySummariesEnabled ||
9135
+ !resolvePipelineProcessingCapabilities(this.config).hourlySummaries ||
9349
9136
  !sessionKey ||
9350
9137
  !this.isRecallSectionEnabled("summaries", true)
9351
9138
  ) {
@@ -9575,7 +9362,7 @@ export class Orchestrator {
9575
9362
  const t0 = Date.now();
9576
9363
  if (
9577
9364
  !this.compounding ||
9578
- !this.config.compoundingInjectEnabled ||
9365
+ !resolveRecallEnhancementCapabilities(this.config).compoundingInject ||
9579
9366
  !this.isRecallSectionEnabled("compounding", true)
9580
9367
  ) {
9581
9368
  recordRecallSectionMetric({
@@ -9916,7 +9703,7 @@ export class Orchestrator {
9916
9703
  this.getRecallSectionMaxChars("explicit-cue") ??
9917
9704
  this.config.explicitCueRecallMaxChars;
9918
9705
  if (
9919
- this.config.explicitCueRecallEnabled &&
9706
+ resolveRecallEnhancementCapabilities(this.config).explicitCueRecall &&
9920
9707
  this.isRecallSectionEnabled("explicit-cue") &&
9921
9708
  explicitCueMaxChars !== 0 &&
9922
9709
  this.lcmEngine?.enabled &&
@@ -9959,7 +9746,7 @@ export class Orchestrator {
9959
9746
  if (
9960
9747
  this.isSpecializedRecallSectionEnabled(
9961
9748
  "targeted-facts",
9962
- this.config.targetedFactRecallEnabled,
9749
+ resolveRecallEnhancementCapabilities(this.config).targetedFactRecall,
9963
9750
  ) &&
9964
9751
  targetedFactMaxChars !== 0 &&
9965
9752
  this.lcmEngine?.enabled &&
@@ -10008,7 +9795,7 @@ export class Orchestrator {
10008
9795
  if (
10009
9796
  this.isSpecializedRecallSectionEnabled(
10010
9797
  "focused-list",
10011
- this.config.focusedListRecallEnabled,
9798
+ resolveRecallEnhancementCapabilities(this.config).focusedListRecall,
10012
9799
  ) &&
10013
9800
  focusedListMaxChars !== 0 &&
10014
9801
  this.lcmEngine?.enabled &&
@@ -10062,7 +9849,7 @@ export class Orchestrator {
10062
9849
  if (
10063
9850
  this.isSpecializedRecallSectionEnabled(
10064
9851
  "response-guidance",
10065
- this.config.responseGuidanceRecallEnabled,
9852
+ resolveRecallEnhancementCapabilities(this.config).responseGuidanceRecall,
10066
9853
  ) &&
10067
9854
  responseGuidanceMaxChars !== 0 &&
10068
9855
  this.lcmEngine?.enabled &&
@@ -10111,7 +9898,7 @@ export class Orchestrator {
10111
9898
  if (
10112
9899
  this.isSpecializedRecallSectionEnabled(
10113
9900
  "event-order",
10114
- this.config.eventOrderRecallEnabled,
9901
+ resolveRecallEnhancementCapabilities(this.config).eventOrderRecall,
10115
9902
  ) &&
10116
9903
  eventOrderMaxChars !== 0 &&
10117
9904
  this.lcmEngine?.enabled &&
@@ -10537,7 +10324,7 @@ export class Orchestrator {
10537
10324
  recallMode === "graph_mode" || isFullModeGraphAssist;
10538
10325
  const graphShadowEvalEnabled =
10539
10326
  isFullModeGraphAssist &&
10540
- this.config.graphAssistShadowEvalEnabled === true;
10327
+ resolveRecallEnhancementCapabilities(this.config).graphAssistShadowEval === true;
10541
10328
  if (shouldRunGraphExpansion) {
10542
10329
  shouldPersistGraphSnapshot = true;
10543
10330
  graphDecisionShadowMode = graphShadowEvalEnabled;
@@ -10801,7 +10588,7 @@ export class Orchestrator {
10801
10588
  );
10802
10589
 
10803
10590
  // E-Mem-inspired memory reconstruction: fill gaps for referenced entities
10804
- if (this.config.memoryReconstructionEnabled && memoryResults.length > 0) {
10591
+ if (resolveRecallEnhancementCapabilities(this.config).memoryReconstruction && memoryResults.length > 0) {
10805
10592
  try {
10806
10593
  const snippets = memoryResults.map((r) => r.snippet);
10807
10594
  // Extract entity paths already present in recall results to avoid duplicates
@@ -12465,7 +12252,7 @@ export class Orchestrator {
12465
12252
  sessionKey: string,
12466
12253
  options: { bufferKey?: string } = {},
12467
12254
  ): Promise<void> {
12468
- if (this.config.sessionObserverEnabled !== true) return;
12255
+ if (resolvePipelineProcessingCapabilities(this.config).sessionObserver !== true) return;
12469
12256
  if (!sessionKey || sessionKey.length === 0) return;
12470
12257
 
12471
12258
  const bufferKey =
@@ -13489,7 +13276,7 @@ export class Orchestrator {
13489
13276
  // the citation survives hostile memory text, copy/paste, and LLM quoting.
13490
13277
  // The helper is a no-op when the feature flag is off, so legacy pipelines
13491
13278
  // see zero behavioral change.
13492
- const citationEnabled = this.config.inlineSourceAttributionEnabled === true;
13279
+ const citationEnabled = resolvePipelineProcessingCapabilities(this.config).inlineSourceAttribution === true;
13493
13280
  const citationTemplate = this.config.inlineSourceAttributionFormat;
13494
13281
  // The stable fields (agent, session) are computed once; `ts` is intentionally
13495
13282
  // omitted here and added fresh per invocation so each fact in a large batch
@@ -13648,7 +13435,7 @@ export class Orchestrator {
13648
13435
  const actualTier = confidenceTier(confidence);
13649
13436
  const actualRank = confidenceTierOrder.indexOf(actualTier);
13650
13437
  if (actualRank === -1) return false;
13651
- if (!this.config.autoPromoteToSharedEnabled) return false;
13438
+ if (!resolveRecallEnhancementCapabilities(this.config).autoPromoteToShared) return false;
13652
13439
  if (!this.config.autoPromoteToSharedCategories.includes(category as any))
13653
13440
  return false;
13654
13441
  const minimumRank = confidenceTierOrder.indexOf(
@@ -15048,7 +14835,7 @@ export class Orchestrator {
15048
14835
  // a high-similarity update/correction is linked as a superseding
15049
14836
  // contradiction rather than silently dropped.
15050
14837
  let pendingSemanticSkip: (SemanticDedupDecision & { action: "skip" }) | null = null;
15051
- if (this.config.semanticDedupEnabled) {
14838
+ if (resolvePipelineProcessingCapabilities(this.config).semanticDedup) {
15052
14839
  let semanticDecision: SemanticDedupDecision;
15053
14840
  // UUI2: skip embedding lookup for the rest of this batch once we know
15054
14841
  // the backend is unavailable. The flag is reset per-batch (set to false
@@ -15093,7 +14880,7 @@ export class Orchestrator {
15093
14880
  }
15094
14881
  }
15095
14882
 
15096
- const inferredIntent = this.config.intentRoutingEnabled
14883
+ const inferredIntent = resolveConversationContextCapabilities(this.config).intentRouting
15097
14884
  ? inferIntentFromText(
15098
14885
  `${writeCategory} ${fact.tags.join(" ")} ${fact.content}`,
15099
14886
  )
@@ -15133,7 +14920,7 @@ export class Orchestrator {
15133
14920
  // for a pending_review fact — an unfaithful extraction in the review queue
15134
14921
  // must not trigger auto-resolve and retire an existing active memory.
15135
14922
  if (
15136
- this.config.contradictionDetectionEnabled &&
14923
+ resolveRecallEnhancementCapabilities(this.config).contradictionDetection &&
15137
14924
  this.qmd.isAvailable() &&
15138
14925
  faithfulnessEnforceStatus !== "pending_review"
15139
14926
  ) {
@@ -15218,10 +15005,10 @@ export class Orchestrator {
15218
15005
  // When semanticChunkingEnabled is true, prefer the embedding-based
15219
15006
  // semantic chunker which produces more coherent topic-aligned segments.
15220
15007
  // Falls back to the recursive sentence-boundary chunker on failure.
15221
- if (this.config.chunkingEnabled && writeCategory !== "procedure") {
15008
+ if (resolvePipelineProcessingCapabilities(this.config).chunking && writeCategory !== "procedure") {
15222
15009
  let chunkResult: { chunked: boolean; chunks: { content: string; index: number; tokenCount: number }[] };
15223
15010
 
15224
- if (this.config.semanticChunkingEnabled) {
15011
+ if (resolvePipelineProcessingCapabilities(this.config).semanticChunking) {
15225
15012
  try {
15226
15013
  const embedFn = this.embeddingFallback.embedTexts.bind(this.embeddingFallback);
15227
15014
  const semanticResult: SemanticChunkResult = await semanticChunkContent(
@@ -15588,7 +15375,7 @@ export class Orchestrator {
15588
15375
  }
15589
15376
 
15590
15377
  // Suggest links for this memory (Phase 3A)
15591
- if (this.config.memoryLinkingEnabled && this.qmd.isAvailable()) {
15378
+ if (resolveRecallEnhancementCapabilities(this.config).memoryLinking && this.qmd.isAvailable()) {
15592
15379
  const targetNamespace = this.storageDirNamespace(targetStorage.dir);
15593
15380
  const suggestedLinks = await this.suggestLinksForMemory(
15594
15381
  fact.content,
@@ -15908,7 +15695,7 @@ export class Orchestrator {
15908
15695
 
15909
15696
  // Persist entity relationships (v7.0)
15910
15697
  if (
15911
- this.config.entityRelationshipsEnabled &&
15698
+ resolveRecallEnhancementCapabilities(this.config).entityRelationships &&
15912
15699
  Array.isArray(result.relationships)
15913
15700
  ) {
15914
15701
  for (const rel of result.relationships.slice(0, 5)) {
@@ -15932,7 +15719,7 @@ export class Orchestrator {
15932
15719
  }
15933
15720
 
15934
15721
  // Persist entity activity (v7.0)
15935
- if (this.config.entityActivityLogEnabled) {
15722
+ if (resolveRecallEnhancementCapabilities(this.config).entityActivityLog) {
15936
15723
  const today = new Date().toISOString().slice(0, 10);
15937
15724
  for (const entity of entities) {
15938
15725
  const name = (entity as any)?.name;
@@ -15970,7 +15757,7 @@ export class Orchestrator {
15970
15757
  // counts as a durable non-fact write for the catalog touch below (NIIly).
15971
15758
  // Only count it when the write actually succeeds (best-effort write); the
15972
15759
  // touch is recorded AFTER this so a rolled-back/failed write never touches.
15973
- if (this.config.identityEnabled && result.identityReflection) {
15760
+ if (resolveRecallEnhancementCapabilities(this.config).identity && result.identityReflection) {
15974
15761
  try {
15975
15762
  await storage.appendIdentityReflection(result.identityReflection);
15976
15763
  recordDurableNonFactWrite();
@@ -16537,7 +16324,7 @@ export class Orchestrator {
16537
16324
  allMemories = await this.storage.readAllMemories();
16538
16325
 
16539
16326
  // Fact archival pass (v6.0) — move old, low-importance, rarely-accessed facts to archive/
16540
- if (this.config.factArchivalEnabled) {
16327
+ if (resolveRecallEnhancementCapabilities(this.config).factArchival) {
16541
16328
  const archived = await this.runFactArchival(allMemories);
16542
16329
  if (archived > 0) {
16543
16330
  memoryItemMutated = true;
@@ -16635,7 +16422,7 @@ export class Orchestrator {
16635
16422
  }
16636
16423
 
16637
16424
  // Auto-consolidate IDENTITY.md if it's getting large
16638
- if (this.config.identityEnabled) {
16425
+ if (resolveRecallEnhancementCapabilities(this.config).identity) {
16639
16426
  await this.autoConsolidateIdentity();
16640
16427
  }
16641
16428
 
@@ -16675,12 +16462,12 @@ export class Orchestrator {
16675
16462
  }
16676
16463
 
16677
16464
  // Memory Summarization (Phase 4A)
16678
- if (this.config.summarizationEnabled) {
16465
+ if (resolvePipelineProcessingCapabilities(this.config).summarization) {
16679
16466
  await this.runSummarization(allMemories);
16680
16467
  }
16681
16468
 
16682
16469
  // Topic Extraction (Phase 4B)
16683
- if (this.config.topicExtractionEnabled) {
16470
+ if (resolvePipelineProcessingCapabilities(this.config).topicExtraction) {
16684
16471
  await this.runTopicExtraction(allMemories);
16685
16472
  }
16686
16473
 
@@ -17365,156 +17152,15 @@ export class Orchestrator {
17365
17152
  return matches[0] ?? null;
17366
17153
  }
17367
17154
 
17155
+ // Issue #1526: recall-rerank methods moved to RecallRerankCoordinator.
17156
+ // Thin delegation keeps the private API stable for callers + tests.
17368
17157
  private async applyMemoryWorthRerank(
17369
17158
  results: QmdSearchResult[],
17370
17159
  namespaces: string[],
17371
17160
  ): Promise<QmdSearchResult[]> {
17372
- // Build the counter lookup. We union frontmatter counters across every
17373
- // namespace the recall spans — the recall path itself already
17374
- // aggregates candidates from multiple namespaces, so we must do the
17375
- // same when looking up counters. Per-namespace results are cached with
17376
- // a short TTL so interactive recall doesn't trigger a full
17377
- // `readAllMemories` scan per query (addresses codex P2 on PR 4).
17378
- const counters = new Map<string, MemoryWorthCounters>();
17379
- const seenNamespaces = new Set<string>();
17380
- const nowMs = Date.now();
17381
-
17382
- // Evict all expired entries on every call so long-running processes
17383
- // touching a high-cardinality namespace set (coding/project overlays,
17384
- // per-branch) don't grow the cache unboundedly. Without this, an entry
17385
- // for a namespace that's never looked up again would pin its full
17386
- // counter map forever.
17387
- for (const [key, entry] of this.memoryWorthCounterCache) {
17388
- if (nowMs - entry.at >= Orchestrator.MEMORY_WORTH_CACHE_TTL_MS) {
17389
- this.memoryWorthCounterCache.delete(key);
17390
- }
17391
- }
17392
-
17393
- for (const ns of namespaces) {
17394
- if (seenNamespaces.has(ns)) continue;
17395
- seenNamespaces.add(ns);
17396
- try {
17397
- const cached = this.memoryWorthCounterCache.get(ns);
17398
- let nsMap: ReadonlyMap<string, MemoryWorthCounters> | undefined;
17399
- if (
17400
- cached &&
17401
- nowMs - cached.at < Orchestrator.MEMORY_WORTH_CACHE_TTL_MS
17402
- ) {
17403
- nsMap = cached.counters;
17404
- } else {
17405
- const storage = await this.getStorage(ns);
17406
- const memories = await storage.readAllMemories();
17407
- nsMap = buildMemoryWorthCounterMap(memories);
17408
- this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
17409
- }
17410
- for (const [path, c] of nsMap) counters.set(path, c);
17411
- } catch (err) {
17412
- log.debug("memory-worth: failed to read namespace, skipping", {
17413
- namespace: ns,
17414
- error: (err as Error).message,
17415
- });
17416
- }
17417
- }
17418
-
17419
- // For candidates whose path didn't show up in any hot-tier namespace
17420
- // scan (typical of cold-tier / archive fallback), try a direct
17421
- // per-path read. Without this, cold-tier candidates always stay at
17422
- // multiplier 1.0 even when they have outcome history. Errors are
17423
- // swallowed so a single unreadable archive entry can't break the
17424
- // whole recall.
17425
- const missing = results.filter((r) => !counters.has(r.path));
17426
- if (missing.length > 0) {
17427
- // Use the first-seen namespace's storage as the reader — all
17428
- // StorageManagers share the same on-disk format, and
17429
- // `readMemoryByPath` takes an absolute path so the baseDir doesn't
17430
- // have to match.
17431
- let reader: StorageManager | null = null;
17432
- for (const ns of namespaces) {
17433
- try {
17434
- reader = await this.getStorage(ns);
17435
- break;
17436
- } catch {
17437
- // try next namespace
17438
- }
17439
- }
17440
- if (reader) {
17441
- for (const r of missing) {
17442
- try {
17443
- const memory = await this.readQmdResultMemory(r.path, reader, namespaces);
17444
- if (!memory) continue;
17445
- const fm = memory.frontmatter;
17446
- if (fm.mw_success === undefined && fm.mw_fail === undefined) continue;
17447
- counters.set(r.path, {
17448
- mw_success: fm.mw_success,
17449
- mw_fail: fm.mw_fail,
17450
- lastAccessed: fm.lastAccessed,
17451
- });
17452
- } catch (err) {
17453
- log.debug("memory-worth: direct path lookup failed", {
17454
- path: r.path,
17455
- error: (err as Error).message,
17456
- });
17457
- }
17458
- }
17459
- }
17460
- }
17461
-
17462
- // If no memory in the candidate set has any counter data, the filter
17463
- // would be a no-op — skip the reorder to avoid spurious log spam.
17464
- if (counters.size === 0) return results;
17465
-
17466
- // Preserve upstream ordering (reranker, specialized tiers, etc.) for
17467
- // neutral candidates. The upstream stages set `memoryResults` in their
17468
- // intended order but often leave `r.score` as the raw QMD score. If we
17469
- // sorted by `r.score * multiplier` directly, neutral candidates
17470
- // (multiplier 1.0) would snap back to raw-QMD order and silently undo
17471
- // the reranker. Feed the filter a synthetic monotone-decreasing rank
17472
- // score so it uses input position as the baseline, then applies the
17473
- // multiplier on top. Ties fall back to the stable secondary key in
17474
- // `applyMemoryWorthFilter`.
17475
- const rankedInputs = results.map((r, i) => ({
17476
- path: r.path,
17477
- // Large positive rank score so multiplier math stays well-scaled and
17478
- // we never hit zero; descending so earlier items rank higher.
17479
- score: results.length - i,
17480
- }));
17481
- const filtered = applyMemoryWorthFilter(rankedInputs, {
17482
- counters,
17483
- now: new Date(),
17484
- halfLifeMs:
17485
- this.config.recallMemoryWorthHalfLifeMs > 0
17486
- ? this.config.recallMemoryWorthHalfLifeMs
17487
- : undefined,
17488
- });
17489
-
17490
- // Reconstruct the QmdSearchResult list in the new order. `.score` is
17491
- // preserved from the upstream pipeline (rerank, tier scoring, etc.) —
17492
- // we only reorder. Writing the synthetic rank-weighted score back
17493
- // would contaminate downstream logic (telemetry, confidence gates)
17494
- // that expects the original QMD/rerank score semantics.
17495
- const byPath = new Map(results.map((r) => [r.path, r]));
17496
- const reordered: QmdSearchResult[] = [];
17497
- for (const item of filtered) {
17498
- const original = byPath.get(item.path);
17499
- if (original) reordered.push(original);
17500
- }
17501
- return reordered;
17161
+ return this.recallRerankCoordinator.applyMemoryWorthRerank(results, namespaces);
17502
17162
  }
17503
17163
 
17504
- /**
17505
- * Issue #1577 — unified TrustScore recall stage. Thin wiring over the pure
17506
- * {@link applyTrustScoreStage} scorer + the {@link buildTrustSignalsForRerank}
17507
- * signal builder. The stage subsumes the Memory Worth multiplier — the
17508
- * orchestrator runs exactly one of the two (mutual exclusion, rule 39; the
17509
- * double-multiplier test in trust-score-stage.test.ts pins it structurally).
17510
- *
17511
- * Returns the admitted results AND the per-path trust map (including
17512
- * quarantined items) so the caller can: (a) render epistemic hedges, (b)
17513
- * surface quarantined items in X-ray with a reason (rule 34), and (c) filter
17514
- * quarantined paths from fallback recall branches. The trust map is a
17515
- * per-recall local — never instance state — so concurrent recalls cannot
17516
- * race on it (review: shared-trust-map concurrency).
17517
- */
17518
17164
  private async applyTrustScoreRerank(
17519
17165
  results: QmdSearchResult[],
17520
17166
  namespaces: string[],
@@ -17522,74 +17168,9 @@ export class Orchestrator {
17522
17168
  results: QmdSearchResult[];
17523
17169
  trustByPath: Map<string, TrustStageResultItem> | null;
17524
17170
  }> {
17525
- if (results.length === 0) return { results, trustByPath: null };
17526
- const now = new Date();
17527
- const halfLifeDays =
17528
- this.config.recallMemoryWorthHalfLifeMs > 0
17529
- ? this.config.recallMemoryWorthHalfLifeMs / (24 * 60 * 60 * 1000)
17530
- : undefined;
17531
- // Cold-tier direct-fallback reader: resolve once, reuse for every missing
17532
- // candidate (mirrors the memory-worth filter's reader selection).
17533
- let fallbackReader: StorageManager | null = null;
17534
- const signals = await buildTrustSignalsForRerank(
17535
- results.map((r) => r.path),
17536
- namespaces,
17537
- {
17538
- readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
17539
- readMemoryFrontmatter: async (path) => {
17540
- if (!fallbackReader) {
17541
- for (const ns of namespaces) {
17542
- try {
17543
- fallbackReader = await this.getStorage(ns);
17544
- break;
17545
- } catch {
17546
- // try next namespace
17547
- }
17548
- }
17549
- }
17550
- if (!fallbackReader) return null;
17551
- const memory = await this.readQmdResultMemory(path, fallbackReader, namespaces);
17552
- return memory ? memory.frontmatter : null;
17553
- },
17554
- },
17555
- { cache: this.trustSignalCache, ttlMs: Orchestrator.TRUST_SIGNAL_CACHE_TTL_MS },
17556
- now,
17557
- {
17558
- recencyHalfLifeDays: halfLifeDays,
17559
- logDebug: (message, context) => log.debug(message, context),
17560
- },
17561
- );
17562
- if (signals.size === 0) {
17563
- return { results, trustByPath: null };
17564
- }
17565
- // Synthetic monotone-decreasing rank so the multiplier rebias is applied
17566
- // on top of upstream ordering, not raw QMD scores (see applyMemoryWorthRerank).
17567
- const rankedInputs = results.map((r, i) => ({ path: r.path, score: results.length - i }));
17568
- const stage = applyTrustScoreStage(rankedInputs, {
17569
- signals,
17570
- weights: this.config.trustScoreWeights,
17571
- minMultiplier: this.config.trustScoreMinMultiplier,
17572
- maxMultiplier: this.config.trustScoreMaxMultiplier,
17573
- quarantine: this.config.trustScoreQuarantine,
17574
- });
17575
- const trustByPath = new Map(stage.all.map((item) => [item.path, item]));
17576
- const byPath = new Map(results.map((r) => [r.path, r]));
17577
- const admitted = stage.admitted
17578
- .map((item) => byPath.get(item.path))
17579
- .filter((r): r is QmdSearchResult => r !== undefined);
17580
- return { results: admitted, trustByPath };
17171
+ return this.recallRerankCoordinator.applyTrustScoreRerank(results, namespaces);
17581
17172
  }
17582
17173
 
17583
- /**
17584
- * Issue #1577 — apply the TrustScore stage (or, when trust is off, the Memory
17585
- * Worth multiplier fallback) to ONE recall branch's results, returning the
17586
- * scored results + the per-path trust map. Thin wiring over
17587
- * {@link applyTrustScoreRerank} so every recall path — hot QMD, embedding
17588
- * fallback, recent scan — applies the SAME multiplier gate (rule 41: a
17589
- * feature gate must apply across ALL parallel recall paths). TrustScore
17590
- * subsumes Memory Worth; exactly one runs (rule 39). Fail-open on lookup
17591
- * errors so a storage hiccup never breaks a fallback path.
17592
- */
17593
17174
  private async applyTrustScoreToBranch(
17594
17175
  results: QmdSearchResult[],
17595
17176
  namespaces: string[],
@@ -17599,25 +17180,7 @@ export class Orchestrator {
17599
17180
  results: QmdSearchResult[];
17600
17181
  trustByPath: Map<string, TrustStageResultItem> | null;
17601
17182
  }> {
17602
- if (caps.recallTrustScore && results.length > 0) {
17603
- try {
17604
- return await this.applyTrustScoreRerank(results, namespaces);
17605
- } catch (err) {
17606
- log.debug(`trust-score stage (${label}) failed open`, {
17607
- error: (err as Error).message,
17608
- });
17609
- }
17610
- } else if (caps.recallMemoryWorthFilter && results.length > 0) {
17611
- try {
17612
- const filtered = await this.applyMemoryWorthRerank(results, namespaces);
17613
- return { results: filtered, trustByPath: null };
17614
- } catch (err) {
17615
- log.debug(`memory-worth filter (${label}) failed open`, {
17616
- error: (err as Error).message,
17617
- });
17618
- }
17619
- }
17620
- return { results, trustByPath: null };
17183
+ return this.recallRerankCoordinator.applyTrustScoreToBranch(results, namespaces, caps, label);
17621
17184
  }
17622
17185
 
17623
17186
  private diversifyAndLimitRecallResults(
@@ -17625,89 +17188,17 @@ export class Orchestrator {
17625
17188
  results: QmdSearchResult[],
17626
17189
  limit: number,
17627
17190
  retrievalQuery?: string,
17628
- // `caps` is additive AND last (issue #1523) so the positional call shape
17629
- // stays backward-compatible: the recall pipeline threads a resolved set,
17630
- // but callers that omit it (e.g. direct unit-test invocations) get an
17631
- // equivalent set derived from the same config — behavior-preserving.
17632
17191
  caps: CapabilitySet = resolveCapabilities(this.config),
17633
17192
  ): QmdSearchResult[] {
17634
- const safeLimit =
17635
- typeof limit === "number" && Number.isFinite(limit)
17636
- ? Math.max(0, Math.floor(limit))
17637
- : 0;
17638
- if (!Array.isArray(results) || results.length === 0) return [];
17639
- // `recallResultLimit === 0` is a true zero limit (e.g. when
17640
- // `memoriesSectionEnabled` is false) and must return an empty array so
17641
- // the memories section is genuinely skipped. This mirrors the
17642
- // `slice(0, 0)` semantics of every call site this helper replaced.
17643
- if (safeLimit === 0) return [];
17644
- // Issue #564 PR 3: when the feature flag is on, boost reasoning_trace
17645
- // memories for problem-solving asks so they bubble up ahead of ordinary
17646
- // facts/decisions before MMR picks the final section. No-op when the
17647
- // flag is off or the query is not a problem-solving ask.
17648
- const boosted =
17649
- caps.recallReasoningTraceBoost && typeof retrievalQuery === "string"
17650
- ? applyReasoningTraceBoost(results, {
17651
- enabled: true,
17652
- query: retrievalQuery,
17653
- })
17654
- : results;
17655
- const diversified = this.applyMmrToQmdResults(sectionId, boosted, caps);
17656
- return diversified.slice(0, safeLimit);
17193
+ return this.recallRerankCoordinator.diversifyAndLimitRecallResults(sectionId, results, limit, retrievalQuery, caps);
17657
17194
  }
17658
17195
 
17659
- /**
17660
- * Apply Maximal Marginal Relevance to a section's ordered candidate list.
17661
- *
17662
- * Operates per-section so one redundant cluster cannot dominate a section,
17663
- * and so one section's MMR pass cannot starve other sections. Returns the
17664
- * input unchanged when disabled, when there are fewer than 2 candidates, or
17665
- * when no budget information is available.
17666
- */
17667
17196
  private applyMmrToQmdResults(
17668
17197
  sectionId: string,
17669
17198
  results: QmdSearchResult[],
17670
- // Additive `caps` (issue #1523); defaults to a config-derived set so direct
17671
- // callers that omit it behave identically to the threaded recall path.
17672
17199
  caps: CapabilitySet = resolveCapabilities(this.config),
17673
17200
  ): QmdSearchResult[] {
17674
- if (!caps.recallMmr) return results;
17675
- if (!Array.isArray(results) || results.length < 2) return results;
17676
-
17677
- // Config is runtime API (see AGENTS.md §4): preserve `0` as a true zero
17678
- // limit rather than coercing it to a non-zero value. A configured topN of
17679
- // 0 means "apply MMR over an empty window" — i.e. skip the reorder and
17680
- // return the upstream candidates unchanged. This keeps read-time
17681
- // behavior symmetric with the write-time semantics parseConfig exposes.
17682
- const configuredTopN = this.config.recallMmrTopN;
17683
- const topN =
17684
- typeof configuredTopN === "number" && Number.isFinite(configuredTopN)
17685
- ? Math.max(0, Math.floor(configuredTopN))
17686
- : 40;
17687
- if (topN === 0) return results;
17688
- const lambda = this.config.recallMmrLambda ?? 0.7;
17689
-
17690
- // Delegate to the pure helper so candidate keying (path-first, index
17691
- // suffixed for uniqueness) and the head-of-list diversity metric are
17692
- // exercised by the same code path that the unit tests cover.
17693
- const { reordered, diversity } = reorderRecallResultsWithMmr(results, {
17694
- lambda,
17695
- topN,
17696
- });
17697
-
17698
- try {
17699
- log.info(
17700
- `recall_mmr: section=${sectionId} kept=${diversity.kept}/${diversity.considered} ` +
17701
- `headReorderCount=${diversity.headReorderCount} ` +
17702
- `avgSimBefore=${diversity.avgPairwiseSimBefore.toFixed(3)} ` +
17703
- `avgSimAfter=${diversity.avgPairwiseSimAfter.toFixed(3)} ` +
17704
- `lambda=${lambda.toFixed(2)}`,
17705
- );
17706
- } catch {
17707
- // Metrics must never break recall.
17708
- }
17709
-
17710
- return reordered;
17201
+ return this.recallRerankCoordinator.applyMmrToQmdResults(sectionId, results, caps);
17711
17202
  }
17712
17203
 
17713
17204
  private buildLastRecallBudgetSummary(options: {
@@ -18389,7 +17880,7 @@ export class Orchestrator {
18389
17880
  * Updates are batched in memory and flushed during consolidation.
18390
17881
  */
18391
17882
  trackMemoryAccess(memoryIds: string[]): void {
18392
- if (!this.config.accessTrackingEnabled) return;
17883
+ if (!resolveRecallEnhancementCapabilities(this.config).accessTracking) return;
18393
17884
 
18394
17885
  const now = new Date().toISOString();
18395
17886
  for (const id of memoryIds) {
@@ -18824,7 +18315,7 @@ export class Orchestrator {
18824
18315
  ]);
18825
18316
 
18826
18317
  const queryIntent =
18827
- this.config.intentRoutingEnabled && prompt
18318
+ resolveConversationContextCapabilities(this.config).intentRouting && prompt
18828
18319
  ? inferIntentFromText(prompt)
18829
18320
  : null;
18830
18321
 
@@ -18892,7 +18383,7 @@ export class Orchestrator {
18892
18383
  }
18893
18384
 
18894
18385
  // Feedback bias (v2.2): apply small user-provided up/down vote adjustments.
18895
- if (this.config.feedbackEnabled) {
18386
+ if (resolveRecallEnhancementCapabilities(this.config).feedback) {
18896
18387
  const match = memory.path.match(/([^/]+)\.md$/);
18897
18388
  const memoryId = match ? match[1] : null;
18898
18389
  if (memoryId) {
@@ -18906,7 +18397,7 @@ export class Orchestrator {
18906
18397
  }
18907
18398
 
18908
18399
  // Negative examples (v2.2): apply a small penalty for memories repeatedly marked "not useful".
18909
- if (this.config.negativeExamplesEnabled) {
18400
+ if (resolvePipelineProcessingCapabilities(this.config).negativeExamples) {
18910
18401
  const match = memory.path.match(/([^/]+)\.md$/);
18911
18402
  const memoryId = match ? match[1] : null;
18912
18403
  if (memoryId) {
@@ -18978,7 +18469,7 @@ export class Orchestrator {
18978
18469
  // Gated by reinforcementRecallBoostEnabled (default false).
18979
18470
  let reinforcementBoost = 0;
18980
18471
  if (
18981
- this.config.reinforcementRecallBoostEnabled &&
18472
+ resolveRecallEnhancementCapabilities(this.config).reinforcementRecallBoost &&
18982
18473
  typeof memory.frontmatter.reinforcement_count === "number" &&
18983
18474
  memory.frontmatter.reinforcement_count > 0
18984
18475
  ) {