open-multi-agent-kit 0.96.1 → 0.97.0

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 (267) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +14 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/resource-doctor-cli.d.ts +22 -0
  7. package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
  8. package/dist/commands/resource-doctor-cli.js +98 -0
  9. package/dist/commands/resource-doctor-cli.js.map +1 -0
  10. package/dist/core/agent-session.d.ts +46 -2
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +373 -12
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/attachment-store.d.ts +39 -0
  15. package/dist/core/attachment-store.d.ts.map +1 -0
  16. package/dist/core/attachment-store.js +81 -0
  17. package/dist/core/attachment-store.js.map +1 -0
  18. package/dist/core/command-safety.d.ts +8 -0
  19. package/dist/core/command-safety.d.ts.map +1 -1
  20. package/dist/core/command-safety.js +140 -14
  21. package/dist/core/command-safety.js.map +1 -1
  22. package/dist/core/compaction/compaction.d.ts +6 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +35 -1
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/completion-sound.d.ts +109 -0
  27. package/dist/core/completion-sound.d.ts.map +1 -0
  28. package/dist/core/completion-sound.js +165 -0
  29. package/dist/core/completion-sound.js.map +1 -0
  30. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  31. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  32. package/dist/core/context-budget-governor-v2.js +1 -1
  33. package/dist/core/context-budget-governor-v2.js.map +1 -1
  34. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  35. package/dist/core/context-budget-system-prompt-items.js +2 -2
  36. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  37. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  38. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  39. package/dist/core/context-budget-v2-cache-keys.js +2 -0
  40. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  41. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  42. package/dist/core/context-budget-v2-plan-cache-keys.js +3 -0
  43. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  44. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  45. package/dist/core/context-budget-v2-planner.js +2 -1
  46. package/dist/core/context-budget-v2-planner.js.map +1 -1
  47. package/dist/core/context-budget-v2-scoring.d.ts +17 -0
  48. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  49. package/dist/core/context-budget-v2-scoring.js +25 -8
  50. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  51. package/dist/core/context-budget-v2-selection.d.ts +2 -2
  52. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  53. package/dist/core/context-budget-v2-selection.js +18 -1
  54. package/dist/core/context-budget-v2-selection.js.map +1 -1
  55. package/dist/core/context-budget-v2-types.d.ts +17 -0
  56. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  57. package/dist/core/context-budget-v2-types.js +15 -0
  58. package/dist/core/context-budget-v2-types.js.map +1 -1
  59. package/dist/core/extensions/builtin/command-safety-gate.d.ts +3 -1
  60. package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/command-safety-gate.js +7 -0
  62. package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
  63. package/dist/core/extensions/types.d.ts +12 -1
  64. package/dist/core/extensions/types.d.ts.map +1 -1
  65. package/dist/core/extensions/types.js.map +1 -1
  66. package/dist/core/host-resource-probe.d.ts +63 -0
  67. package/dist/core/host-resource-probe.d.ts.map +1 -0
  68. package/dist/core/host-resource-probe.js +126 -0
  69. package/dist/core/host-resource-probe.js.map +1 -0
  70. package/dist/core/host-resource-snapshot.d.ts +54 -0
  71. package/dist/core/host-resource-snapshot.d.ts.map +1 -0
  72. package/dist/core/host-resource-snapshot.js +139 -0
  73. package/dist/core/host-resource-snapshot.js.map +1 -0
  74. package/dist/core/model-registry.d.ts +8 -3
  75. package/dist/core/model-registry.d.ts.map +1 -1
  76. package/dist/core/model-registry.js +71 -1
  77. package/dist/core/model-registry.js.map +1 -1
  78. package/dist/core/package-doctor-source-scan.d.ts +9 -0
  79. package/dist/core/package-doctor-source-scan.d.ts.map +1 -1
  80. package/dist/core/package-doctor-source-scan.js +77 -4
  81. package/dist/core/package-doctor-source-scan.js.map +1 -1
  82. package/dist/core/prompt-attachment.d.ts +51 -0
  83. package/dist/core/prompt-attachment.d.ts.map +1 -0
  84. package/dist/core/prompt-attachment.js +186 -0
  85. package/dist/core/prompt-attachment.js.map +1 -0
  86. package/dist/core/prompt-settlement.d.ts +81 -0
  87. package/dist/core/prompt-settlement.d.ts.map +1 -0
  88. package/dist/core/prompt-settlement.js +84 -0
  89. package/dist/core/prompt-settlement.js.map +1 -0
  90. package/dist/core/provider-resilience.d.ts +28 -0
  91. package/dist/core/provider-resilience.d.ts.map +1 -1
  92. package/dist/core/provider-resilience.js +51 -1
  93. package/dist/core/provider-resilience.js.map +1 -1
  94. package/dist/core/provider-retry.d.ts +7 -0
  95. package/dist/core/provider-retry.d.ts.map +1 -1
  96. package/dist/core/provider-retry.js +24 -0
  97. package/dist/core/provider-retry.js.map +1 -1
  98. package/dist/core/provider-usage.d.ts +15 -2
  99. package/dist/core/provider-usage.d.ts.map +1 -1
  100. package/dist/core/provider-usage.js +176 -2
  101. package/dist/core/provider-usage.js.map +1 -1
  102. package/dist/core/reasoning-router-promotion.d.ts +84 -0
  103. package/dist/core/reasoning-router-promotion.d.ts.map +1 -0
  104. package/dist/core/reasoning-router-promotion.js +92 -0
  105. package/dist/core/reasoning-router-promotion.js.map +1 -0
  106. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  107. package/dist/core/reasoning-router-resolver.js +1 -1
  108. package/dist/core/reasoning-router-resolver.js.map +1 -1
  109. package/dist/core/resource-admission-config.d.ts +55 -0
  110. package/dist/core/resource-admission-config.d.ts.map +1 -0
  111. package/dist/core/resource-admission-config.js +97 -0
  112. package/dist/core/resource-admission-config.js.map +1 -0
  113. package/dist/core/resource-admission.d.ts +81 -0
  114. package/dist/core/resource-admission.d.ts.map +1 -0
  115. package/dist/core/resource-admission.js +128 -0
  116. package/dist/core/resource-admission.js.map +1 -0
  117. package/dist/core/resource-governor-format.d.ts +50 -0
  118. package/dist/core/resource-governor-format.d.ts.map +1 -0
  119. package/dist/core/resource-governor-format.js +113 -0
  120. package/dist/core/resource-governor-format.js.map +1 -0
  121. package/dist/core/resource-governor-settings.d.ts +56 -0
  122. package/dist/core/resource-governor-settings.d.ts.map +1 -0
  123. package/dist/core/resource-governor-settings.js +89 -0
  124. package/dist/core/resource-governor-settings.js.map +1 -0
  125. package/dist/core/resource-observation-journal.d.ts +74 -0
  126. package/dist/core/resource-observation-journal.d.ts.map +1 -0
  127. package/dist/core/resource-observation-journal.js +198 -0
  128. package/dist/core/resource-observation-journal.js.map +1 -0
  129. package/dist/core/resource-safety-gate.d.ts +54 -0
  130. package/dist/core/resource-safety-gate.d.ts.map +1 -0
  131. package/dist/core/resource-safety-gate.js +51 -0
  132. package/dist/core/resource-safety-gate.js.map +1 -0
  133. package/dist/core/run-journal.d.ts.map +1 -1
  134. package/dist/core/run-journal.js +13 -1
  135. package/dist/core/run-journal.js.map +1 -1
  136. package/dist/core/run-resource-lease.d.ts +73 -0
  137. package/dist/core/run-resource-lease.d.ts.map +1 -0
  138. package/dist/core/run-resource-lease.js +78 -0
  139. package/dist/core/run-resource-lease.js.map +1 -0
  140. package/dist/core/session-bash-service.d.ts +12 -0
  141. package/dist/core/session-bash-service.d.ts.map +1 -1
  142. package/dist/core/session-bash-service.js +18 -3
  143. package/dist/core/session-bash-service.js.map +1 -1
  144. package/dist/core/session-system-prompt.d.ts +2 -0
  145. package/dist/core/session-system-prompt.d.ts.map +1 -1
  146. package/dist/core/session-system-prompt.js +2 -0
  147. package/dist/core/session-system-prompt.js.map +1 -1
  148. package/dist/core/session-termination.d.ts +8 -3
  149. package/dist/core/session-termination.d.ts.map +1 -1
  150. package/dist/core/session-termination.js +40 -3
  151. package/dist/core/session-termination.js.map +1 -1
  152. package/dist/core/settings-manager.d.ts +12 -0
  153. package/dist/core/settings-manager.d.ts.map +1 -1
  154. package/dist/core/settings-manager.js +36 -0
  155. package/dist/core/settings-manager.js.map +1 -1
  156. package/dist/core/slash-commands.d.ts.map +1 -1
  157. package/dist/core/slash-commands.js +1 -0
  158. package/dist/core/slash-commands.js.map +1 -1
  159. package/dist/core/subagent-lane-launcher.d.ts +64 -0
  160. package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
  161. package/dist/core/subagent-lane-launcher.js +107 -0
  162. package/dist/core/subagent-lane-launcher.js.map +1 -0
  163. package/dist/core/system-cpu-sampler.d.ts +45 -0
  164. package/dist/core/system-cpu-sampler.d.ts.map +1 -0
  165. package/dist/core/system-cpu-sampler.js +95 -0
  166. package/dist/core/system-cpu-sampler.js.map +1 -0
  167. package/dist/core/system-prompt.d.ts.map +1 -1
  168. package/dist/core/system-prompt.js +5 -8
  169. package/dist/core/system-prompt.js.map +1 -1
  170. package/dist/core/tools/bash.d.ts.map +1 -1
  171. package/dist/core/tools/bash.js +3 -7
  172. package/dist/core/tools/bash.js.map +1 -1
  173. package/dist/core/tools/grep.d.ts.map +1 -1
  174. package/dist/core/tools/grep.js +13 -48
  175. package/dist/core/tools/grep.js.map +1 -1
  176. package/dist/core/tools/read.d.ts.map +1 -1
  177. package/dist/core/tools/read.js +1 -34
  178. package/dist/core/tools/read.js.map +1 -1
  179. package/dist/core/workload-classifier.d.ts +32 -0
  180. package/dist/core/workload-classifier.d.ts.map +1 -0
  181. package/dist/core/workload-classifier.js +66 -0
  182. package/dist/core/workload-classifier.js.map +1 -0
  183. package/dist/core/workload-family-matcher.d.ts +3 -0
  184. package/dist/core/workload-family-matcher.d.ts.map +1 -0
  185. package/dist/core/workload-family-matcher.js +126 -0
  186. package/dist/core/workload-family-matcher.js.map +1 -0
  187. package/dist/core/workload-permit-pool.d.ts +78 -0
  188. package/dist/core/workload-permit-pool.d.ts.map +1 -0
  189. package/dist/core/workload-permit-pool.js +138 -0
  190. package/dist/core/workload-permit-pool.js.map +1 -0
  191. package/dist/core/workload-shard-executor.d.ts +68 -0
  192. package/dist/core/workload-shard-executor.d.ts.map +1 -0
  193. package/dist/core/workload-shard-executor.js +156 -0
  194. package/dist/core/workload-shard-executor.js.map +1 -0
  195. package/dist/core/workload-shard-plan.d.ts +81 -0
  196. package/dist/core/workload-shard-plan.d.ts.map +1 -0
  197. package/dist/core/workload-shard-plan.js +183 -0
  198. package/dist/core/workload-shard-plan.js.map +1 -0
  199. package/dist/core/workload-shard-runner.d.ts +11 -0
  200. package/dist/core/workload-shard-runner.d.ts.map +1 -0
  201. package/dist/core/workload-shard-runner.js +98 -0
  202. package/dist/core/workload-shard-runner.js.map +1 -0
  203. package/dist/core/workload-shard-store.d.ts +44 -0
  204. package/dist/core/workload-shard-store.d.ts.map +1 -0
  205. package/dist/core/workload-shard-store.js +189 -0
  206. package/dist/core/workload-shard-store.js.map +1 -0
  207. package/dist/core/workload-sharders.d.ts +47 -0
  208. package/dist/core/workload-sharders.d.ts.map +1 -0
  209. package/dist/core/workload-sharders.js +162 -0
  210. package/dist/core/workload-sharders.js.map +1 -0
  211. package/dist/core/workload-shell-scan.d.ts +12 -0
  212. package/dist/core/workload-shell-scan.d.ts.map +1 -0
  213. package/dist/core/workload-shell-scan.js +79 -0
  214. package/dist/core/workload-shell-scan.js.map +1 -0
  215. package/dist/index.d.ts +1 -1
  216. package/dist/index.d.ts.map +1 -1
  217. package/dist/index.js +1 -1
  218. package/dist/index.js.map +1 -1
  219. package/dist/main.d.ts.map +1 -1
  220. package/dist/main.js +6 -0
  221. package/dist/main.js.map +1 -1
  222. package/dist/modes/interactive/components/attachment-strip.d.ts +21 -0
  223. package/dist/modes/interactive/components/attachment-strip.d.ts.map +1 -0
  224. package/dist/modes/interactive/components/attachment-strip.js +136 -0
  225. package/dist/modes/interactive/components/attachment-strip.js.map +1 -0
  226. package/dist/modes/interactive/components/footer.d.ts +6 -0
  227. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/footer.js +26 -1
  229. package/dist/modes/interactive/components/footer.js.map +1 -1
  230. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/status-sidebar.js +12 -1
  232. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  233. package/dist/modes/interactive/interactive-mode.d.ts +27 -4
  234. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  235. package/dist/modes/interactive/interactive-mode.js +255 -53
  236. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  237. package/docs/compaction.md +1 -1
  238. package/docs/extensions.md +12 -3
  239. package/docs/index.md +4 -1
  240. package/docs/metrics.md +74 -2
  241. package/docs/provider-resilience.md +11 -0
  242. package/docs/providers.md +1 -1
  243. package/docs/sdk.md +4 -0
  244. package/docs/settings.md +64 -2
  245. package/docs/skills.md +16 -0
  246. package/docs/usage.md +1 -1
  247. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  248. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  249. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  250. package/examples/extensions/gondolin/package-lock.json +2 -2
  251. package/examples/extensions/gondolin/package.json +1 -1
  252. package/examples/extensions/sandbox/package-lock.json +2 -2
  253. package/examples/extensions/sandbox/package.json +1 -1
  254. package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
  255. package/examples/extensions/subagent/domain-profiles.ts +0 -1
  256. package/examples/extensions/with-deps/package-lock.json +2 -2
  257. package/examples/extensions/with-deps/package.json +1 -1
  258. package/npm-shrinkwrap.json +18 -18
  259. package/package.json +8 -10
  260. package/dist/core/tools/omp-pure-seams.d.ts +0 -41
  261. package/dist/core/tools/omp-pure-seams.d.ts.map +0 -1
  262. package/dist/core/tools/omp-pure-seams.js +0 -142
  263. package/dist/core/tools/omp-pure-seams.js.map +0 -1
  264. package/dist/core/tools/omp-seam-runtime.d.ts +0 -100
  265. package/dist/core/tools/omp-seam-runtime.d.ts.map +0 -1
  266. package/dist/core/tools/omp-seam-runtime.js +0 -40
  267. package/dist/core/tools/omp-seam-runtime.js.map +0 -1
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Promotion gate for learned reasoning-router weights.
3
+ *
4
+ * The router already produces a feedback ledger (`router-feedback-collector.ts`)
5
+ * and an offline calibration stack (`scripts/reasoning-router/`: calibration,
6
+ * held-out evaluation, golden diffing, and McNemar's exact test). What was
7
+ * missing is the decision policy that makes automatic promotion trustworthy:
8
+ * without it the loop is open and every weight change is a judgement call.
9
+ *
10
+ * This module is that policy, as a pure total function. It deliberately refuses
11
+ * to promote on anything short of unanimous evidence:
12
+ *
13
+ * - a statistically significant win on the held-in gold set (McNemar's exact
14
+ * test), computed by the caller and passed in;
15
+ * - enough discordant pairs for that test to mean anything;
16
+ * - the candidate — not the baseline — winning those pairs;
17
+ * - no accuracy regression on a held-out split the calibration never saw;
18
+ * - no unreviewed behavior changes surfaced by golden diffing; and
19
+ * - an explicit human approval.
20
+ *
21
+ * "The agent tried again" is not the same as "the harness improved", so a
22
+ * candidate that merely fails to be worse is rejected, and malformed evidence
23
+ * fails closed rather than defaulting to promotion.
24
+ */
25
+ /** Held-in McNemar outcome. Wins are counted in discordant pairs only. */
26
+ export interface RouterHeldInEvidence {
27
+ /** Rows the baseline classified correctly and the candidate got wrong. */
28
+ readonly baselineWins: number;
29
+ /** Rows the candidate classified correctly and the baseline got wrong. */
30
+ readonly candidateWins: number;
31
+ readonly pValue: number;
32
+ readonly significant: boolean;
33
+ }
34
+ /** Accuracy on a split withheld from calibration. */
35
+ export interface RouterHoldoutEvidence {
36
+ readonly baselineCorrect: number;
37
+ readonly candidateCorrect: number;
38
+ readonly total: number;
39
+ }
40
+ /** Everything the gate is allowed to consider. */
41
+ export interface RouterPromotionEvidence {
42
+ readonly heldIn: RouterHeldInEvidence;
43
+ readonly holdout: RouterHoldoutEvidence;
44
+ /** Golden-diff changes still unreviewed at decision time. */
45
+ readonly goldenChanges: number;
46
+ /** True only when a human explicitly approved this exact candidate. */
47
+ readonly humanApproved: boolean;
48
+ }
49
+ /** Tunable thresholds. Loosening these can never authorize a regression. */
50
+ export interface RouterPromotionPolicy {
51
+ /** Minimum discordant pairs before McNemar's verdict is trusted. */
52
+ readonly minDiscordant: number;
53
+ /** Minimum held-out rows before "no regression" is meaningful. */
54
+ readonly minHoldout: number;
55
+ /** Maximum unreviewed golden-diff changes tolerated. */
56
+ readonly maxGoldenChanges: number;
57
+ readonly requireHumanApproval: boolean;
58
+ }
59
+ export type RouterPromotionBlocker = "malformed_evidence" | "insufficient_discordant_pairs" | "mcnemar_not_significant" | "candidate_not_favored" | "holdout_regression" | "insufficient_holdout" | "unreviewed_behavior_changes" | "human_approval_missing";
60
+ export interface RouterPromotionVerdict {
61
+ readonly promote: boolean;
62
+ /** Every independent reason promotion was refused, in evaluation order. */
63
+ readonly blockers: readonly RouterPromotionBlocker[];
64
+ }
65
+ /**
66
+ * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a
67
+ * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior
68
+ * changes by regenerating the golden set, never by tolerating drift.
69
+ *
70
+ * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per
71
+ * class × 7 classes). A higher bar would be unsatisfiable rather than strict,
72
+ * and a gate that can never pass is a gate nobody runs.
73
+ */
74
+ export declare const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy;
75
+ /**
76
+ * Decide whether a candidate weight set may replace the active one.
77
+ *
78
+ * Returns every independent blocker rather than the first, so one calibration
79
+ * run reports the full distance to promotion instead of revealing it one
80
+ * rejection at a time. Malformed evidence short-circuits: the gate never
81
+ * reasons about numbers it cannot trust.
82
+ */
83
+ export declare function evaluateRouterPromotion(evidence: RouterPromotionEvidence, policy?: RouterPromotionPolicy): RouterPromotionVerdict;
84
+ //# sourceMappingURL=reasoning-router-promotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoning-router-promotion.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B;AAED,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACrC,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAC/B,oBAAoB,GACpB,+BAA+B,GAC/B,yBAAyB,GACzB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,GACtB,6BAA6B,GAC7B,wBAAwB,CAAC;AAE5B,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,EAAE,qBAK7C,CAAC;AAeF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,uBAAuB,EACjC,MAAM,GAAE,qBAAuD,GAC7D,sBAAsB,CA+BxB","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { goldenChanges, heldIn, holdout, humanApproved } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Promotion gate for learned reasoning-router weights.
3
+ *
4
+ * The router already produces a feedback ledger (`router-feedback-collector.ts`)
5
+ * and an offline calibration stack (`scripts/reasoning-router/`: calibration,
6
+ * held-out evaluation, golden diffing, and McNemar's exact test). What was
7
+ * missing is the decision policy that makes automatic promotion trustworthy:
8
+ * without it the loop is open and every weight change is a judgement call.
9
+ *
10
+ * This module is that policy, as a pure total function. It deliberately refuses
11
+ * to promote on anything short of unanimous evidence:
12
+ *
13
+ * - a statistically significant win on the held-in gold set (McNemar's exact
14
+ * test), computed by the caller and passed in;
15
+ * - enough discordant pairs for that test to mean anything;
16
+ * - the candidate — not the baseline — winning those pairs;
17
+ * - no accuracy regression on a held-out split the calibration never saw;
18
+ * - no unreviewed behavior changes surfaced by golden diffing; and
19
+ * - an explicit human approval.
20
+ *
21
+ * "The agent tried again" is not the same as "the harness improved", so a
22
+ * candidate that merely fails to be worse is rejected, and malformed evidence
23
+ * fails closed rather than defaulting to promotion.
24
+ */
25
+ /**
26
+ * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a
27
+ * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior
28
+ * changes by regenerating the golden set, never by tolerating drift.
29
+ *
30
+ * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per
31
+ * class × 7 classes). A higher bar would be unsatisfiable rather than strict,
32
+ * and a gate that can never pass is a gate nobody runs.
33
+ */
34
+ export const DEFAULT_ROUTER_PROMOTION_POLICY = {
35
+ maxGoldenChanges: 0,
36
+ minDiscordant: 20,
37
+ minHoldout: 40,
38
+ requireHumanApproval: true,
39
+ };
40
+ function isCount(value) {
41
+ return Number.isFinite(value) && value >= 0;
42
+ }
43
+ function isMalformed(evidence) {
44
+ const { goldenChanges, heldIn, holdout } = evidence;
45
+ if (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins))
46
+ return true;
47
+ if (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1)
48
+ return true;
49
+ if (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total))
50
+ return true;
51
+ if (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total)
52
+ return true;
53
+ return !isCount(goldenChanges);
54
+ }
55
+ /**
56
+ * Decide whether a candidate weight set may replace the active one.
57
+ *
58
+ * Returns every independent blocker rather than the first, so one calibration
59
+ * run reports the full distance to promotion instead of revealing it one
60
+ * rejection at a time. Malformed evidence short-circuits: the gate never
61
+ * reasons about numbers it cannot trust.
62
+ */
63
+ export function evaluateRouterPromotion(evidence, policy = DEFAULT_ROUTER_PROMOTION_POLICY) {
64
+ if (isMalformed(evidence)) {
65
+ return { blockers: ["malformed_evidence"], promote: false };
66
+ }
67
+ const { goldenChanges, heldIn, holdout, humanApproved } = evidence;
68
+ const blockers = [];
69
+ if (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {
70
+ blockers.push("insufficient_discordant_pairs");
71
+ }
72
+ if (!heldIn.significant) {
73
+ blockers.push("mcnemar_not_significant");
74
+ }
75
+ if (heldIn.candidateWins <= heldIn.baselineWins) {
76
+ blockers.push("candidate_not_favored");
77
+ }
78
+ if (holdout.total < policy.minHoldout) {
79
+ blockers.push("insufficient_holdout");
80
+ }
81
+ if (holdout.candidateCorrect < holdout.baselineCorrect) {
82
+ blockers.push("holdout_regression");
83
+ }
84
+ if (goldenChanges > policy.maxGoldenChanges) {
85
+ blockers.push("unreviewed_behavior_changes");
86
+ }
87
+ if (policy.requireHumanApproval && !humanApproved) {
88
+ blockers.push("human_approval_missing");
89
+ }
90
+ return { blockers, promote: blockers.length === 0 };
91
+ }
92
+ //# sourceMappingURL=reasoning-router-promotion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasoning-router-promotion.js","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAwDH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAA0B;IACrE,gBAAgB,EAAE,CAAC;IACnB,aAAa,EAAE,EAAE;IACjB,UAAU,EAAE,EAAE;IACd,oBAAoB,EAAE,IAAI;CAC1B,CAAC;AAEF,SAAS,OAAO,CAAC,KAAa,EAAW;IACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAAA,CAC5C;AAED,SAAS,WAAW,CAAC,QAAiC,EAAW;IAChE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IACpD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IACjF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3F,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpH,IAAI,OAAO,CAAC,eAAe,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,gBAAgB,GAAG,OAAO,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACrG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;AAAA,CAC/B;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACtC,QAAiC,EACjC,MAAM,GAA0B,+BAA+B,EACtC;IACzB,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7D,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,QAAQ,CAAC;IACnE,MAAM,QAAQ,GAA6B,EAAE,CAAC;IAE9C,IAAI,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;QACvE,QAAQ,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACjD,QAAQ,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,OAAO,CAAC,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,aAAa,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC7C,QAAQ,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,CAAC,oBAAoB,IAAI,CAAC,aAAa,EAAE,CAAC;QACnD,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,CACpD","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { goldenChanges, heldIn, holdout, humanApproved } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-router-resolver.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAyD,CAAC;AAE/G,iIAAiI;AACjI,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAQ1F,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAItE,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAM7C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAU9G;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,kBAAkB,EAC7B,eAAe,EAAE,SAAS,aAAa,EAAE,EACzC,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EACzD,eAAe,EAAE,MAAM,GACrB,aAAa,CAoBf","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
1
+ {"version":3,"file":"reasoning-router-resolver.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAkE,CAAC;AAExH,iIAAiI;AACjI,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAQ1F,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAItE,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAM7C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAU9G;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,kBAAkB,EAC7B,eAAe,EAAE,SAAS,aAAa,EAAE,EACzC,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EACzD,eAAe,EAAE,MAAM,GACrB,aAAa,CAoBf","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\", \"ultra\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
@@ -6,7 +6,7 @@
6
6
  * Deterministic, side-effect-free: no clock, randomness, or I/O.
7
7
  */
8
8
  /** Reasoning ladder used for targets and clamping. Intentionally excludes "off". */
9
- export const REASONING_LADDER = ["minimal", "low", "medium", "high", "xhigh", "max"];
9
+ export const REASONING_LADDER = ["minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
10
10
  /** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */
11
11
  export const TASK_CLASS_THINKING_LEVELS = {
12
12
  trivial: "minimal",
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-router-resolver.js","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
1
+ {"version":3,"file":"reasoning-router-resolver.js","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\", \"ultra\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)
3
+ * lands in later milestones; the type is part of the layer-1 contract.
4
+ */
5
+ export type ResourceGovernorMode = "off" | "observe" | "adaptive" | "strict";
6
+ export interface ResourceAdmissionThresholds {
7
+ readonly constrainedAvailableMemoryMiB: number;
8
+ readonly criticalAvailableMemoryMiB: number;
9
+ readonly constrainedDiskFreeMiB: number;
10
+ readonly criticalDiskFreeMiB: number;
11
+ readonly constrainedHeapRatio: number;
12
+ readonly criticalHeapRatio: number;
13
+ readonly busyCpuPercent: number;
14
+ }
15
+ export interface ResourcePressureCaps {
16
+ readonly maxToolConcurrency: number;
17
+ readonly maxParallelLanes: number;
18
+ readonly maxHeavyProcesses: number;
19
+ }
20
+ export interface ResourceAdmissionCapTable {
21
+ readonly normal: ResourcePressureCaps;
22
+ readonly constrained: ResourcePressureCaps;
23
+ readonly critical: ResourcePressureCaps;
24
+ }
25
+ export interface ResourceAdmissionConfig {
26
+ readonly thresholds: ResourceAdmissionThresholds;
27
+ readonly caps: ResourceAdmissionCapTable;
28
+ }
29
+ /** §6.7 default thresholds. */
30
+ export declare const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds;
31
+ /** §7.2 default caps per pressure tier. */
32
+ export declare const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable;
33
+ export declare const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig;
34
+ export type ResourceAdmissionConfigOverrides = {
35
+ thresholds?: Partial<ResourceAdmissionThresholds>;
36
+ caps?: {
37
+ normal?: Partial<ResourcePressureCaps>;
38
+ constrained?: Partial<ResourcePressureCaps>;
39
+ critical?: Partial<ResourcePressureCaps>;
40
+ };
41
+ };
42
+ export interface ResolvedResourceAdmissionConfig {
43
+ readonly config: ResourceAdmissionConfig;
44
+ /** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */
45
+ readonly errors: readonly string[];
46
+ }
47
+ /**
48
+ * Validate overrides against §18.1 and merge them over the defaults.
49
+ * Any validation error fails closed: the full default config is returned
50
+ * together with the explicit error list (startup diagnostics are the
51
+ * caller's responsibility in later slices).
52
+ */
53
+ export declare function resolveResourceAdmissionConfig(overrides?: ResourceAdmissionConfigOverrides): ResolvedResourceAdmissionConfig;
54
+ export declare function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[];
55
+ //# sourceMappingURL=resource-admission-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-admission-config.d.ts","sourceRoot":"","sources":["../../src/core/resource-admission-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE7E,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAC;IAC/C,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;CACxC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;CACzC;AAED,gCAA+B;AAC/B,eAAO,MAAM,qCAAqC,EAAE,2BAQnD,CAAC;AAEF,4CAA2C;AAC3C,eAAO,MAAM,oCAAoC,EAAE,yBAIlD,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,uBAG/C,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACvC,WAAW,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;KACzC,CAAC;CACF,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,qGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAKD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC7C,SAAS,CAAC,EAAE,gCAAgC,GAC1C,+BAA+B,CAcjC;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,MAAM,EAAE,CA0DlG","sourcesContent":["/**\n * Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)\n * lands in later milestones; the type is part of the layer-1 contract.\n */\nexport type ResourceGovernorMode = \"off\" | \"observe\" | \"adaptive\" | \"strict\";\n\nexport interface ResourceAdmissionThresholds {\n\treadonly constrainedAvailableMemoryMiB: number;\n\treadonly criticalAvailableMemoryMiB: number;\n\treadonly constrainedDiskFreeMiB: number;\n\treadonly criticalDiskFreeMiB: number;\n\treadonly constrainedHeapRatio: number;\n\treadonly criticalHeapRatio: number;\n\treadonly busyCpuPercent: number;\n}\n\nexport interface ResourcePressureCaps {\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n}\n\nexport interface ResourceAdmissionCapTable {\n\treadonly normal: ResourcePressureCaps;\n\treadonly constrained: ResourcePressureCaps;\n\treadonly critical: ResourcePressureCaps;\n}\n\nexport interface ResourceAdmissionConfig {\n\treadonly thresholds: ResourceAdmissionThresholds;\n\treadonly caps: ResourceAdmissionCapTable;\n}\n\n/** §6.7 default thresholds. */\nexport const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds = {\n\tconstrainedAvailableMemoryMiB: 1536,\n\tcriticalAvailableMemoryMiB: 512,\n\tconstrainedDiskFreeMiB: 4096,\n\tcriticalDiskFreeMiB: 1024,\n\tconstrainedHeapRatio: 0.75,\n\tcriticalHeapRatio: 0.85,\n\tbusyCpuPercent: 85,\n};\n\n/** §7.2 default caps per pressure tier. */\nexport const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable = {\n\tnormal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },\n\tconstrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },\n\tcritical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },\n};\n\nexport const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig = {\n\tthresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tcaps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n};\n\nexport type ResourceAdmissionConfigOverrides = {\n\tthresholds?: Partial<ResourceAdmissionThresholds>;\n\tcaps?: {\n\t\tnormal?: Partial<ResourcePressureCaps>;\n\t\tconstrained?: Partial<ResourcePressureCaps>;\n\t\tcritical?: Partial<ResourcePressureCaps>;\n\t};\n};\n\nexport interface ResolvedResourceAdmissionConfig {\n\treadonly config: ResourceAdmissionConfig;\n\t/** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */\n\treadonly errors: readonly string[];\n}\n\nconst MIN_CAP = 1;\nconst MAX_CAP = 64;\n\n/**\n * Validate overrides against §18.1 and merge them over the defaults.\n * Any validation error fails closed: the full default config is returned\n * together with the explicit error list (startup diagnostics are the\n * caller's responsibility in later slices).\n */\nexport function resolveResourceAdmissionConfig(\n\toverrides?: ResourceAdmissionConfigOverrides,\n): ResolvedResourceAdmissionConfig {\n\tconst merged: ResourceAdmissionConfig = {\n\t\tthresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },\n\t\tcaps: {\n\t\t\tnormal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },\n\t\t\tconstrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },\n\t\t\tcritical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },\n\t\t},\n\t};\n\tconst errors = validateResourceAdmissionConfig(merged);\n\tif (errors.length > 0) {\n\t\treturn { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };\n\t}\n\treturn { config: merged, errors };\n}\n\nexport function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[] {\n\tconst errors: string[] = [];\n\tconst { thresholds, caps } = config;\n\n\tconst positiveMiB: ReadonlyArray<readonly [string, number]> = [\n\t\t[\"constrainedAvailableMemoryMiB\", thresholds.constrainedAvailableMemoryMiB],\n\t\t[\"criticalAvailableMemoryMiB\", thresholds.criticalAvailableMemoryMiB],\n\t\t[\"constrainedDiskFreeMiB\", thresholds.constrainedDiskFreeMiB],\n\t\t[\"criticalDiskFreeMiB\", thresholds.criticalDiskFreeMiB],\n\t];\n\tfor (const [name, value] of positiveMiB) {\n\t\tif (!Number.isSafeInteger(value) || value <= 0) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {\n\t\terrors.push(\"resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB\");\n\t}\n\tif (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {\n\t\terrors.push(\"resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB\");\n\t}\n\tfor (const [name, value] of [\n\t\t[\"constrainedHeapRatio\", thresholds.constrainedHeapRatio],\n\t\t[\"criticalHeapRatio\", thresholds.criticalHeapRatio],\n\t] as const) {\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {\n\t\terrors.push(\"resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio\");\n\t}\n\tif (\n\t\t!Number.isFinite(thresholds.busyCpuPercent) ||\n\t\tthresholds.busyCpuPercent < 1 ||\n\t\tthresholds.busyCpuPercent > 100\n\t) {\n\t\terrors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);\n\t}\n\n\tconst capFields = [\"maxToolConcurrency\", \"maxParallelLanes\", \"maxHeavyProcesses\"] as const;\n\tfor (const tier of [\"normal\", \"constrained\", \"critical\"] as const) {\n\t\tfor (const field of capFields) {\n\t\t\tconst value = caps[tier][field];\n\t\t\tif (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\t// §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.\n\tfor (const field of capFields) {\n\t\tif (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {\n\t\t\terrors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);\n\t\t}\n\t}\n\treturn errors;\n}\n"]}
@@ -0,0 +1,97 @@
1
+ /** §6.7 default thresholds. */
2
+ export const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS = {
3
+ constrainedAvailableMemoryMiB: 1536,
4
+ criticalAvailableMemoryMiB: 512,
5
+ constrainedDiskFreeMiB: 4096,
6
+ criticalDiskFreeMiB: 1024,
7
+ constrainedHeapRatio: 0.75,
8
+ criticalHeapRatio: 0.85,
9
+ busyCpuPercent: 85,
10
+ };
11
+ /** §7.2 default caps per pressure tier. */
12
+ export const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE = {
13
+ normal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },
14
+ constrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },
15
+ critical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },
16
+ };
17
+ export const DEFAULT_RESOURCE_ADMISSION_CONFIG = {
18
+ thresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,
19
+ caps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,
20
+ };
21
+ const MIN_CAP = 1;
22
+ const MAX_CAP = 64;
23
+ /**
24
+ * Validate overrides against §18.1 and merge them over the defaults.
25
+ * Any validation error fails closed: the full default config is returned
26
+ * together with the explicit error list (startup diagnostics are the
27
+ * caller's responsibility in later slices).
28
+ */
29
+ export function resolveResourceAdmissionConfig(overrides) {
30
+ const merged = {
31
+ thresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },
32
+ caps: {
33
+ normal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },
34
+ constrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },
35
+ critical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },
36
+ },
37
+ };
38
+ const errors = validateResourceAdmissionConfig(merged);
39
+ if (errors.length > 0) {
40
+ return { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };
41
+ }
42
+ return { config: merged, errors };
43
+ }
44
+ export function validateResourceAdmissionConfig(config) {
45
+ const errors = [];
46
+ const { thresholds, caps } = config;
47
+ const positiveMiB = [
48
+ ["constrainedAvailableMemoryMiB", thresholds.constrainedAvailableMemoryMiB],
49
+ ["criticalAvailableMemoryMiB", thresholds.criticalAvailableMemoryMiB],
50
+ ["constrainedDiskFreeMiB", thresholds.constrainedDiskFreeMiB],
51
+ ["criticalDiskFreeMiB", thresholds.criticalDiskFreeMiB],
52
+ ];
53
+ for (const [name, value] of positiveMiB) {
54
+ if (!Number.isSafeInteger(value) || value <= 0) {
55
+ errors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);
56
+ }
57
+ }
58
+ if (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {
59
+ errors.push("resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB");
60
+ }
61
+ if (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {
62
+ errors.push("resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB");
63
+ }
64
+ for (const [name, value] of [
65
+ ["constrainedHeapRatio", thresholds.constrainedHeapRatio],
66
+ ["criticalHeapRatio", thresholds.criticalHeapRatio],
67
+ ]) {
68
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1) {
69
+ errors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);
70
+ }
71
+ }
72
+ if (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {
73
+ errors.push("resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio");
74
+ }
75
+ if (!Number.isFinite(thresholds.busyCpuPercent) ||
76
+ thresholds.busyCpuPercent < 1 ||
77
+ thresholds.busyCpuPercent > 100) {
78
+ errors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);
79
+ }
80
+ const capFields = ["maxToolConcurrency", "maxParallelLanes", "maxHeavyProcesses"];
81
+ for (const tier of ["normal", "constrained", "critical"]) {
82
+ for (const field of capFields) {
83
+ const value = caps[tier][field];
84
+ if (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {
85
+ errors.push(`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`);
86
+ }
87
+ }
88
+ }
89
+ // §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.
90
+ for (const field of capFields) {
91
+ if (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {
92
+ errors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);
93
+ }
94
+ }
95
+ return errors;
96
+ }
97
+ //# sourceMappingURL=resource-admission-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-admission-config.js","sourceRoot":"","sources":["../../src/core/resource-admission-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Governor rollout mode (§7.4). Mode enforcement (probe/record/cap wiring)\n * lands in later milestones; the type is part of the layer-1 contract.\n */\nexport type ResourceGovernorMode = \"off\" | \"observe\" | \"adaptive\" | \"strict\";\n\nexport interface ResourceAdmissionThresholds {\n\treadonly constrainedAvailableMemoryMiB: number;\n\treadonly criticalAvailableMemoryMiB: number;\n\treadonly constrainedDiskFreeMiB: number;\n\treadonly criticalDiskFreeMiB: number;\n\treadonly constrainedHeapRatio: number;\n\treadonly criticalHeapRatio: number;\n\treadonly busyCpuPercent: number;\n}\n\nexport interface ResourcePressureCaps {\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n}\n\nexport interface ResourceAdmissionCapTable {\n\treadonly normal: ResourcePressureCaps;\n\treadonly constrained: ResourcePressureCaps;\n\treadonly critical: ResourcePressureCaps;\n}\n\nexport interface ResourceAdmissionConfig {\n\treadonly thresholds: ResourceAdmissionThresholds;\n\treadonly caps: ResourceAdmissionCapTable;\n}\n\n/** §6.7 default thresholds. */\nexport const DEFAULT_RESOURCE_ADMISSION_THRESHOLDS: ResourceAdmissionThresholds = {\n\tconstrainedAvailableMemoryMiB: 1536,\n\tcriticalAvailableMemoryMiB: 512,\n\tconstrainedDiskFreeMiB: 4096,\n\tcriticalDiskFreeMiB: 1024,\n\tconstrainedHeapRatio: 0.75,\n\tcriticalHeapRatio: 0.85,\n\tbusyCpuPercent: 85,\n};\n\n/** §7.2 default caps per pressure tier. */\nexport const DEFAULT_RESOURCE_ADMISSION_CAP_TABLE: ResourceAdmissionCapTable = {\n\tnormal: { maxToolConcurrency: 4, maxParallelLanes: 4, maxHeavyProcesses: 2 },\n\tconstrained: { maxToolConcurrency: 2, maxParallelLanes: 2, maxHeavyProcesses: 1 },\n\tcritical: { maxToolConcurrency: 1, maxParallelLanes: 1, maxHeavyProcesses: 1 },\n};\n\nexport const DEFAULT_RESOURCE_ADMISSION_CONFIG: ResourceAdmissionConfig = {\n\tthresholds: DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tcaps: DEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n};\n\nexport type ResourceAdmissionConfigOverrides = {\n\tthresholds?: Partial<ResourceAdmissionThresholds>;\n\tcaps?: {\n\t\tnormal?: Partial<ResourcePressureCaps>;\n\t\tconstrained?: Partial<ResourcePressureCaps>;\n\t\tcritical?: Partial<ResourcePressureCaps>;\n\t};\n};\n\nexport interface ResolvedResourceAdmissionConfig {\n\treadonly config: ResourceAdmissionConfig;\n\t/** §18.1: explicit errors over silent clamps. Non-empty means fail-closed defaults were applied. */\n\treadonly errors: readonly string[];\n}\n\nconst MIN_CAP = 1;\nconst MAX_CAP = 64;\n\n/**\n * Validate overrides against §18.1 and merge them over the defaults.\n * Any validation error fails closed: the full default config is returned\n * together with the explicit error list (startup diagnostics are the\n * caller's responsibility in later slices).\n */\nexport function resolveResourceAdmissionConfig(\n\toverrides?: ResourceAdmissionConfigOverrides,\n): ResolvedResourceAdmissionConfig {\n\tconst merged: ResourceAdmissionConfig = {\n\t\tthresholds: { ...DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, ...overrides?.thresholds },\n\t\tcaps: {\n\t\t\tnormal: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.normal, ...overrides?.caps?.normal },\n\t\t\tconstrained: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.constrained, ...overrides?.caps?.constrained },\n\t\t\tcritical: { ...DEFAULT_RESOURCE_ADMISSION_CAP_TABLE.critical, ...overrides?.caps?.critical },\n\t\t},\n\t};\n\tconst errors = validateResourceAdmissionConfig(merged);\n\tif (errors.length > 0) {\n\t\treturn { config: DEFAULT_RESOURCE_ADMISSION_CONFIG, errors };\n\t}\n\treturn { config: merged, errors };\n}\n\nexport function validateResourceAdmissionConfig(config: ResourceAdmissionConfig): readonly string[] {\n\tconst errors: string[] = [];\n\tconst { thresholds, caps } = config;\n\n\tconst positiveMiB: ReadonlyArray<readonly [string, number]> = [\n\t\t[\"constrainedAvailableMemoryMiB\", thresholds.constrainedAvailableMemoryMiB],\n\t\t[\"criticalAvailableMemoryMiB\", thresholds.criticalAvailableMemoryMiB],\n\t\t[\"constrainedDiskFreeMiB\", thresholds.constrainedDiskFreeMiB],\n\t\t[\"criticalDiskFreeMiB\", thresholds.criticalDiskFreeMiB],\n\t];\n\tfor (const [name, value] of positiveMiB) {\n\t\tif (!Number.isSafeInteger(value) || value <= 0) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be a positive safe integer, got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalAvailableMemoryMiB > thresholds.constrainedAvailableMemoryMiB) {\n\t\terrors.push(\"resourceGovernor.criticalAvailableMemoryMiB: must be <= constrainedAvailableMemoryMiB\");\n\t}\n\tif (thresholds.criticalDiskFreeMiB > thresholds.constrainedDiskFreeMiB) {\n\t\terrors.push(\"resourceGovernor.criticalDiskFreeMiB: must be <= constrainedDiskFreeMiB\");\n\t}\n\tfor (const [name, value] of [\n\t\t[\"constrainedHeapRatio\", thresholds.constrainedHeapRatio],\n\t\t[\"criticalHeapRatio\", thresholds.criticalHeapRatio],\n\t] as const) {\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0 || value > 1) {\n\t\t\terrors.push(`resourceGovernor.${name}: must be within (0, 1], got ${String(value)}`);\n\t\t}\n\t}\n\tif (thresholds.criticalHeapRatio < thresholds.constrainedHeapRatio) {\n\t\terrors.push(\"resourceGovernor.criticalHeapRatio: must be >= constrainedHeapRatio\");\n\t}\n\tif (\n\t\t!Number.isFinite(thresholds.busyCpuPercent) ||\n\t\tthresholds.busyCpuPercent < 1 ||\n\t\tthresholds.busyCpuPercent > 100\n\t) {\n\t\terrors.push(`resourceGovernor.busyCpuPercent: must be within [1, 100], got ${String(thresholds.busyCpuPercent)}`);\n\t}\n\n\tconst capFields = [\"maxToolConcurrency\", \"maxParallelLanes\", \"maxHeavyProcesses\"] as const;\n\tfor (const tier of [\"normal\", \"constrained\", \"critical\"] as const) {\n\t\tfor (const field of capFields) {\n\t\t\tconst value = caps[tier][field];\n\t\t\tif (!Number.isSafeInteger(value) || value < MIN_CAP || value > MAX_CAP) {\n\t\t\t\terrors.push(\n\t\t\t\t\t`resourceGovernor.caps.${tier}.${field}: must be an integer within [${MIN_CAP}, ${MAX_CAP}], got ${String(value)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\t// §4.4 / §18.1 monotonicity: normalCap >= constrainedCap >= criticalCap.\n\tfor (const field of capFields) {\n\t\tif (caps.normal[field] < caps.constrained[field] || caps.constrained[field] < caps.critical[field]) {\n\t\t\terrors.push(`resourceGovernor.caps.${field}: requires normal >= constrained >= critical`);\n\t\t}\n\t}\n\treturn errors;\n}\n"]}
@@ -0,0 +1,81 @@
1
+ import { type HostResourceSnapshot } from "./host-resource-snapshot.ts";
2
+ import { type ResourceAdmissionConfig, type ResourceAdmissionThresholds } from "./resource-admission-config.ts";
3
+ export type { ResolvedResourceAdmissionConfig, ResourceAdmissionCapTable, ResourceAdmissionConfig, ResourceAdmissionConfigOverrides, ResourceAdmissionThresholds, ResourceGovernorMode, ResourcePressureCaps, } from "./resource-admission-config.ts";
4
+ export { DEFAULT_RESOURCE_ADMISSION_CAP_TABLE, DEFAULT_RESOURCE_ADMISSION_CONFIG, DEFAULT_RESOURCE_ADMISSION_THRESHOLDS, resolveResourceAdmissionConfig, validateResourceAdmissionConfig, } from "./resource-admission-config.ts";
5
+ /**
6
+ * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).
7
+ *
8
+ * This module maps an immutable {@link HostResourceSnapshot} to an immutable
9
+ * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are
10
+ * injectable), side-effect free, and performs no scheduler wiring — the
11
+ * decision only becomes scheduler authority in later milestones (M2+).
12
+ *
13
+ * Policy invariants (verified by property tests, roadmap §23.2):
14
+ * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves
15
+ * pressure and never raises a cap.
16
+ * - Conservative unknown handling (§4.3): a known critical fact wins; a
17
+ * missing key probe (memory, disk, heap) degrades to `constrained`; only a
18
+ * fully healthy snapshot is `normal`.
19
+ * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,
20
+ * while memory/disk/heap may block heavy admission.
21
+ * - User cap precedence (§7.3): effective caps never exceed configured caps,
22
+ * and `0 = unlimited` settings are limited to the admission cap.
23
+ */
24
+ export declare const RESOURCE_ADMISSION_VERSION: 1;
25
+ export type ResourcePressure = "normal" | "constrained" | "critical";
26
+ export type ResourceAdmissionAction = "allow" | "throttle" | "defer-heavy";
27
+ export type ResourceReasonCode = "resource.memory.low" | "resource.memory.critical" | "resource.disk.low" | "resource.disk.critical" | "resource.cpu.busy" | "resource.heap.high" | "resource.heap.critical" | "resource.probe.partial" | "resource.probe.timeout";
28
+ export interface ResourceAdmissionDecision {
29
+ readonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;
30
+ readonly decisionId: string;
31
+ readonly snapshotDigest: string;
32
+ readonly pressure: ResourcePressure;
33
+ readonly action: ResourceAdmissionAction;
34
+ readonly maxToolConcurrency: number;
35
+ readonly maxParallelLanes: number;
36
+ readonly maxHeavyProcesses: number;
37
+ readonly reasons: readonly ResourceReasonCode[];
38
+ readonly decidedAt: string;
39
+ }
40
+ /** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */
41
+ export interface ModelResourceBudgetHint {
42
+ readonly pressure: ResourcePressure;
43
+ readonly maxToolConcurrency: number;
44
+ readonly maxParallelLanes: number;
45
+ readonly maxHeavyProcesses: number;
46
+ readonly reasons: readonly ResourceReasonCode[];
47
+ }
48
+ /** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */
49
+ export declare function resourcePressureRank(pressure: ResourcePressure): number;
50
+ export interface ResourcePressureEvaluation {
51
+ readonly pressure: ResourcePressure;
52
+ readonly reasons: readonly ResourceReasonCode[];
53
+ }
54
+ /**
55
+ * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).
56
+ * Pure and monotonic: strictly worse inputs never yield a better pressure.
57
+ */
58
+ export declare function evaluateResourcePressure(snapshot: HostResourceSnapshot, thresholds?: ResourceAdmissionThresholds): ResourcePressureEvaluation;
59
+ /**
60
+ * Existing user settings that admission must never exceed (§7.3).
61
+ * `0` or `undefined` means "unlimited" and is limited to the admission cap.
62
+ */
63
+ export interface ConfiguredResourceCaps {
64
+ readonly maxToolConcurrency?: number;
65
+ readonly maxParallelLanes?: number;
66
+ readonly maxHeavyProcesses?: number;
67
+ }
68
+ export interface ResourceAdmissionInput {
69
+ readonly snapshot: HostResourceSnapshot;
70
+ readonly config?: ResourceAdmissionConfig;
71
+ readonly configuredCaps?: ConfiguredResourceCaps;
72
+ /** Injectable for deterministic tests; defaults to a UUID-based id. */
73
+ readonly decisionId?: string;
74
+ /** Injectable ISO timestamp; defaults to the current time. */
75
+ readonly decidedAt?: string;
76
+ }
77
+ /** Map a snapshot to an immutable admission decision (§7.1-§7.3). */
78
+ export declare function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision;
79
+ /** Bounded hint for the model (§6.2). Carries no raw host values. */
80
+ export declare function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint;
81
+ //# sourceMappingURL=resource-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-admission.d.ts","sourceRoot":"","sources":["../../src/core/resource-admission.ts"],"names":[],"mappings":"AACA,OAAO,EAGN,KAAK,oBAAoB,EACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGN,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACX,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,EACvB,gCAAgC,EAChC,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACN,oCAAoC,EACpC,iCAAiC,EACjC,qCAAqC,EACrC,8BAA8B,EAC9B,+BAA+B,GAC/B,MAAM,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,0BAA0B,GAAa,CAAC;AAErD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;AAErE,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;AAE3E,MAAM,MAAM,kBAAkB,GAC3B,qBAAqB,GACrB,0BAA0B,GAC1B,mBAAmB,GACnB,wBAAwB,GACxB,mBAAmB,GACnB,oBAAoB,GACpB,wBAAwB,GACxB,wBAAwB,GACxB,wBAAwB,CAAC;AAE5B,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,aAAa,EAAE,OAAO,0BAA0B,CAAC;IAC1D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IAEzC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IAEnC,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,qFAAoF;AACpF,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAChD;AAaD,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAEvE;AAED,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAChD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACvC,QAAQ,EAAE,oBAAoB,EAC9B,UAAU,GAAE,2BAAmE,GAC7E,0BAA0B,CAmD5B;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACjD,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,yBAAyB,CAgBhG;AAED,sEAAqE;AACrE,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,uBAAuB,CAQtG","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tcomputeHostResourceSnapshotDigest,\n\teffectiveAvailableMemoryBytes,\n\ttype HostResourceSnapshot,\n} from \"./host-resource-snapshot.ts\";\nimport {\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\ttype ResourceAdmissionConfig,\n\ttype ResourceAdmissionThresholds,\n} from \"./resource-admission-config.ts\";\n\nexport type {\n\tResolvedResourceAdmissionConfig,\n\tResourceAdmissionCapTable,\n\tResourceAdmissionConfig,\n\tResourceAdmissionConfigOverrides,\n\tResourceAdmissionThresholds,\n\tResourceGovernorMode,\n\tResourcePressureCaps,\n} from \"./resource-admission-config.ts\";\nexport {\n\tDEFAULT_RESOURCE_ADMISSION_CAP_TABLE,\n\tDEFAULT_RESOURCE_ADMISSION_CONFIG,\n\tDEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n\tresolveResourceAdmissionConfig,\n\tvalidateResourceAdmissionConfig,\n} from \"./resource-admission-config.ts\";\n\n/**\n * Pure resource admission policy (OMK v0.97.x roadmap §6.6-§7, M1/PR1).\n *\n * This module maps an immutable {@link HostResourceSnapshot} to an immutable\n * {@link ResourceAdmissionDecision}. It is deterministic (clock and id are\n * injectable), side-effect free, and performs no scheduler wiring — the\n * decision only becomes scheduler authority in later milestones (M2+).\n *\n * Policy invariants (verified by property tests, roadmap §23.2):\n * - Monotonic degradation (§4.4): worse memory/disk/CPU never improves\n * pressure and never raises a cap.\n * - Conservative unknown handling (§4.3): a known critical fact wins; a\n * missing key probe (memory, disk, heap) degrades to `constrained`; only a\n * fully healthy snapshot is `normal`.\n * - CPU alone never produces `critical` (§6.7): CPU pressure throttles,\n * while memory/disk/heap may block heavy admission.\n * - User cap precedence (§7.3): effective caps never exceed configured caps,\n * and `0 = unlimited` settings are limited to the admission cap.\n */\n\nexport const RESOURCE_ADMISSION_VERSION = 1 as const;\n\nexport type ResourcePressure = \"normal\" | \"constrained\" | \"critical\";\n\nexport type ResourceAdmissionAction = \"allow\" | \"throttle\" | \"defer-heavy\";\n\nexport type ResourceReasonCode =\n\t| \"resource.memory.low\"\n\t| \"resource.memory.critical\"\n\t| \"resource.disk.low\"\n\t| \"resource.disk.critical\"\n\t| \"resource.cpu.busy\"\n\t| \"resource.heap.high\"\n\t| \"resource.heap.critical\"\n\t| \"resource.probe.partial\"\n\t| \"resource.probe.timeout\";\n\nexport interface ResourceAdmissionDecision {\n\treadonly schemaVersion: typeof RESOURCE_ADMISSION_VERSION;\n\treadonly decisionId: string;\n\treadonly snapshotDigest: string;\n\treadonly pressure: ResourcePressure;\n\treadonly action: ResourceAdmissionAction;\n\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\n\treadonly reasons: readonly ResourceReasonCode[];\n\treadonly decidedAt: string;\n}\n\n/** Bounded model-facing hint (§6.2). Never expose the raw snapshot to the model. */\nexport interface ModelResourceBudgetHint {\n\treadonly pressure: ResourcePressure;\n\treadonly maxToolConcurrency: number;\n\treadonly maxParallelLanes: number;\n\treadonly maxHeavyProcesses: number;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\nconst MIB = 1024 * 1024;\nconst MIN_CAP = 1;\nconst PRESSURE_RANK: Record<ResourcePressure, number> = { normal: 0, constrained: 1, critical: 2 };\nconst RANK_TO_PRESSURE: readonly ResourcePressure[] = [\"normal\", \"constrained\", \"critical\"];\n/** §7 action mapping: allow at normal, throttle at constrained, defer heavy work at critical. */\nconst ACTION_BY_PRESSURE: Record<ResourcePressure, ResourceAdmissionAction> = {\n\tnormal: \"allow\",\n\tconstrained: \"throttle\",\n\tcritical: \"defer-heavy\",\n};\n\n/** Total order for pressure levels: `normal` (0) < `constrained` (1) < `critical` (2). */\nexport function resourcePressureRank(pressure: ResourcePressure): number {\n\treturn PRESSURE_RANK[pressure];\n}\n\nexport interface ResourcePressureEvaluation {\n\treadonly pressure: ResourcePressure;\n\treadonly reasons: readonly ResourceReasonCode[];\n}\n\n/**\n * Derive pressure and reason codes from a snapshot (§4.3, §6.6, §6.7).\n * Pure and monotonic: strictly worse inputs never yield a better pressure.\n */\nexport function evaluateResourcePressure(\n\tsnapshot: HostResourceSnapshot,\n\tthresholds: ResourceAdmissionThresholds = DEFAULT_RESOURCE_ADMISSION_THRESHOLDS,\n): ResourcePressureEvaluation {\n\tconst reasons: ResourceReasonCode[] = [];\n\tlet rank = PRESSURE_RANK.normal;\n\tconst escalate = (target: ResourcePressure, reason: ResourceReasonCode): void => {\n\t\treasons.push(reason);\n\t\trank = Math.max(rank, PRESSURE_RANK[target]);\n\t};\n\n\tconst memory = effectiveAvailableMemoryBytes(snapshot);\n\tif (memory !== null) {\n\t\tif (memory < thresholds.criticalAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.memory.critical\");\n\t\t} else if (memory < thresholds.constrainedAvailableMemoryMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.memory.low\");\n\t\t}\n\t}\n\n\tconst disk = snapshot.workspaceAvailableBytes;\n\tif (disk !== null) {\n\t\tif (disk < thresholds.criticalDiskFreeMiB * MIB) {\n\t\t\tescalate(\"critical\", \"resource.disk.critical\");\n\t\t} else if (disk < thresholds.constrainedDiskFreeMiB * MIB) {\n\t\t\tescalate(\"constrained\", \"resource.disk.low\");\n\t\t}\n\t}\n\n\tconst heapRatio = snapshot.heapLimitBytes > 0 ? snapshot.heapUsedBytes / snapshot.heapLimitBytes : null;\n\tif (heapRatio !== null) {\n\t\tif (heapRatio >= thresholds.criticalHeapRatio) {\n\t\t\tescalate(\"critical\", \"resource.heap.critical\");\n\t\t} else if (heapRatio >= thresholds.constrainedHeapRatio) {\n\t\t\tescalate(\"constrained\", \"resource.heap.high\");\n\t\t}\n\t}\n\n\t// §6.7: CPU pressure only throttles; it can never be critical on its own.\n\tif (snapshot.systemCpuPercent !== null && snapshot.systemCpuPercent >= thresholds.busyCpuPercent) {\n\t\tescalate(\"constrained\", \"resource.cpu.busy\");\n\t}\n\n\t// §4.3 conservative unknown handling. Key probes are the heavy-admission\n\t// authorities (memory, disk, heap); a missing one degrades to constrained.\n\tif (memory === null || disk === null || heapRatio === null) {\n\t\tescalate(\"constrained\", \"resource.probe.partial\");\n\t}\n\t// §21: any probe timeout keeps the prompt going but never reports normal.\n\tif (snapshot.diagnostics.some((code) => code.endsWith(\".timeout\"))) {\n\t\tescalate(\"constrained\", \"resource.probe.timeout\");\n\t}\n\n\treturn { pressure: RANK_TO_PRESSURE[rank] ?? \"critical\", reasons };\n}\n\n/**\n * Existing user settings that admission must never exceed (§7.3).\n * `0` or `undefined` means \"unlimited\" and is limited to the admission cap.\n */\nexport interface ConfiguredResourceCaps {\n\treadonly maxToolConcurrency?: number;\n\treadonly maxParallelLanes?: number;\n\treadonly maxHeavyProcesses?: number;\n}\n\nexport interface ResourceAdmissionInput {\n\treadonly snapshot: HostResourceSnapshot;\n\treadonly config?: ResourceAdmissionConfig;\n\treadonly configuredCaps?: ConfiguredResourceCaps;\n\t/** Injectable for deterministic tests; defaults to a UUID-based id. */\n\treadonly decisionId?: string;\n\t/** Injectable ISO timestamp; defaults to the current time. */\n\treadonly decidedAt?: string;\n}\n\n/** Map a snapshot to an immutable admission decision (§7.1-§7.3). */\nexport function decideResourceAdmission(input: ResourceAdmissionInput): ResourceAdmissionDecision {\n\tconst config = input.config ?? DEFAULT_RESOURCE_ADMISSION_CONFIG;\n\tconst { pressure, reasons } = evaluateResourcePressure(input.snapshot, config.thresholds);\n\tconst tier = config.caps[pressure];\n\treturn {\n\t\tschemaVersion: RESOURCE_ADMISSION_VERSION,\n\t\tdecisionId: input.decisionId ?? `res-adm-${randomUUID()}`,\n\t\tsnapshotDigest: computeHostResourceSnapshotDigest(input.snapshot),\n\t\tpressure,\n\t\taction: ACTION_BY_PRESSURE[pressure],\n\t\tmaxToolConcurrency: effectiveCap(input.configuredCaps?.maxToolConcurrency, tier.maxToolConcurrency),\n\t\tmaxParallelLanes: effectiveCap(input.configuredCaps?.maxParallelLanes, tier.maxParallelLanes),\n\t\tmaxHeavyProcesses: effectiveCap(input.configuredCaps?.maxHeavyProcesses, tier.maxHeavyProcesses),\n\t\treasons,\n\t\tdecidedAt: input.decidedAt ?? new Date().toISOString(),\n\t};\n}\n\n/** Bounded hint for the model (§6.2). Carries no raw host values. */\nexport function toModelResourceBudgetHint(decision: ResourceAdmissionDecision): ModelResourceBudgetHint {\n\treturn {\n\t\tpressure: decision.pressure,\n\t\tmaxToolConcurrency: decision.maxToolConcurrency,\n\t\tmaxParallelLanes: decision.maxParallelLanes,\n\t\tmaxHeavyProcesses: decision.maxHeavyProcesses,\n\t\treasons: decision.reasons,\n\t};\n}\n\n/** §7.3: `min(configured, admission)`, where configured `0`/absent means unlimited. */\nfunction effectiveCap(configured: number | undefined, admission: number): number {\n\tconst admitted = Math.max(MIN_CAP, Math.floor(admission));\n\tif (typeof configured !== \"number\" || !Number.isFinite(configured) || configured <= 0) {\n\t\treturn admitted;\n\t}\n\treturn Math.max(MIN_CAP, Math.min(Math.floor(configured), admitted));\n}\n"]}