@tangle-network/agent-eval 0.217.1 → 0.218.1

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 (172) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/adapters/http.d.ts +2 -2
  3. package/dist/analyst/index.d.ts +1 -1
  4. package/dist/analyst/index.d.ts.map +1 -1
  5. package/dist/analyst/index.js +9 -9
  6. package/dist/analyst/index.js.map +1 -1
  7. package/dist/{analyze-runs-DyeNcenW.d.ts → analyze-runs-DjlZOVXx.d.ts} +2 -2
  8. package/dist/{analyze-runs-DyeNcenW.d.ts.map → analyze-runs-DjlZOVXx.d.ts.map} +1 -1
  9. package/dist/{benchmark-command-xQK8nOI6.js → benchmark-command-BupdHRyf.js} +661 -562
  10. package/dist/benchmark-command-BupdHRyf.js.map +1 -0
  11. package/dist/benchmarks/index.d.ts +4 -4
  12. package/dist/benchmarks/index.js +3 -3
  13. package/dist/campaign/index.d.ts +4 -4
  14. package/dist/campaign/index.js +10 -10
  15. package/dist/{campaign-CJGkxAQ6.js → campaign-CGvUTnmc.js} +312 -281
  16. package/dist/{campaign-CJGkxAQ6.js.map → campaign-CGvUTnmc.js.map} +1 -1
  17. package/dist/{campaign-evidence-CQYw8_0H.js → campaign-evidence-DunzcJGv.js} +2 -2
  18. package/dist/{campaign-evidence-CQYw8_0H.js.map → campaign-evidence-DunzcJGv.js.map} +1 -1
  19. package/dist/{chat-client-ldqxYYTN.js → chat-client-B2pXlPdx.js} +2 -2
  20. package/dist/{chat-client-ldqxYYTN.js.map → chat-client-B2pXlPdx.js.map} +1 -1
  21. package/dist/{chat-json-call-DavzexSp.js → chat-json-call-Dm9c-b41.js} +2 -2
  22. package/dist/{chat-json-call-DavzexSp.js.map → chat-json-call-Dm9c-b41.js.map} +1 -1
  23. package/dist/cli.js +10 -10
  24. package/dist/{completion-verifier-B_jw6C9p.js → completion-verifier-DbLVf3i8.js} +4 -4
  25. package/dist/{completion-verifier-B_jw6C9p.js.map → completion-verifier-DbLVf3i8.js.map} +1 -1
  26. package/dist/contract/index.d.ts +7 -7
  27. package/dist/contract/index.js +90 -10
  28. package/dist/contract/index.js.map +1 -1
  29. package/dist/{default-registry-ClYjq3QE.js → default-registry-BJ-Pq6yL.js} +309 -277
  30. package/dist/{default-registry-ClYjq3QE.js.map → default-registry-BJ-Pq6yL.js.map} +1 -1
  31. package/dist/{define-agent-eval-BDxNlP3D.js → define-agent-eval-BGdFKlrc.js} +165 -105
  32. package/dist/define-agent-eval-BGdFKlrc.js.map +1 -0
  33. package/dist/{define-agent-eval-BIkH_gAR.d.ts → define-agent-eval-BlH10zEw.d.ts} +6 -6
  34. package/dist/{define-agent-eval-BIkH_gAR.d.ts.map → define-agent-eval-BlH10zEw.d.ts.map} +1 -1
  35. package/dist/diagnosis/index.d.ts +1 -1
  36. package/dist/{dspy-rlm-engine-DhdCr1Dc.js → dspy-rlm-engine-CSMwudLO.js} +2 -2
  37. package/dist/{dspy-rlm-engine-DhdCr1Dc.js.map → dspy-rlm-engine-CSMwudLO.js.map} +1 -1
  38. package/dist/eval-campaign-BUMIxKTE.js +393 -0
  39. package/dist/eval-campaign-BUMIxKTE.js.map +1 -0
  40. package/dist/{evaluation-jTjC_QQD.d.ts → evaluation-BOZ38vOH.d.ts} +2 -2
  41. package/dist/{evaluation-jTjC_QQD.d.ts.map → evaluation-BOZ38vOH.d.ts.map} +1 -1
  42. package/dist/evaluation.d.ts +1 -1
  43. package/dist/experiment/index.d.ts +1 -1
  44. package/dist/experiment/index.d.ts.map +1 -1
  45. package/dist/experiment/index.js +110 -71
  46. package/dist/experiment/index.js.map +1 -1
  47. package/dist/{exporters-Cl6IeNAu.js → exporters-DWvM16LE.js} +2 -2
  48. package/dist/{exporters-Cl6IeNAu.js.map → exporters-DWvM16LE.js.map} +1 -1
  49. package/dist/{external-optimizer-process-KVFGvYX1.js → external-optimizer-process-C3Wm7Vhn.js} +2 -2
  50. package/dist/{external-optimizer-process-KVFGvYX1.js.map → external-optimizer-process-C3Wm7Vhn.js.map} +1 -1
  51. package/dist/{external-optimizer-subprocess-XIHBOSKt.js → external-optimizer-subprocess-B5gdLRZv.js} +3 -3
  52. package/dist/{external-optimizer-subprocess-XIHBOSKt.js.map → external-optimizer-subprocess-B5gdLRZv.js.map} +1 -1
  53. package/dist/{findings-store-BMVQtEsm.js → findings-store-BtTzPL0d.js} +5 -5
  54. package/dist/{findings-store-BMVQtEsm.js.map → findings-store-BtTzPL0d.js.map} +1 -1
  55. package/dist/{hf-dataset-D9f7-oX7.js → hf-dataset-CKxIJQba.js} +3 -3
  56. package/dist/{hf-dataset-D9f7-oX7.js.map → hf-dataset-CKxIJQba.js.map} +1 -1
  57. package/dist/hosted/index.js +1 -1
  58. package/dist/{index-ZuWx52WG.d.ts → index-B16FNdW5.d.ts} +34 -17
  59. package/dist/index-B16FNdW5.d.ts.map +1 -0
  60. package/dist/{index-9jJ2fKSX.d.ts → index-CalAsXMR.d.ts} +7 -5
  61. package/dist/index-CalAsXMR.d.ts.map +1 -0
  62. package/dist/{index-BiT6RtNH.d.ts → index-DbMOXYnP.d.ts} +6 -6
  63. package/dist/{index-BiT6RtNH.d.ts.map → index-DbMOXYnP.d.ts.map} +1 -1
  64. package/dist/index.d.ts +9 -9
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +611 -688
  67. package/dist/index.js.map +1 -1
  68. package/dist/{integrity-DOd9Q86O.js → integrity-C8oUeB0g.js} +322 -197
  69. package/dist/integrity-C8oUeB0g.js.map +1 -0
  70. package/dist/{journal-CR4QikYp.js → journal-C7om8Gum.js} +48 -38
  71. package/dist/journal-C7om8Gum.js.map +1 -0
  72. package/dist/ledger-core/index.d.ts +1 -1
  73. package/dist/ledger-core/index.js +2 -2
  74. package/dist/ledger-core/lock-holder.d.ts +2 -0
  75. package/dist/ledger-core/lock-holder.js +181 -0
  76. package/dist/ledger-core/lock-holder.js.map +1 -0
  77. package/dist/{ledger-core-DrJhbms9.js → ledger-core-CAxUdDk0.js} +2 -2
  78. package/dist/{ledger-core-DrJhbms9.js.map → ledger-core-CAxUdDk0.js.map} +1 -1
  79. package/dist/{llm-client-BshCREn6.js → llm-client-_Y6ic-tf.js} +238 -248
  80. package/dist/llm-client-_Y6ic-tf.js.map +1 -0
  81. package/dist/{llm-judge-C8jMTNNG.js → llm-judge-Djxh-Uhs.js} +646 -458
  82. package/dist/llm-judge-Djxh-Uhs.js.map +1 -0
  83. package/dist/lock-holder-DaJbfpci.d.ts +86 -0
  84. package/dist/lock-holder-DaJbfpci.d.ts.map +1 -0
  85. package/dist/{matrix-DHe-dwVJ.d.ts → matrix-CCSBna4q.d.ts} +2 -2
  86. package/dist/{matrix-DHe-dwVJ.d.ts.map → matrix-CCSBna4q.d.ts.map} +1 -1
  87. package/dist/{mint-zszyYlpA.js → mint-CbdwjTPW.js} +2 -2
  88. package/dist/{mint-zszyYlpA.js.map → mint-CbdwjTPW.js.map} +1 -1
  89. package/dist/multishot/golden/index.d.ts +1 -1
  90. package/dist/multishot/index.d.ts +2 -2
  91. package/dist/openapi.json +1 -1
  92. package/dist/{pareto-BzsxidFD.js → pareto-CTv8vozZ.js} +4 -4
  93. package/dist/{pareto-BzsxidFD.js.map → pareto-CTv8vozZ.js.map} +1 -1
  94. package/dist/{promotion-gate-DaY95yU6.d.ts → promotion-gate-CxEHRsZW.d.ts} +2 -2
  95. package/dist/{promotion-gate-DaY95yU6.d.ts.map → promotion-gate-CxEHRsZW.d.ts.map} +1 -1
  96. package/dist/{proposal-findings-BbWidvUk.js → proposal-findings-n-5WBbEx.js} +2 -2
  97. package/dist/{proposal-findings-BbWidvUk.js.map → proposal-findings-n-5WBbEx.js.map} +1 -1
  98. package/dist/{render-C-w2EZ6p.js → render-Cs34y0iX.js} +404 -208
  99. package/dist/render-Cs34y0iX.js.map +1 -0
  100. package/dist/reporting.d.ts +2 -2
  101. package/dist/{researcher-0ABVIdGa.d.ts → researcher-GU1tV4dn.d.ts} +2 -2
  102. package/dist/{researcher-0ABVIdGa.d.ts.map → researcher-GU1tV4dn.d.ts.map} +1 -1
  103. package/dist/rl.d.ts +2 -2
  104. package/dist/rl.js +4 -4
  105. package/dist/rollout/index.js +5 -5
  106. package/dist/{rollout-D6C50mfA.js → rollout-Dd1ucT_M.js} +5 -5
  107. package/dist/{rollout-D6C50mfA.js.map → rollout-Dd1ucT_M.js.map} +1 -1
  108. package/dist/schema-C-ziyJD7.d.ts.map +1 -1
  109. package/dist/{schema-DxcKTzbi.js → schema-CvhfLjtZ.js} +77 -47
  110. package/dist/schema-CvhfLjtZ.js.map +1 -0
  111. package/dist/search/index.d.ts +2 -2
  112. package/dist/search/index.d.ts.map +1 -1
  113. package/dist/search/index.js +2 -2
  114. package/dist/{search-kernel-DxqjBxA-.d.ts → search-kernel-p9JB3SmL.d.ts} +3 -3
  115. package/dist/{search-kernel-DxqjBxA-.d.ts.map → search-kernel-p9JB3SmL.d.ts.map} +1 -1
  116. package/dist/{search-ledger-TUvB5gWH.js → search-ledger-ClpN4I3e.js} +4 -4
  117. package/dist/{search-ledger-TUvB5gWH.js.map → search-ledger-ClpN4I3e.js.map} +1 -1
  118. package/dist/{search-ledger-file-UKCO2r8a.js → search-ledger-file-Bd5Tioq6.js} +3 -3
  119. package/dist/{search-ledger-file-UKCO2r8a.js.map → search-ledger-file-Bd5Tioq6.js.map} +1 -1
  120. package/dist/{search-shipper-Cw2z3-td.js → search-shipper-Bg2HW3Uv.js} +3 -3
  121. package/dist/{search-shipper-Cw2z3-td.js.map → search-shipper-Bg2HW3Uv.js.map} +1 -1
  122. package/dist/{search-summary-DQIvJsSB.js → search-summary-CrBuvVnf.js} +3 -3
  123. package/dist/{search-summary-DQIvJsSB.js.map → search-summary-CrBuvVnf.js.map} +1 -1
  124. package/dist/{server-DoVbqqtJ.js → server-C6M0qR8h.js} +2 -2
  125. package/dist/{server-DoVbqqtJ.js.map → server-C6M0qR8h.js.map} +1 -1
  126. package/dist/{single-run-lock-U58yf8N6.js → single-run-lock-Dekd8_Wv.js} +7 -7
  127. package/dist/single-run-lock-Dekd8_Wv.js.map +1 -0
  128. package/dist/{skillopt-optimization-method-kyL94-ri.js → skillopt-optimization-method-5dlex7MJ.js} +1339 -1059
  129. package/dist/skillopt-optimization-method-5dlex7MJ.js.map +1 -0
  130. package/dist/{statistical-heldout-C9AbgREN.d.ts → statistical-heldout-BHfLZHrm.d.ts} +2 -2
  131. package/dist/{statistical-heldout-C9AbgREN.d.ts.map → statistical-heldout-BHfLZHrm.d.ts.map} +1 -1
  132. package/dist/{summary-report-DM7tlml1.d.ts → summary-report-CkbRcSuu.d.ts} +16 -1
  133. package/dist/summary-report-CkbRcSuu.d.ts.map +1 -0
  134. package/dist/supervisor-run/index.d.ts.map +1 -1
  135. package/dist/supervisor-run/index.js +3 -3
  136. package/dist/{systemone-Bp_Ac0p1.d.ts → systemone-BMzDJb1i.d.ts} +3 -3
  137. package/dist/{systemone-Bp_Ac0p1.d.ts.map → systemone-BMzDJb1i.d.ts.map} +1 -1
  138. package/dist/systemone.d.ts +2 -2
  139. package/dist/{terminal-record-78g6E4vh.js → terminal-record-D4Rv-j1W.js} +241 -193
  140. package/dist/{terminal-record-78g6E4vh.js.map → terminal-record-D4Rv-j1W.js.map} +1 -1
  141. package/dist/trajectory-replay/index.d.ts +4 -1
  142. package/dist/trajectory-replay/index.d.ts.map +1 -1
  143. package/dist/trajectory-replay/index.js +834 -824
  144. package/dist/trajectory-replay/index.js.map +1 -1
  145. package/dist/{tree-BGRglkKd.js → tree-B5e1uAeL.js} +122 -14
  146. package/dist/tree-B5e1uAeL.js.map +1 -0
  147. package/dist/{types-DtMbFw6U.d.ts → types-BKCzzmit.d.ts} +2 -2
  148. package/dist/{types-DtMbFw6U.d.ts.map → types-BKCzzmit.d.ts.map} +1 -1
  149. package/dist/{types-D8GhJoHk.d.ts → types-DtkO1G9L.d.ts} +2 -2
  150. package/dist/{types-D8GhJoHk.d.ts.map → types-DtkO1G9L.d.ts.map} +1 -1
  151. package/dist/wire/index.js +1 -1
  152. package/docs/design/statistics-decisions.md +1 -1
  153. package/docs/eval-surface-map.md +1 -0
  154. package/docs/profile-json-identity.md +1 -2
  155. package/docs/public-api.md +27 -16
  156. package/package.json +7 -2
  157. package/dist/benchmark-command-xQK8nOI6.js.map +0 -1
  158. package/dist/define-agent-eval-BDxNlP3D.js.map +0 -1
  159. package/dist/eval-campaign-BLEfBGbA.js +0 -343
  160. package/dist/eval-campaign-BLEfBGbA.js.map +0 -1
  161. package/dist/index-9jJ2fKSX.d.ts.map +0 -1
  162. package/dist/index-ZuWx52WG.d.ts.map +0 -1
  163. package/dist/integrity-DOd9Q86O.js.map +0 -1
  164. package/dist/journal-CR4QikYp.js.map +0 -1
  165. package/dist/llm-client-BshCREn6.js.map +0 -1
  166. package/dist/llm-judge-C8jMTNNG.js.map +0 -1
  167. package/dist/render-C-w2EZ6p.js.map +0 -1
  168. package/dist/schema-DxcKTzbi.js.map +0 -1
  169. package/dist/single-run-lock-U58yf8N6.js.map +0 -1
  170. package/dist/skillopt-optimization-method-kyL94-ri.js.map +0 -1
  171. package/dist/summary-report-DM7tlml1.d.ts.map +0 -1
  172. package/dist/tree-BGRglkKd.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { i as JudgeError, n as CaptureIntegrityError, o as NotFoundError, r as ConfigError, s as ValidationError, t as AgentEvalError } from "./errors-BcOs5MKW.js";
3
3
  import { a as hashCanonical, r as canonicalString } from "./canonical-Dq2hOQ8L.js";
4
- import { a as HARNESS_NATIVE_MODEL, c as agentProfileModelId, i as CODING_HARNESSES, l as expandProfileAxes, n as createLlmCorrectnessChecker, o as agentProfileHash, r as verifyCompletion, s as agentProfileId, t as completionVerdict, u as harnessAxisOf } from "./completion-verifier-B_jw6C9p.js";
4
+ import { a as HARNESS_NATIVE_MODEL, c as agentProfileModelId, i as CODING_HARNESSES, l as expandProfileAxes, n as createLlmCorrectnessChecker, o as agentProfileHash, r as verifyCompletion, s as agentProfileId, t as completionVerdict, u as harnessAxisOf } from "./completion-verifier-DbLVf3i8.js";
5
5
  import { a as groupRunsByAgentProfileCell, c as verifyAgentProfileCell, i as buildAgentProfileCell, l as hashJson, o as toAgentProfileJson, r as agentProfileCellKey, s as validateAgentProfileCell, t as AGENT_PROFILE_KINDS, u as manifestContentDigest } from "./agent-profile-cell-B1V1gUZs.js";
6
6
  import { r as observedSplitScore, t as isRealnessGated } from "./reward-nw2xZGZG.js";
7
7
  import { i as isToolSpan, n as isJudgeSpan, r as isLlmSpan, t as FAILURE_CLASSES } from "./schema-Bt2vLFm3.js";
@@ -23,18 +23,18 @@ import { t as eProcess } from "./sequential-eprocess-DodI6RoF.js";
23
23
  import { i as pairedDeltaTest, n as pairedDecisionShape, r as minimumPairsForPairedDeltaTest, t as decidePairedPromotion } from "./paired-promotion-decision-D0SHb-ZL.js";
24
24
  import { c as runsForScenario, i as judgeSpans, t as argHash } from "./query-B_RZ36an.js";
25
25
  import { n as contentHash, r as fileVerdictCache, t as canonicalJson } from "./verdict-cache-CL_7tePD.js";
26
- import { G as runCanaries, H as redTeamDataset, Q as runCampaign, U as redTeamReport, V as DEFAULT_RED_TEAM_CORPUS, W as scoreRedTeamOutput, a as aggregateRunScore, i as transientDispatchFailure, o as clamp01, t as llmJudge } from "./llm-judge-C8jMTNNG.js";
26
+ import { G as runCanaries, H as redTeamDataset, Q as runCampaign, U as redTeamReport, V as DEFAULT_RED_TEAM_CORPUS, W as scoreRedTeamOutput, a as aggregateRunScore, i as transientDispatchFailure, o as clamp01, t as llmJudge } from "./llm-judge-Djxh-Uhs.js";
27
27
  import { a as resolveModelPricing, i as isModelPriced, n as estimateCost, r as estimateTokens, t as MODEL_PRICING } from "./metrics-BKaPBtIG.js";
28
28
  import { c as costForTokenPricing, i as CostCeilingReachedError, l as costForUsage, n as CostAccountingIncompleteError, t as CostLedger } from "./cost-ledger-D0352Z4d.js";
29
- import { D as surfaceContentHash, M as assertRealBackend, N as summarizeBackendIntegrity, _ as parseReflectionResponse, a as ATTESTATION_ALGORITHM, g as buildReflectionPrompt, j as BackendIntegrityError, o as attest, s as verifyAttestation } from "./campaign-evidence-CQYw8_0H.js";
29
+ import { D as surfaceContentHash, M as assertRealBackend, N as summarizeBackendIntegrity, _ as parseReflectionResponse, a as ATTESTATION_ALGORITHM, g as buildReflectionPrompt, j as BackendIntegrityError, o as attest, s as verifyAttestation } from "./campaign-evidence-DunzcJGv.js";
30
30
  import { a as redact, c as shareAllowed, n as assessShareSafety, o as redactForShare, s as redactText, t as REDACTION_VERSION } from "./redact-mtkTiJVk.js";
31
31
  import { a as makeProposalFinding, i as makeFinding, r as computeFindingId } from "./types-DqFMNTgK.js";
32
- import { S as requiredPairsForPairedPromotion, n as paretoFrontier, t as dominates, x as pairedPromotionPower } from "./pareto-BzsxidFD.js";
32
+ import { S as requiredPairsForPairedPromotion, n as paretoFrontier, t as dominates, x as pairedPromotionPower } from "./pareto-CTv8vozZ.js";
33
33
  import { a as LlmCallError, i as stripFencedJson, n as isRecord, o as LlmResponseError } from "./json-BhdsJaOQ.js";
34
- import { r as selfImprove, t as defineAgentEval } from "./define-agent-eval-BDxNlP3D.js";
34
+ import { r as selfImprove, t as defineAgentEval } from "./define-agent-eval-BGdFKlrc.js";
35
35
  import { n as equivalenceVerdict, t as certificationEvidenceDigest } from "./verdict-BGQmxQ8A.js";
36
36
  import { c as createBoundedTraceAnalysisStore, p as DEFAULT_TRACE_ANALYST_BUDGETS, t as createTraceAnalyst } from "./kind-factory-Gy4fXixd.js";
37
- import { _ as readAnalystReview, c as FAILURE_MODE_KIND_SPEC, g as completedAnalystReviewQuality, h as assertUniqueFindingIds, i as DEFAULT_TRACE_ANALYST_KINDS, m as analystRunDigest, n as AnalystRegistry, p as analystFindingDigest, t as buildDefaultAnalystRegistry, v as snapshotAnalystRun, y as validateAnalystReviewDecisions } from "./default-registry-ClYjq3QE.js";
37
+ import { _ as readAnalystReview, c as FAILURE_MODE_KIND_SPEC, g as completedAnalystReviewQuality, h as assertUniqueFindingIds, i as DEFAULT_TRACE_ANALYST_KINDS, m as analystRunDigest, n as AnalystRegistry, p as analystFindingDigest, t as buildDefaultAnalystRegistry, v as snapshotAnalystRun, y as validateAnalystReviewDecisions } from "./default-registry-BJ-Pq6yL.js";
38
38
  import { OUTPUT_VALUE } from "./trace-attributes.js";
39
39
  import { n as extractUsageFromSse, t as extractUsage } from "./extract-usage-BZLU01kR.js";
40
40
  import { n as InMemoryRawProviderSink, r as NoopRawProviderSink, t as FileSystemRawProviderSink } from "./raw-provider-sink-75w4FWBj.js";
@@ -46,19 +46,19 @@ import { t as packageVersion$1 } from "./package-version-D7lQHt_-.js";
46
46
  import { t as buildTrajectory } from "./trajectory-D_7rLrvE.js";
47
47
  import { i as failureBlame, n as classifyFailure, r as classifyFailureReason } from "./failure-taxonomy-Bx5oWqaW.js";
48
48
  import { n as runBoundedProcess, t as DEFAULT_BOUNDED_PROCESS_TIMEOUT_MS } from "./bounded-process-Cj9YjXbE.js";
49
- import { t as runEvalCampaign } from "./eval-campaign-BLEfBGbA.js";
50
- import "./rollout-D6C50mfA.js";
51
- import { t as mintRolloutRows } from "./mint-zszyYlpA.js";
49
+ import { t as runEvalCampaign } from "./eval-campaign-BUMIxKTE.js";
50
+ import "./rollout-Dd1ucT_M.js";
51
+ import { t as mintRolloutRows } from "./mint-CbdwjTPW.js";
52
52
  import { n as pairedEvalueSequence, t as evaluateInterimReleaseConfidence } from "./sequential-BVv1sjsw.js";
53
- import { a as maximumChargeForLlmRequest, c as ModelSubstitutionError, f as assertServedModels, g as judgeFamily, h as assertCrossFamily, i as costReceiptFromLlmError, l as assertCrossFamilyServed, m as CrossFamilyError, o as backoffMs, p as checkServedModel, r as costReceiptFromLlm, s as isTransientLlmError, u as assertServedModel } from "./llm-client-BshCREn6.js";
54
- import { n as paidJsonChat } from "./chat-json-call-DavzexSp.js";
55
- import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-DhdCr1Dc.js";
56
- import { n as diffFindings, r as createChatTraceEngine, t as FindingsStore } from "./findings-store-BMVQtEsm.js";
53
+ import { a as maximumChargeForLlmRequest, c as ModelSubstitutionError, f as assertServedModels, g as judgeFamily, h as assertCrossFamily, i as costReceiptFromLlmError, l as assertCrossFamilyServed, m as CrossFamilyError, o as backoffMs, p as checkServedModel, r as costReceiptFromLlm, s as isTransientLlmError, u as assertServedModel } from "./llm-client-_Y6ic-tf.js";
54
+ import { n as paidJsonChat } from "./chat-json-call-Dm9c-b41.js";
55
+ import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-CSMwudLO.js";
56
+ import { n as diffFindings, r as createChatTraceEngine, t as FindingsStore } from "./findings-store-BtTzPL0d.js";
57
57
  import { a as budgetBreachView, i as failureClusterView, n as computeToolUseMetrics, r as judgeAgreementView, t as toolWasteView } from "./tool-waste-DLsjS_p-.js";
58
58
  import { a as judgeGateDecision, c as snapshotFromJudgeAgreement, g as analyzeSeries, i as assertJudgeMayGate, n as JudgeGateRefusedError, o as measureJudgeAgreement, r as addCalibrationVerdicts, s as registerCalibrationSet, t as DEFAULT_JUDGE_GATE_POLICY } from "./judge-gate-Co5tuh57.js";
59
59
  import { a as spanRecordsToTraceAnalysisStore, i as otlpTextToTraceAnalysisStore, r as OtlpFileTraceStore } from "./store-otlp-BteDAbHK.js";
60
60
  import { n as bootstrapCi, t as evaluateReleaseConfidence } from "./release-confidence-CYIFH-fp.js";
61
- import { t as createChatClient } from "./chat-client-ldqxYYTN.js";
61
+ import { t as createChatClient } from "./chat-client-B2pXlPdx.js";
62
62
  import { createHash } from "node:crypto";
63
63
  import { accessSync, appendFileSync, constants, cpSync, existsSync, mkdirSync, promises, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
64
64
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -3738,7 +3738,6 @@ function observeAll(detectors, event) {
3738
3738
  //#region src/control-runtime.ts
3739
3739
  async function runAgentControlLoop(config) {
3740
3740
  const budget = normalizeBudget(config.budget);
3741
- const actionFailure = config.actionFailure ?? "continue";
3742
3741
  const controller = new AbortController();
3743
3742
  const upstreamAbort = () => controller.abort(config.signal?.reason);
3744
3743
  if (config.signal) {
@@ -3747,473 +3746,51 @@ async function runAgentControlLoop(config) {
3747
3746
  }
3748
3747
  const started = Date.now();
3749
3748
  const wallTimer = budget.maxWallMs ? setTimeout(() => controller.abort(/* @__PURE__ */ new Error("control runtime wall timeout")), budget.maxWallMs) : void 0;
3750
- const history = [];
3751
- const emitter = config.store ? new TraceEmitter(config.store, { onCaptureError: () => void 0 }) : void 0;
3752
- let spentCostUsd = 0;
3753
- const runtimeErrors = [];
3754
- const repeatedDetector = repeatedActionDetector({ maxRepeated: config.stopPolicies?.maxRepeatedActions ?? 0 });
3755
- const progressDetector = noProgressDetector({
3756
- maxNoProgress: config.stopPolicies?.maxNoProgressSteps ?? 0,
3757
- minScoreDelta: config.stopPolicies?.minScoreDelta ?? .001
3758
- });
3749
+ const run = {
3750
+ config,
3751
+ budget,
3752
+ signal: controller.signal,
3753
+ started,
3754
+ history: [],
3755
+ emitter: config.store ? new TraceEmitter(config.store, { onCaptureError: () => void 0 }) : void 0,
3756
+ runtimeErrors: [],
3757
+ repeatedDetector: repeatedActionDetector({ maxRepeated: config.stopPolicies?.maxRepeatedActions ?? 0 }),
3758
+ progressDetector: noProgressDetector({
3759
+ maxNoProgress: config.stopPolicies?.maxNoProgressSteps ?? 0,
3760
+ minScoreDelta: config.stopPolicies?.minScoreDelta ?? .001
3761
+ }),
3762
+ spentCostUsd: 0,
3763
+ state: void 0,
3764
+ evals: []
3765
+ };
3759
3766
  try {
3760
- if (emitter) await runTrace(emitter, runtimeErrors, 0, () => emitter.startRun({
3761
- scenarioId: config.scenarioId ?? "agent-control-loop",
3762
- projectId: config.projectId,
3763
- variantId: config.variantId,
3764
- layer: "meta",
3765
- tags: {
3766
- intent: config.intent.slice(0, 120),
3767
- maxSteps: String(budget.maxSteps),
3768
- ...budget.maxCostUsd !== void 0 ? { maxCostUsd: String(budget.maxCostUsd) } : {}
3769
- }
3770
- }));
3771
- let state;
3772
- let evals;
3773
- try {
3774
- state = await config.observe({
3775
- history,
3776
- abortSignal: controller.signal
3777
- });
3778
- } catch (err) {
3779
- const error = runtimeError("observe", 0, err);
3780
- runtimeErrors.push(error);
3781
- return finish(emitter, {
3782
- intent: config.intent,
3783
- pass: false,
3784
- completed: false,
3785
- reason: error.message,
3786
- steps: history,
3787
- finalState: void 0,
3788
- finalEvals: [],
3789
- wallMs: Date.now() - started,
3790
- spentCostUsd,
3791
- runId: emitter?.runId ?? null,
3792
- failureClass: "unknown",
3793
- runtimeErrors,
3794
- stoppedBy: "runtime-error"
3795
- });
3796
- }
3797
- try {
3798
- evals = await config.validate({
3799
- intent: config.intent,
3800
- state,
3801
- history,
3802
- abortSignal: controller.signal
3803
- });
3804
- await recordEvalSpans(emitter, evals, "initial", runtimeErrors, 0);
3805
- } catch (err) {
3806
- const error = runtimeError("validate", 0, err);
3807
- runtimeErrors.push(error);
3808
- return finish(emitter, {
3809
- intent: config.intent,
3810
- pass: false,
3811
- completed: false,
3812
- reason: error.message,
3813
- steps: history,
3814
- finalState: state,
3815
- finalEvals: [],
3816
- wallMs: Date.now() - started,
3817
- spentCostUsd,
3818
- runId: emitter?.runId ?? null,
3819
- failureClass: "unknown",
3820
- runtimeErrors,
3821
- stoppedBy: "runtime-error"
3822
- });
3823
- }
3824
- progressDetector.observe({
3825
- stateFingerprint: fingerprintState(state, config.stopPolicies),
3826
- score: averageScore(evals)
3767
+ await startTrace(run);
3768
+ const failedStart = await observeInitial(run);
3769
+ if (failedStart) return failedStart;
3770
+ run.progressDetector.observe({
3771
+ stateFingerprint: fingerprintState(run.state, config.stopPolicies),
3772
+ score: averageScore(run.evals)
3827
3773
  });
3828
3774
  for (let stepIndex = 0; stepIndex < budget.maxSteps; stepIndex++) {
3829
- if (controller.signal.aborted) return finish(emitter, {
3830
- intent: config.intent,
3831
- pass: false,
3832
- completed: false,
3833
- reason: abortReason(controller.signal),
3834
- score: void 0,
3835
- steps: history,
3836
- finalState: state,
3837
- finalEvals: evals,
3838
- wallMs: Date.now() - started,
3839
- spentCostUsd,
3840
- runId: emitter?.runId ?? null,
3841
- failureClass: "timeout",
3842
- runtimeErrors,
3843
- stoppedBy: "abort"
3844
- });
3845
- const budgetStop = budgetStopDecision(budget, spentCostUsd);
3846
- if (budgetStop.stop) return finish(emitter, {
3847
- intent: config.intent,
3848
- pass: false,
3849
- completed: false,
3850
- reason: budgetStop.reason,
3851
- score: averageScore(evals),
3852
- steps: history,
3853
- finalState: state,
3854
- finalEvals: evals,
3855
- wallMs: Date.now() - started,
3856
- spentCostUsd,
3857
- runId: emitter?.runId ?? null,
3858
- failureClass: "budget_exceeded",
3859
- runtimeErrors,
3860
- stoppedBy: "budget"
3861
- });
3862
- const ctx = makeContext(config.intent, state, evals, history, budget, stepIndex, started, spentCostUsd, controller.signal, emitter);
3863
- let stop;
3864
- try {
3865
- stop = config.shouldStop ? await config.shouldStop(ctx) : defaultStopDecision(evals);
3866
- } catch (err) {
3867
- runtimeErrors.push(runtimeError("stop-policy", stepIndex, err));
3868
- return finish(emitter, {
3869
- intent: config.intent,
3870
- pass: false,
3871
- completed: false,
3872
- reason: runtimeErrors[runtimeErrors.length - 1].message,
3873
- score: averageScore(evals),
3874
- steps: history,
3875
- finalState: state,
3876
- finalEvals: evals,
3877
- wallMs: Date.now() - started,
3878
- spentCostUsd,
3879
- runId: emitter?.runId ?? null,
3880
- failureClass: "unknown",
3881
- runtimeErrors,
3882
- stoppedBy: "runtime-error"
3883
- });
3884
- }
3885
- if (stop.stop) return finish(emitter, {
3886
- intent: config.intent,
3887
- pass: stop.pass,
3888
- completed: true,
3889
- reason: stop.reason,
3890
- score: stop.score,
3891
- steps: history,
3892
- finalState: state,
3893
- finalEvals: evals,
3894
- wallMs: Date.now() - started,
3895
- spentCostUsd,
3896
- runId: emitter?.runId ?? null,
3897
- failureClass: stop.failureClass,
3898
- runtimeErrors,
3899
- stoppedBy: "stop-policy"
3900
- });
3901
- let decision;
3902
- try {
3903
- decision = await config.decide(ctx);
3904
- } catch (err) {
3905
- runtimeErrors.push(runtimeError("decide", stepIndex, err));
3906
- return finish(emitter, {
3907
- intent: config.intent,
3908
- pass: false,
3909
- completed: false,
3910
- reason: runtimeErrors[runtimeErrors.length - 1].message,
3911
- score: averageScore(evals),
3912
- steps: history,
3913
- finalState: state,
3914
- finalEvals: evals,
3915
- wallMs: Date.now() - started,
3916
- spentCostUsd,
3917
- runId: emitter?.runId ?? null,
3918
- failureClass: "unknown",
3919
- runtimeErrors,
3920
- stoppedBy: "runtime-error"
3921
- });
3922
- }
3923
- if (decision.type === "stop") {
3924
- const pass = decision.pass ?? false;
3925
- return finish(emitter, {
3926
- intent: config.intent,
3927
- pass,
3928
- completed: true,
3929
- reason: decision.reason,
3930
- score: decision.score,
3931
- steps: history,
3932
- finalState: state,
3933
- finalEvals: evals,
3934
- wallMs: Date.now() - started,
3935
- spentCostUsd,
3936
- runId: emitter?.runId ?? null,
3937
- failureClass: pass ? void 0 : decision.failureClass ?? "unknown",
3938
- runtimeErrors,
3939
- stoppedBy: "policy"
3940
- });
3941
- }
3942
- const actionFingerprint = fingerprintAction(decision.action, config.stopPolicies);
3943
- const repeatedActionSignal = repeatedDetector.observe({ actionFingerprint });
3944
- const repeatedActionStreak = repeatedDetector.streak;
3945
- if (repeatedActionSignal) return finish(emitter, {
3946
- intent: config.intent,
3947
- pass: false,
3948
- completed: true,
3949
- reason: repeatedActionSignal.reason,
3950
- score: averageScore(evals),
3951
- steps: history,
3952
- finalState: state,
3953
- finalEvals: evals,
3954
- wallMs: Date.now() - started,
3955
- spentCostUsd,
3956
- runId: emitter?.runId ?? null,
3957
- failureClass: "tool_recovery_failure",
3958
- runtimeErrors,
3959
- stoppedBy: "stop-policy"
3960
- });
3961
- const beforeState = state;
3962
- const evalsBefore = evals;
3963
- const scoreBefore = averageScore(evals);
3964
- const actionStarted = Date.now();
3965
- const stepHandle = emitter ? await runTrace(emitter, runtimeErrors, stepIndex, () => emitter.tool({
3966
- name: `control-step-${stepIndex}`,
3967
- toolName: "agent-control-action",
3968
- args: decision.action,
3969
- attributes: {
3970
- decision: decision.reason ?? "continue",
3971
- repeatedActionStreak
3972
- }
3973
- })) : void 0;
3974
- let actionOutcome;
3975
- try {
3976
- const result = await config.act(decision.action, ctx);
3977
- const rawCostUsd = config.getActionCostUsd?.({
3978
- action: decision.action,
3979
- result,
3980
- state,
3981
- evals,
3982
- history
3983
- });
3984
- const costUsd = normalizeActionCostUsd(rawCostUsd, runtimeErrors, stepIndex);
3985
- if (costUsd !== void 0 && Number.isFinite(costUsd) && costUsd > 0) {
3986
- spentCostUsd += costUsd;
3987
- await recordCostBudget(emitter, budget, spentCostUsd, stepHandle, runtimeErrors, stepIndex);
3988
- }
3989
- actionOutcome = {
3990
- ok: true,
3991
- result,
3992
- ...costUsd !== void 0 ? { costUsd } : {},
3993
- durationMs: Date.now() - actionStarted
3994
- };
3995
- } catch (err) {
3996
- runtimeErrors.push(runtimeError("act", stepIndex, err));
3997
- actionOutcome = {
3998
- ok: false,
3999
- error: runtimeErrors[runtimeErrors.length - 1].message,
4000
- durationMs: Date.now() - actionStarted
4001
- };
4002
- if (actionFailure === "stop") {
4003
- await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.fail(actionOutcome.error ?? "action failed"));
4004
- const step = {
4005
- index: stepIndex,
4006
- decision,
4007
- beforeState,
4008
- afterState: state,
4009
- evalsBefore,
4010
- evalsAfter: evals,
4011
- actionOutcome,
4012
- startedAt: new Date(actionStarted).toISOString(),
4013
- endedAt: (/* @__PURE__ */ new Date()).toISOString()
4014
- };
4015
- history.push(step);
4016
- await runOnStep(config.onStep, step, runtimeErrors);
4017
- return finish(emitter, {
4018
- intent: config.intent,
4019
- pass: false,
4020
- completed: false,
4021
- reason: actionOutcome.error ?? "action failed",
4022
- score: averageScore(evals),
4023
- steps: history,
4024
- finalState: state,
4025
- finalEvals: evals,
4026
- wallMs: Date.now() - started,
4027
- spentCostUsd,
4028
- runId: emitter?.runId ?? null,
4029
- failureClass: "unknown",
4030
- runtimeErrors,
4031
- stoppedBy: "runtime-error"
4032
- });
4033
- }
4034
- }
4035
- let observationPhase = "observe";
4036
- try {
4037
- state = await config.observe({
4038
- history,
4039
- abortSignal: controller.signal
4040
- });
4041
- observationPhase = "validate";
4042
- evals = await config.validate({
4043
- intent: config.intent,
4044
- state,
4045
- history,
4046
- abortSignal: controller.signal
4047
- });
4048
- await recordEvalSpans(emitter, evals, `step-${stepIndex}`, runtimeErrors, stepIndex, stepHandle?.span.spanId);
4049
- } catch (err) {
4050
- runtimeErrors.push(runtimeError(observationPhase, stepIndex, err));
4051
- const afterState = observationPhase === "observe" ? beforeState : state;
4052
- const step = {
4053
- index: stepIndex,
4054
- decision,
4055
- beforeState,
4056
- afterState,
4057
- evalsBefore,
4058
- evalsAfter: evals,
4059
- actionOutcome,
4060
- startedAt: new Date(actionStarted).toISOString(),
4061
- endedAt: (/* @__PURE__ */ new Date()).toISOString()
4062
- };
4063
- history.push(step);
4064
- await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.fail(runtimeErrors[runtimeErrors.length - 1].message));
4065
- await runOnStep(config.onStep, step, runtimeErrors);
4066
- return finish(emitter, {
4067
- intent: config.intent,
4068
- pass: false,
4069
- completed: false,
4070
- reason: runtimeErrors[runtimeErrors.length - 1].message,
4071
- score: averageScore(evals),
4072
- steps: history,
4073
- finalState: afterState,
4074
- finalEvals: evals,
4075
- wallMs: Date.now() - started,
4076
- spentCostUsd,
4077
- runId: emitter?.runId ?? null,
4078
- failureClass: "unknown",
4079
- runtimeErrors,
4080
- stoppedBy: "runtime-error"
4081
- });
4082
- }
4083
- const scoreAfter = averageScore(evals);
4084
- const stateFingerprint = fingerprintState(state, config.stopPolicies);
4085
- const noProgressSignal = progressDetector.observe({
4086
- stateFingerprint,
4087
- score: scoreAfter
4088
- });
4089
- const noProgressStreak = progressDetector.streak;
4090
- const step = {
4091
- index: stepIndex,
4092
- decision,
4093
- beforeState,
4094
- afterState: state,
4095
- evalsBefore,
4096
- evalsAfter: evals,
4097
- actionOutcome,
4098
- startedAt: new Date(actionStarted).toISOString(),
4099
- endedAt: (/* @__PURE__ */ new Date()).toISOString()
4100
- };
4101
- history.push(step);
4102
- if (actionOutcome.ok) await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.end({ attributes: {
4103
- actionCostUsd: actionOutcome.costUsd ?? null,
4104
- spentCostUsd,
4105
- scoreBefore: scoreBefore ?? null,
4106
- scoreAfter: scoreAfter ?? null,
4107
- noProgressStreak
4108
- } }));
4109
- else await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.fail(actionOutcome.error ?? "action failed", { attributes: {
4110
- spentCostUsd,
4111
- noProgressStreak
4112
- } }));
4113
- await runOnStep(config.onStep, step, runtimeErrors);
4114
- if (noProgressSignal) return finish(emitter, {
4115
- intent: config.intent,
4116
- pass: false,
4117
- completed: true,
4118
- reason: noProgressSignal.reason,
4119
- score: scoreAfter,
4120
- steps: history,
4121
- finalState: state,
4122
- finalEvals: evals,
4123
- wallMs: Date.now() - started,
4124
- spentCostUsd,
4125
- runId: emitter?.runId ?? null,
4126
- failureClass: "tool_recovery_failure",
4127
- runtimeErrors,
4128
- stoppedBy: "stop-policy"
4129
- });
4130
- const postStepBudgetStop = budgetStopDecision(budget, spentCostUsd);
4131
- if (postStepBudgetStop.stop) return finish(emitter, {
4132
- intent: config.intent,
4133
- pass: false,
4134
- completed: false,
4135
- reason: postStepBudgetStop.reason,
4136
- score: scoreAfter,
4137
- steps: history,
4138
- finalState: state,
4139
- finalEvals: evals,
4140
- wallMs: Date.now() - started,
4141
- spentCostUsd,
4142
- runId: emitter?.runId ?? null,
4143
- failureClass: "budget_exceeded",
4144
- runtimeErrors,
4145
- stoppedBy: "budget"
4146
- });
4147
- const postStepCtx = makeContext(config.intent, state, evals, history, budget, stepIndex + 1, started, spentCostUsd, controller.signal, emitter);
4148
- let postStepStop;
4149
- try {
4150
- postStepStop = config.shouldStop ? await config.shouldStop(postStepCtx) : defaultStopDecision(evals);
4151
- } catch (err) {
4152
- runtimeErrors.push(runtimeError("stop-policy", stepIndex + 1, err));
4153
- return finish(emitter, {
4154
- intent: config.intent,
4155
- pass: false,
4156
- completed: false,
4157
- reason: runtimeErrors[runtimeErrors.length - 1].message,
4158
- score: averageScore(evals),
4159
- steps: history,
4160
- finalState: state,
4161
- finalEvals: evals,
4162
- wallMs: Date.now() - started,
4163
- spentCostUsd,
4164
- runId: emitter?.runId ?? null,
4165
- failureClass: "unknown",
4166
- runtimeErrors,
4167
- stoppedBy: "runtime-error"
4168
- });
4169
- }
4170
- if (postStepStop.stop) return finish(emitter, {
4171
- intent: config.intent,
4172
- pass: postStepStop.pass,
4173
- completed: true,
4174
- reason: postStepStop.reason,
4175
- score: postStepStop.score,
4176
- steps: history,
4177
- finalState: state,
4178
- finalEvals: evals,
4179
- wallMs: Date.now() - started,
4180
- spentCostUsd,
4181
- runId: emitter?.runId ?? null,
4182
- failureClass: postStepStop.failureClass,
4183
- runtimeErrors,
4184
- stoppedBy: "stop-policy"
4185
- });
3775
+ const ended = await runStep(run, stepIndex);
3776
+ if (ended) return ended;
4186
3777
  }
4187
- return finish(emitter, {
4188
- intent: config.intent,
3778
+ return settle(run, {
4189
3779
  pass: false,
4190
3780
  completed: false,
4191
3781
  reason: `budget exhausted: maxSteps=${budget.maxSteps}`,
4192
- steps: history,
4193
- finalState: state,
4194
- finalEvals: evals,
4195
- wallMs: Date.now() - started,
4196
- spentCostUsd,
4197
- runId: emitter?.runId ?? null,
4198
3782
  failureClass: "budget_exceeded",
4199
- runtimeErrors,
4200
3783
  stoppedBy: "budget"
4201
3784
  });
4202
3785
  } catch (err) {
4203
- runtimeErrors.push(runtimeError("act", history.length, err));
4204
- return finish(emitter, {
4205
- intent: config.intent,
3786
+ run.runtimeErrors.push(runtimeError("act", run.history.length, err));
3787
+ return settle(run, {
4206
3788
  pass: false,
4207
3789
  completed: false,
4208
- reason: runtimeErrors[runtimeErrors.length - 1].message,
4209
- steps: history,
3790
+ reason: lastErrorMessage(run),
4210
3791
  finalState: void 0,
4211
3792
  finalEvals: [],
4212
- wallMs: Date.now() - started,
4213
- spentCostUsd,
4214
- runId: emitter?.runId ?? null,
4215
3793
  failureClass: "unknown",
4216
- runtimeErrors,
4217
3794
  stoppedBy: "runtime-error"
4218
3795
  });
4219
3796
  } finally {
@@ -4221,6 +3798,349 @@ async function runAgentControlLoop(config) {
4221
3798
  if (config.signal) config.signal.removeEventListener("abort", upstreamAbort);
4222
3799
  }
4223
3800
  }
3801
+ /** End the run with `how`; everything not named there is read from the run as it stands now. */
3802
+ function settle(run, how) {
3803
+ return finish(run.emitter, {
3804
+ intent: run.config.intent,
3805
+ pass: how.pass,
3806
+ completed: how.completed,
3807
+ reason: how.reason,
3808
+ ..."score" in how ? { score: how.score } : {},
3809
+ steps: run.history,
3810
+ finalState: "finalState" in how ? how.finalState : run.state,
3811
+ finalEvals: how.finalEvals ?? run.evals,
3812
+ wallMs: Date.now() - run.started,
3813
+ spentCostUsd: run.spentCostUsd,
3814
+ runId: run.emitter?.runId ?? null,
3815
+ failureClass: how.failureClass,
3816
+ runtimeErrors: run.runtimeErrors,
3817
+ stoppedBy: how.stoppedBy
3818
+ });
3819
+ }
3820
+ function lastErrorMessage(run) {
3821
+ return run.runtimeErrors[run.runtimeErrors.length - 1].message;
3822
+ }
3823
+ /** End the run on the runtime error just recorded. */
3824
+ function settleOnRuntimeError(run, finalState) {
3825
+ return settle(run, {
3826
+ pass: false,
3827
+ completed: false,
3828
+ reason: lastErrorMessage(run),
3829
+ score: averageScore(run.evals),
3830
+ ...finalState ? { finalState: finalState.value } : {},
3831
+ failureClass: "unknown",
3832
+ stoppedBy: "runtime-error"
3833
+ });
3834
+ }
3835
+ async function startTrace(run) {
3836
+ const { emitter, config, budget } = run;
3837
+ if (!emitter) return;
3838
+ await runTrace(emitter, run.runtimeErrors, 0, () => emitter.startRun({
3839
+ scenarioId: config.scenarioId ?? "agent-control-loop",
3840
+ projectId: config.projectId,
3841
+ variantId: config.variantId,
3842
+ layer: "meta",
3843
+ tags: {
3844
+ intent: config.intent.slice(0, 120),
3845
+ maxSteps: String(budget.maxSteps),
3846
+ ...budget.maxCostUsd !== void 0 ? { maxCostUsd: String(budget.maxCostUsd) } : {}
3847
+ }
3848
+ }));
3849
+ }
3850
+ /** The first observation and validation. Returns the result that ends the run when either fails. */
3851
+ async function observeInitial(run) {
3852
+ try {
3853
+ run.state = await run.config.observe({
3854
+ history: run.history,
3855
+ abortSignal: run.signal
3856
+ });
3857
+ } catch (err) {
3858
+ const error = runtimeError("observe", 0, err);
3859
+ run.runtimeErrors.push(error);
3860
+ return settle(run, {
3861
+ pass: false,
3862
+ completed: false,
3863
+ reason: error.message,
3864
+ failureClass: "unknown",
3865
+ stoppedBy: "runtime-error"
3866
+ });
3867
+ }
3868
+ try {
3869
+ run.evals = await run.config.validate({
3870
+ intent: run.config.intent,
3871
+ state: run.state,
3872
+ history: run.history,
3873
+ abortSignal: run.signal
3874
+ });
3875
+ await recordEvalSpans(run.emitter, run.evals, "initial", run.runtimeErrors, 0);
3876
+ } catch (err) {
3877
+ const error = runtimeError("validate", 0, err);
3878
+ run.runtimeErrors.push(error);
3879
+ return settle(run, {
3880
+ pass: false,
3881
+ completed: false,
3882
+ reason: error.message,
3883
+ finalEvals: [],
3884
+ failureClass: "unknown",
3885
+ stoppedBy: "runtime-error"
3886
+ });
3887
+ }
3888
+ }
3889
+ /** One decide-act-observe step. Returns the result that ends the run, or nothing to go on. */
3890
+ async function runStep(run, stepIndex) {
3891
+ const halted = await checkAbortAndBudget(run);
3892
+ if (halted) return halted;
3893
+ const ctx = makeRunContext(run, stepIndex);
3894
+ const stopped = await checkStopPolicy(run, ctx, stepIndex);
3895
+ if (stopped) return stopped;
3896
+ const chosen = await decideAction(run, ctx, stepIndex);
3897
+ if ("ended" in chosen) return chosen.ended;
3898
+ const { decision } = chosen.next;
3899
+ const repeated = await detectRepeatedAction(run, decision);
3900
+ if ("ended" in repeated) return repeated.ended;
3901
+ const attempt = await attemptAction(run, ctx, decision, stepIndex, repeated.next);
3902
+ if ("ended" in attempt) return attempt.ended;
3903
+ const observed = await observeAfterAction(run, attempt.next, stepIndex);
3904
+ if (observed) return observed;
3905
+ return recordStepAndCheck(run, attempt.next, stepIndex);
3906
+ }
3907
+ async function checkAbortAndBudget(run) {
3908
+ if (run.signal.aborted) return settle(run, {
3909
+ pass: false,
3910
+ completed: false,
3911
+ reason: abortReason(run.signal),
3912
+ score: void 0,
3913
+ failureClass: "timeout",
3914
+ stoppedBy: "abort"
3915
+ });
3916
+ const budgetStop = budgetStopDecision(run.budget, run.spentCostUsd);
3917
+ if (!budgetStop.stop) return void 0;
3918
+ return settle(run, {
3919
+ pass: false,
3920
+ completed: false,
3921
+ reason: budgetStop.reason,
3922
+ score: averageScore(run.evals),
3923
+ failureClass: "budget_exceeded",
3924
+ stoppedBy: "budget"
3925
+ });
3926
+ }
3927
+ function makeRunContext(run, stepIndex) {
3928
+ return makeContext(run.config.intent, run.state, run.evals, run.history, run.budget, stepIndex, run.started, run.spentCostUsd, run.signal, run.emitter);
3929
+ }
3930
+ /** Ask the stop policy about the current state. `errorStep` is the step a failing policy is recorded against. */
3931
+ async function checkStopPolicy(run, ctx, errorStep) {
3932
+ let stop;
3933
+ try {
3934
+ stop = run.config.shouldStop ? await run.config.shouldStop(ctx) : defaultStopDecision(run.evals);
3935
+ } catch (err) {
3936
+ run.runtimeErrors.push(runtimeError("stop-policy", errorStep, err));
3937
+ return settleOnRuntimeError(run);
3938
+ }
3939
+ if (!stop.stop) return void 0;
3940
+ return settle(run, {
3941
+ pass: stop.pass,
3942
+ completed: true,
3943
+ reason: stop.reason,
3944
+ score: stop.score,
3945
+ failureClass: stop.failureClass,
3946
+ stoppedBy: "stop-policy"
3947
+ });
3948
+ }
3949
+ async function decideAction(run, ctx, stepIndex) {
3950
+ let decision;
3951
+ try {
3952
+ decision = await run.config.decide(ctx);
3953
+ } catch (err) {
3954
+ run.runtimeErrors.push(runtimeError("decide", stepIndex, err));
3955
+ return { ended: await settleOnRuntimeError(run) };
3956
+ }
3957
+ if (decision.type !== "stop") return { next: { decision } };
3958
+ const pass = decision.pass ?? false;
3959
+ return { ended: await settle(run, {
3960
+ pass,
3961
+ completed: true,
3962
+ reason: decision.reason,
3963
+ score: decision.score,
3964
+ failureClass: pass ? void 0 : decision.failureClass ?? "unknown",
3965
+ stoppedBy: "policy"
3966
+ }) };
3967
+ }
3968
+ /** Feed the chosen action to the repeated-action detector; its streak is returned for the step's trace. */
3969
+ async function detectRepeatedAction(run, decision) {
3970
+ const actionFingerprint = fingerprintAction(decision.action, run.config.stopPolicies);
3971
+ const signal = run.repeatedDetector.observe({ actionFingerprint });
3972
+ if (!signal) return { next: run.repeatedDetector.streak };
3973
+ return { ended: await settle(run, {
3974
+ pass: false,
3975
+ completed: true,
3976
+ reason: signal.reason,
3977
+ score: averageScore(run.evals),
3978
+ failureClass: "tool_recovery_failure",
3979
+ stoppedBy: "stop-policy"
3980
+ }) };
3981
+ }
3982
+ /** Run the chosen action under its trace span and price it. A failed action ends the run only when `actionFailure` is 'stop'. */
3983
+ async function attemptAction(run, ctx, decision, stepIndex, repeatedActionStreak) {
3984
+ const { config, emitter, runtimeErrors } = run;
3985
+ const beforeState = run.state;
3986
+ const evalsBefore = run.evals;
3987
+ const scoreBefore = averageScore(run.evals);
3988
+ const actionStarted = Date.now();
3989
+ const stepHandle = emitter ? await runTrace(emitter, runtimeErrors, stepIndex, () => emitter.tool({
3990
+ name: `control-step-${stepIndex}`,
3991
+ toolName: "agent-control-action",
3992
+ args: decision.action,
3993
+ attributes: {
3994
+ decision: decision.reason ?? "continue",
3995
+ repeatedActionStreak
3996
+ }
3997
+ })) : void 0;
3998
+ let outcome;
3999
+ try {
4000
+ const result = await config.act(decision.action, ctx);
4001
+ const rawCostUsd = config.getActionCostUsd?.({
4002
+ action: decision.action,
4003
+ result,
4004
+ state: run.state,
4005
+ evals: run.evals,
4006
+ history: run.history
4007
+ });
4008
+ const costUsd = normalizeActionCostUsd(rawCostUsd, runtimeErrors, stepIndex);
4009
+ if (costUsd !== void 0 && Number.isFinite(costUsd) && costUsd > 0) {
4010
+ run.spentCostUsd += costUsd;
4011
+ await recordCostBudget(emitter, run.budget, run.spentCostUsd, stepHandle, runtimeErrors, stepIndex);
4012
+ }
4013
+ outcome = {
4014
+ ok: true,
4015
+ result,
4016
+ ...costUsd !== void 0 ? { costUsd } : {},
4017
+ durationMs: Date.now() - actionStarted
4018
+ };
4019
+ } catch (err) {
4020
+ runtimeErrors.push(runtimeError("act", stepIndex, err));
4021
+ outcome = {
4022
+ ok: false,
4023
+ error: lastErrorMessage(run),
4024
+ durationMs: Date.now() - actionStarted
4025
+ };
4026
+ if ((config.actionFailure ?? "continue") === "stop") {
4027
+ await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.fail(outcome.error ?? "action failed"));
4028
+ const step = buildStep(run, {
4029
+ decision,
4030
+ beforeState,
4031
+ evalsBefore,
4032
+ scoreBefore,
4033
+ actionStarted,
4034
+ stepHandle,
4035
+ outcome
4036
+ }, stepIndex, run.state);
4037
+ run.history.push(step);
4038
+ await runOnStep(config.onStep, step, runtimeErrors);
4039
+ return { ended: await settle(run, {
4040
+ pass: false,
4041
+ completed: false,
4042
+ reason: outcome.error ?? "action failed",
4043
+ score: averageScore(run.evals),
4044
+ failureClass: "unknown",
4045
+ stoppedBy: "runtime-error"
4046
+ }) };
4047
+ }
4048
+ }
4049
+ return { next: {
4050
+ decision,
4051
+ beforeState,
4052
+ evalsBefore,
4053
+ scoreBefore,
4054
+ actionStarted,
4055
+ stepHandle,
4056
+ outcome
4057
+ } };
4058
+ }
4059
+ function buildStep(run, attempt, stepIndex, afterState) {
4060
+ return {
4061
+ index: stepIndex,
4062
+ decision: attempt.decision,
4063
+ beforeState: attempt.beforeState,
4064
+ afterState,
4065
+ evalsBefore: attempt.evalsBefore,
4066
+ evalsAfter: run.evals,
4067
+ actionOutcome: attempt.outcome,
4068
+ startedAt: new Date(attempt.actionStarted).toISOString(),
4069
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
4070
+ };
4071
+ }
4072
+ /** Observe and validate after the action. A failure records the step and ends the run. */
4073
+ async function observeAfterAction(run, attempt, stepIndex) {
4074
+ const { config, emitter, runtimeErrors } = run;
4075
+ let observationPhase = "observe";
4076
+ try {
4077
+ run.state = await config.observe({
4078
+ history: run.history,
4079
+ abortSignal: run.signal
4080
+ });
4081
+ observationPhase = "validate";
4082
+ run.evals = await config.validate({
4083
+ intent: config.intent,
4084
+ state: run.state,
4085
+ history: run.history,
4086
+ abortSignal: run.signal
4087
+ });
4088
+ await recordEvalSpans(emitter, run.evals, `step-${stepIndex}`, runtimeErrors, stepIndex, attempt.stepHandle?.span.spanId);
4089
+ return;
4090
+ } catch (err) {
4091
+ runtimeErrors.push(runtimeError(observationPhase, stepIndex, err));
4092
+ const afterState = observationPhase === "observe" ? attempt.beforeState : run.state;
4093
+ const step = buildStep(run, attempt, stepIndex, afterState);
4094
+ run.history.push(step);
4095
+ await runTrace(emitter, runtimeErrors, stepIndex, () => attempt.stepHandle?.fail(lastErrorMessage(run)));
4096
+ await runOnStep(config.onStep, step, runtimeErrors);
4097
+ return settleOnRuntimeError(run, { value: afterState });
4098
+ }
4099
+ }
4100
+ /** Record the finished step, then apply the no-progress, budget and stop-policy checks that follow it. */
4101
+ async function recordStepAndCheck(run, attempt, stepIndex) {
4102
+ const { config, emitter, runtimeErrors } = run;
4103
+ const scoreAfter = averageScore(run.evals);
4104
+ const stateFingerprint = fingerprintState(run.state, config.stopPolicies);
4105
+ const noProgressSignal = run.progressDetector.observe({
4106
+ stateFingerprint,
4107
+ score: scoreAfter
4108
+ });
4109
+ const noProgressStreak = run.progressDetector.streak;
4110
+ const step = buildStep(run, attempt, stepIndex, run.state);
4111
+ run.history.push(step);
4112
+ const { outcome, stepHandle } = attempt;
4113
+ if (outcome.ok) await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.end({ attributes: {
4114
+ actionCostUsd: outcome.costUsd ?? null,
4115
+ spentCostUsd: run.spentCostUsd,
4116
+ scoreBefore: attempt.scoreBefore ?? null,
4117
+ scoreAfter: scoreAfter ?? null,
4118
+ noProgressStreak
4119
+ } }));
4120
+ else await runTrace(emitter, runtimeErrors, stepIndex, () => stepHandle?.fail(outcome.error ?? "action failed", { attributes: {
4121
+ spentCostUsd: run.spentCostUsd,
4122
+ noProgressStreak
4123
+ } }));
4124
+ await runOnStep(config.onStep, step, runtimeErrors);
4125
+ if (noProgressSignal) return settle(run, {
4126
+ pass: false,
4127
+ completed: true,
4128
+ reason: noProgressSignal.reason,
4129
+ score: scoreAfter,
4130
+ failureClass: "tool_recovery_failure",
4131
+ stoppedBy: "stop-policy"
4132
+ });
4133
+ const postStepBudgetStop = budgetStopDecision(run.budget, run.spentCostUsd);
4134
+ if (postStepBudgetStop.stop) return settle(run, {
4135
+ pass: false,
4136
+ completed: false,
4137
+ reason: postStepBudgetStop.reason,
4138
+ score: scoreAfter,
4139
+ failureClass: "budget_exceeded",
4140
+ stoppedBy: "budget"
4141
+ });
4142
+ return checkStopPolicy(run, makeRunContext(run, stepIndex + 1), stepIndex + 1);
4143
+ }
4224
4144
  function makeContext(intent, state, evals, history, budget, stepIndex, started, spentCostUsd, abortSignal, emitter) {
4225
4145
  return {
4226
4146
  intent,
@@ -6695,6 +6615,152 @@ function wranglerDeployRunner(input) {
6695
6615
  } };
6696
6616
  }
6697
6617
  //#endregion
6618
+ //#region src/held-out-gate-inputs.ts
6619
+ /** Drop gamed runs, pair both splits and measure coverage, scores, gaps and cost. */
6620
+ function collectGateInputs(candidate, baseline, baselineKey) {
6621
+ const candidateId = inferCandidateId(candidate, baselineKey);
6622
+ const baselineId = baselineKey;
6623
+ assertScenarioIdentities([...candidate, ...baseline]);
6624
+ const realnessGatedRuns = [...candidate, ...baseline].filter(isRealnessGated).length;
6625
+ const honestCandidate = candidate.filter((run) => !isRealnessGated(run));
6626
+ const honestBaseline = baseline.filter((run) => !isRealnessGated(run));
6627
+ const candidateSearch = scoredRuns(honestCandidate, "search");
6628
+ const baselineSearch = scoredRuns(honestBaseline, "search");
6629
+ const candidateHoldout = scoredRuns(honestCandidate, "holdout");
6630
+ const baselineHoldout = scoredRuns(honestBaseline, "holdout");
6631
+ const searchPairing = pairRunRecords(baselineSearch, candidateSearch);
6632
+ const holdoutPairing = pairRunRecords(baselineHoldout, candidateHoldout);
6633
+ const searchCoverage = splitCoverage(honestBaseline, honestCandidate, "search");
6634
+ const holdoutCoverage = splitCoverage(honestBaseline, honestCandidate, "holdout");
6635
+ const splitScoreOf = (run, split) => observedSplitScore(run, split);
6636
+ const beforeSearch = searchPairing.pairs.map((pair) => splitScoreOf(pair.baseline, "search"));
6637
+ const afterSearch = searchPairing.pairs.map((pair) => splitScoreOf(pair.treatment, "search"));
6638
+ const beforeHoldout = holdoutPairing.pairs.map((pair) => splitScoreOf(pair.baseline, "holdout"));
6639
+ const afterHoldout = holdoutPairing.pairs.map((pair) => splitScoreOf(pair.treatment, "holdout"));
6640
+ const productiveRuns = beforeHoldout.length;
6641
+ const candidateSearchMean = meanOrNull(afterSearch);
6642
+ const candidateHoldoutMean = meanOrNull(afterHoldout);
6643
+ const baselineSearchMean = meanOrNull(beforeSearch);
6644
+ const baselineHoldoutMean = meanOrNull(beforeHoldout);
6645
+ const overfitGap = diffOrNull(candidateSearchMean, candidateHoldoutMean);
6646
+ const baselineOverfitGap = diffOrNull(baselineSearchMean, baselineHoldoutMean);
6647
+ const medianCandidateCost = completeCostMedian([...searchPairing.pairs.map((pair) => pair.treatment), ...holdoutPairing.pairs.map((pair) => pair.treatment)]);
6648
+ const medianBaselineCost = completeCostMedian([...searchPairing.pairs.map((pair) => pair.baseline), ...holdoutPairing.pairs.map((pair) => pair.baseline)]);
6649
+ return {
6650
+ candidateId,
6651
+ baselineId,
6652
+ beforeHoldout,
6653
+ afterHoldout,
6654
+ productiveRuns,
6655
+ overfitGap,
6656
+ baselineOverfitGap,
6657
+ medianCandidateCost,
6658
+ medianBaselineCost,
6659
+ searchCoverage,
6660
+ holdoutCoverage,
6661
+ missingSplitScores: [
6662
+ candidateSearch.length === 0 ? "candidate search" : null,
6663
+ candidateHoldout.length === 0 ? "candidate holdout" : null,
6664
+ baselineSearch.length === 0 ? "baseline search" : null,
6665
+ baselineHoldout.length === 0 ? "baseline holdout" : null
6666
+ ].filter((label) => label !== null),
6667
+ commonEvidence: {
6668
+ productiveRuns,
6669
+ unpairedCandidateRuns: holdoutPairing.unpairedTreatment.length,
6670
+ unpairedBaselineRuns: holdoutPairing.unpairedBaseline.length,
6671
+ searchScore: candidateSearchMean,
6672
+ holdoutScore: candidateHoldoutMean,
6673
+ overfitGap,
6674
+ baselineOverfitGap,
6675
+ medianCandidateCost,
6676
+ medianBaselineCost,
6677
+ realnessGatedRuns,
6678
+ searchCoverage,
6679
+ holdoutCoverage
6680
+ }
6681
+ };
6682
+ }
6683
+ function inferCandidateId(candidate, baselineKey) {
6684
+ for (const run of candidate) if (run.candidateId && run.candidateId !== baselineKey) return run.candidateId;
6685
+ return candidate[0]?.candidateId ?? "(unknown candidate)";
6686
+ }
6687
+ function assertScenarioIdentities(runs) {
6688
+ for (const run of runs) if (typeof run.scenarioId !== "string" || run.scenarioId.trim() === "") throw new Error(`HeldOutGate: run ${run.runId} is missing scenarioId`);
6689
+ }
6690
+ /**
6691
+ * Rows carrying a finite score on one split. RAW (`observedSplitScore`) by
6692
+ * choice: the gated runs are already removed by the caller, so applying the
6693
+ * gate again here would only zero runs that are no longer in the set, and the
6694
+ * reported means must describe the runs that actually decided the promotion.
6695
+ */
6696
+ function scoredRuns(runs, split) {
6697
+ return runs.filter((run) => {
6698
+ if (run.splitTag !== split) return false;
6699
+ const v = observedSplitScore(run, split);
6700
+ return typeof v === "number" && Number.isFinite(v);
6701
+ });
6702
+ }
6703
+ /** Every row tagged to one split, scored or not — the work the split was
6704
+ * DEALT, as opposed to the work it managed to measure. */
6705
+ function dealtRuns(runs, split) {
6706
+ return runs.filter((run) => run.splitTag === split);
6707
+ }
6708
+ function hasFiniteScore(run, split) {
6709
+ const v = observedSplitScore(run, split);
6710
+ return typeof v === "number" && Number.isFinite(v);
6711
+ }
6712
+ /**
6713
+ * Partition one split's dealt work into answered / unscored / one-armed.
6714
+ *
6715
+ * Pairs the UNFILTERED rows with the same `pairRunRecords` the decision uses,
6716
+ * so the denominator comes out of the same identity rules as the numerator and
6717
+ * cannot drift from it. Deliberately a SECOND pairing rather than a widening of
6718
+ * the decision's own: the decision keeps pairing exactly the rows it always
6719
+ * paired, so this check can only ever add a refusal, never move a verdict that
6720
+ * was already being made on a complete set.
6721
+ */
6722
+ function splitCoverage(baseline, candidate, split) {
6723
+ const pairing = pairRunRecords(dealtRuns(baseline, split), dealtRuns(candidate, split));
6724
+ let answered = 0;
6725
+ for (const pair of pairing.pairs) if (hasFiniteScore(pair.baseline, split) && hasFiniteScore(pair.treatment, split)) answered += 1;
6726
+ const unscoredPairs = pairing.pairs.length - answered;
6727
+ const candidateOnly = pairing.unpairedTreatment.length;
6728
+ const baselineOnly = pairing.unpairedBaseline.length;
6729
+ const dealt = pairing.pairs.length + candidateOnly + baselineOnly;
6730
+ return {
6731
+ dealt,
6732
+ answered,
6733
+ unscoredPairs,
6734
+ candidateOnly,
6735
+ baselineOnly,
6736
+ coverage: dealt === 0 ? 0 : answered / dealt
6737
+ };
6738
+ }
6739
+ function meanOrNull(xs) {
6740
+ if (xs.length === 0) return null;
6741
+ return xs.reduce((s, x) => s + x, 0) / xs.length;
6742
+ }
6743
+ function diffOrNull(a, b) {
6744
+ if (a === null || b === null) return null;
6745
+ return a - b;
6746
+ }
6747
+ function medianFinite(xs) {
6748
+ const ys = xs.filter((x) => Number.isFinite(x)).sort((x, y) => x - y);
6749
+ if (ys.length === 0) return null;
6750
+ const mid = Math.floor(ys.length / 2);
6751
+ return ys.length % 2 === 0 ? (ys[mid - 1] + ys[mid]) / 2 : ys[mid];
6752
+ }
6753
+ function completeCostMedian(runs) {
6754
+ if (runs.length === 0) return null;
6755
+ const costs = [];
6756
+ for (const run of runs) {
6757
+ const usd = run.costProvenance.usd;
6758
+ if (usd === null) return null;
6759
+ costs.push(usd);
6760
+ }
6761
+ return medianFinite(costs);
6762
+ }
6763
+ //#endregion
6698
6764
  //#region src/held-out-gate.ts
6699
6765
  /**
6700
6766
  * HeldOutGate — first-class held-out paired-delta promotion gate.
@@ -6854,55 +6920,35 @@ var HeldOutGate = class {
6854
6920
  * Pairing is by `(experimentId, scenarioId, seed)`.
6855
6921
  * Missing or duplicate identities throw instead of comparing by position. */
6856
6922
  evaluate(candidate, baseline) {
6857
- const candidateId = inferCandidateId(candidate, this.baselineKey);
6858
- const baselineId = this.baselineKey;
6859
- assertScenarioIdentities([...candidate, ...baseline]);
6860
- const realnessGatedRuns = [...candidate, ...baseline].filter(isRealnessGated).length;
6861
- const honestCandidate = candidate.filter((run) => !isRealnessGated(run));
6862
- const honestBaseline = baseline.filter((run) => !isRealnessGated(run));
6863
- const candidateSearch = scoredRuns(honestCandidate, "search");
6864
- const baselineSearch = scoredRuns(honestBaseline, "search");
6865
- const candidateHoldout = scoredRuns(honestCandidate, "holdout");
6866
- const baselineHoldout = scoredRuns(honestBaseline, "holdout");
6867
- const searchPairing = pairRunRecords(baselineSearch, candidateSearch);
6868
- const holdoutPairing = pairRunRecords(baselineHoldout, candidateHoldout);
6869
- const searchCoverage = splitCoverage(honestBaseline, honestCandidate, "search");
6870
- const holdoutCoverage = splitCoverage(honestBaseline, honestCandidate, "holdout");
6871
- const splitScoreOf = (run, split) => observedSplitScore(run, split);
6872
- const beforeSearch = searchPairing.pairs.map((pair) => splitScoreOf(pair.baseline, "search"));
6873
- const afterSearch = searchPairing.pairs.map((pair) => splitScoreOf(pair.treatment, "search"));
6874
- const beforeHoldout = holdoutPairing.pairs.map((pair) => splitScoreOf(pair.baseline, "holdout"));
6875
- const afterHoldout = holdoutPairing.pairs.map((pair) => splitScoreOf(pair.treatment, "holdout"));
6876
- const productiveRuns = beforeHoldout.length;
6877
- const candidateSearchMean = meanOrNull(afterSearch);
6878
- const candidateHoldoutMean = meanOrNull(afterHoldout);
6879
- const baselineSearchMean = meanOrNull(beforeSearch);
6880
- const baselineHoldoutMean = meanOrNull(beforeHoldout);
6881
- const overfitGap = diffOrNull(candidateSearchMean, candidateHoldoutMean);
6882
- const baselineOverfitGap = diffOrNull(baselineSearchMean, baselineHoldoutMean);
6883
- const medianCandidateCost = completeCostMedian([...searchPairing.pairs.map((pair) => pair.treatment), ...holdoutPairing.pairs.map((pair) => pair.treatment)]);
6884
- const medianBaselineCost = completeCostMedian([...searchPairing.pairs.map((pair) => pair.baseline), ...holdoutPairing.pairs.map((pair) => pair.baseline)]);
6885
- const commonEvidence = {
6886
- productiveRuns,
6887
- unpairedCandidateRuns: holdoutPairing.unpairedTreatment.length,
6888
- unpairedBaselineRuns: holdoutPairing.unpairedBaseline.length,
6889
- searchScore: candidateSearchMean,
6890
- holdoutScore: candidateHoldoutMean,
6891
- overfitGap,
6892
- baselineOverfitGap,
6893
- medianCandidateCost,
6894
- medianBaselineCost,
6895
- realnessGatedRuns,
6896
- searchCoverage,
6897
- holdoutCoverage
6923
+ const inputs = collectGateInputs(candidate, baseline, this.baselineKey);
6924
+ const missingEvidence = this.refuseOnMissingEvidence(inputs);
6925
+ if (missingEvidence) return missingEvidence;
6926
+ const { overfitGap, baselineOverfitGap } = inputs;
6927
+ if (overfitGap === null || baselineOverfitGap === null) throw new Error("HeldOutGate: complete split scores did not produce overfit gaps");
6928
+ const { decision, evidence } = this.decidePairedDelta(inputs);
6929
+ const refusal = this.refuseOnDelta(inputs, decision, evidence) ?? this.refuseOnOverfitOrCost(inputs, evidence, overfitGap, baselineOverfitGap);
6930
+ if (refusal) return refusal;
6931
+ const { candidateId, baselineId, productiveRuns, medianCandidateCost, medianBaselineCost } = inputs;
6932
+ return {
6933
+ promote: true,
6934
+ candidateId,
6935
+ baselineId,
6936
+ evidence,
6937
+ reason: `promote: paired holdout ${decision.label} Δ=${fmt(decision.delta)} CI=[${fmt(decision.low)}, ${fmt(decision.high)}] over ${productiveRuns} pairs; overfit gap candidate=${fmt(overfitGap)} vs baseline=${fmt(baselineOverfitGap)}; median cost candidate=$${fmt(medianCandidateCost)} vs baseline=$${fmt(medianBaselineCost)}`,
6938
+ rejectionCode: null
6898
6939
  };
6940
+ }
6941
+ /** Refusals decided before any interval is computed: absent split scores,
6942
+ * incomplete coverage, too few paired runs. */
6943
+ refuseOnMissingEvidence(inputs) {
6944
+ const { productiveRuns, searchCoverage, holdoutCoverage } = inputs;
6899
6945
  /** Evidence for the early exits, where no CI was computed at all. The
6900
6946
  * statistic that WOULD have decided is still reported so a caller can see
6901
6947
  * which branch their data lands in without producing a verdict. */
6902
- const earlyShape = this.chooseShape(beforeHoldout, afterHoldout);
6948
+ const earlyShape = this.chooseShape(inputs.beforeHoldout, inputs.afterHoldout);
6903
6949
  const noCiEvidence = {
6904
- ...commonEvidence,
6905
- medianPairedDelta: productiveRuns > 0 ? medianDelta(beforeHoldout, afterHoldout) : null,
6950
+ ...inputs.commonEvidence,
6951
+ medianPairedDelta: productiveRuns > 0 ? medianDelta(inputs.beforeHoldout, inputs.afterHoldout) : null,
6906
6952
  deltaStatistic: earlyShape.statistic,
6907
6953
  decidingDelta: null,
6908
6954
  pairedCI: null,
@@ -6911,38 +6957,19 @@ var HeldOutGate = class {
6911
6957
  binaryScale: earlyShape.binaryScale,
6912
6958
  tieFraction: earlyShape.tieFraction
6913
6959
  };
6914
- const missingSplitScores = [
6915
- candidateSearch.length === 0 ? "candidate search" : null,
6916
- candidateHoldout.length === 0 ? "candidate holdout" : null,
6917
- baselineSearch.length === 0 ? "baseline search" : null,
6918
- baselineHoldout.length === 0 ? "baseline holdout" : null
6919
- ].filter((label) => label !== null);
6920
- if (missingSplitScores.length > 0) return {
6921
- promote: false,
6922
- candidateId,
6923
- baselineId,
6924
- evidence: noCiEvidence,
6925
- reason: `missing_split_scores: ${missingSplitScores.join(", ")} score evidence is absent`,
6926
- rejectionCode: "missing_split_scores"
6927
- };
6960
+ if (inputs.missingSplitScores.length > 0) return rejectGate(inputs, noCiEvidence, "missing_split_scores", `missing_split_scores: ${inputs.missingSplitScores.join(", ")} score evidence is absent`);
6928
6961
  const shortfalls = [["holdout", holdoutCoverage], ["search", searchCoverage]].filter(([, cov]) => cov.coverage < this.minCoverage);
6929
- if (shortfalls.length > 0) return {
6930
- promote: false,
6931
- candidateId,
6932
- baselineId,
6933
- evidence: noCiEvidence,
6934
- reason: `incomplete_coverage: ${shortfalls.map(([split, cov]) => describeCoverage(split, cov)).join("; ")} — required ${fmt(this.minCoverage)} of every dealt item scored on both arms. A missing score is missing data, not an absent item: the gate will not decide on the subset that answered`,
6935
- rejectionCode: "incomplete_coverage"
6936
- };
6937
- if (productiveRuns < this.minProductiveRuns) return {
6938
- promote: false,
6939
- candidateId,
6940
- baselineId,
6941
- evidence: noCiEvidence,
6942
- reason: `few_runs: ${productiveRuns} paired holdout observation(s) < min ${this.minProductiveRuns}`,
6943
- rejectionCode: "few_runs"
6944
- };
6945
- if (overfitGap === null || baselineOverfitGap === null) throw new Error("HeldOutGate: complete split scores did not produce overfit gaps");
6962
+ if (shortfalls.length > 0) return rejectGate(inputs, noCiEvidence, "incomplete_coverage", `incomplete_coverage: ${shortfalls.map(([split, cov]) => describeCoverage(split, cov)).join("; ")} — required ${fmt(this.minCoverage)} of every dealt item scored on both arms. A missing score is missing data, not an absent item: the gate will not decide on the subset that answered`);
6963
+ if (productiveRuns < this.minProductiveRuns) return rejectGate(inputs, noCiEvidence, "few_runs", `few_runs: ${productiveRuns} paired holdout observation(s) < min ${this.minProductiveRuns}`);
6964
+ return null;
6965
+ }
6966
+ /** The paired holdout delta, decided by the one shared rule (see
6967
+ * `paired-promotion-decision.ts`): the estimator comes from the outcome's
6968
+ * shape, McNemar vetoes at a non-negative threshold, and a zero-width
6969
+ * interval is refused. `medianPairedDelta` stays the literal median on
6970
+ * EVERY path so the number that made the old gate blind remains visible. */
6971
+ decidePairedDelta(inputs) {
6972
+ const { beforeHoldout, afterHoldout } = inputs;
6946
6973
  const decision = decidePairedPromotion(beforeHoldout, afterHoldout, {
6947
6974
  threshold: this.pairedDeltaThreshold,
6948
6975
  confidence: this.confidence,
@@ -6953,81 +6980,46 @@ var HeldOutGate = class {
6953
6980
  });
6954
6981
  const literalMedian = medianDelta(beforeHoldout, afterHoldout);
6955
6982
  const wilcoxon = wilcoxonSignedRank(beforeHoldout, afterHoldout);
6956
- const { delta: decidingDelta, low, high, label: deltaLabel } = decision;
6957
- const evidence = {
6958
- ...commonEvidence,
6959
- medianPairedDelta: literalMedian,
6960
- deltaStatistic: decision.statistic,
6961
- decidingDelta,
6962
- pairedCI: {
6963
- low,
6964
- high
6965
- },
6966
- pairedPValue: wilcoxon.p,
6967
- mcnemar: decision.mcnemar,
6968
- binaryScale: decision.binaryScale,
6969
- tieFraction: decision.tieFraction
6970
- };
6971
- if (decision.indeterminate) return {
6972
- promote: false,
6973
- candidateId,
6974
- baselineId,
6975
- evidence,
6976
- reason: `indeterminate_delta: ${decision.indeterminateCause}, so the paired holdout CI is [${fmt(low)}, ${fmt(high)}] and carries no direction — it cannot clear threshold ${fmt(this.pairedDeltaThreshold)} on evidence`,
6977
- rejectionCode: "indeterminate_delta"
6978
- };
6979
- if (!decision.sufficient) return {
6980
- promote: false,
6981
- candidateId,
6982
- baselineId,
6983
- evidence,
6984
- reason: `few_runs: ${productiveRuns} paired holdout observations < ${decision.minimumPairs} required for ${deltaLabel} estimation`,
6985
- rejectionCode: "few_runs"
6983
+ const { delta: decidingDelta, low, high } = decision;
6984
+ return {
6985
+ decision,
6986
+ evidence: {
6987
+ ...inputs.commonEvidence,
6988
+ medianPairedDelta: literalMedian,
6989
+ deltaStatistic: decision.statistic,
6990
+ decidingDelta,
6991
+ pairedCI: {
6992
+ low,
6993
+ high
6994
+ },
6995
+ pairedPValue: wilcoxon.p,
6996
+ mcnemar: decision.mcnemar,
6997
+ binaryScale: decision.binaryScale,
6998
+ tieFraction: decision.tieFraction
6999
+ }
6986
7000
  };
7001
+ }
7002
+ /** Refusals read off the paired delta: a zero-width interval, too few pairs
7003
+ * for the chosen estimator, or an interval that does not clear the threshold. */
7004
+ refuseOnDelta(inputs, decision, evidence) {
7005
+ const { delta: decidingDelta, low, high, label: deltaLabel } = decision;
7006
+ if (decision.indeterminate) return rejectGate(inputs, evidence, "indeterminate_delta", `indeterminate_delta: ${decision.indeterminateCause}, so the paired holdout CI is [${fmt(low)}, ${fmt(high)}] and carries no direction — it cannot clear threshold ${fmt(this.pairedDeltaThreshold)} on evidence`);
7007
+ if (!decision.sufficient) return rejectGate(inputs, evidence, "few_runs", `few_runs: ${inputs.productiveRuns} paired holdout observations < ${decision.minimumPairs} required for ${deltaLabel} estimation`);
6987
7008
  const exactTestVetoes = decision.exactTestVetoes;
6988
7009
  if (!decision.clearsThreshold || exactTestVetoes) {
6989
7010
  const detail = exactTestVetoes ? ` McNemar exact p=${decision.mcnemar?.pValue.toExponential(2)} does not reject at α=${fmt(1 - this.confidence)}.` : decision.methodDetail;
6990
- return {
6991
- promote: false,
6992
- candidateId,
6993
- baselineId,
6994
- evidence,
6995
- reason: `negative_delta: paired holdout ${deltaLabel} Δ=${fmt(decidingDelta)} CI=[${fmt(low)}, ${fmt(high)}] does not clear threshold ${fmt(this.pairedDeltaThreshold)}.${detail}`,
6996
- rejectionCode: "negative_delta"
6997
- };
7011
+ return rejectGate(inputs, evidence, "negative_delta", `negative_delta: paired holdout ${deltaLabel} Δ=${fmt(decidingDelta)} CI=[${fmt(low)}, ${fmt(high)}] does not clear threshold ${fmt(this.pairedDeltaThreshold)}.${detail}`);
6998
7012
  }
6999
- if (overfitGap > baselineOverfitGap + this.overfitGapThreshold) return {
7000
- promote: false,
7001
- candidateId,
7002
- baselineId,
7003
- evidence,
7004
- reason: `overfit_gap: candidate gap=${fmt(overfitGap)} exceeds baseline gap=${fmt(baselineOverfitGap)} by more than ${fmt(this.overfitGapThreshold)}`,
7005
- rejectionCode: "overfit_gap"
7006
- };
7007
- if (this.costPerTaskCeiling !== void 0 && medianCandidateCost === null) return {
7008
- promote: false,
7009
- candidateId,
7010
- baselineId,
7011
- evidence,
7012
- reason: "missing_cost: candidate cost evidence is incomplete",
7013
- rejectionCode: "missing_cost"
7014
- };
7015
- if (this.costPerTaskCeiling !== void 0 && medianCandidateCost !== null && medianCandidateCost > this.costPerTaskCeiling) return {
7016
- promote: false,
7017
- candidateId,
7018
- baselineId,
7019
- evidence,
7020
- reason: `cost_ceiling: candidate median cost $${fmt(medianCandidateCost)} exceeds ceiling $${fmt(this.costPerTaskCeiling)} (baseline $${fmt(medianBaselineCost)})`,
7021
- rejectionCode: "cost_ceiling"
7022
- };
7023
- return {
7024
- promote: true,
7025
- candidateId,
7026
- baselineId,
7027
- evidence,
7028
- reason: `promote: paired holdout ${deltaLabel} Δ=${fmt(decidingDelta)} CI=[${fmt(low)}, ${fmt(high)}] over ${productiveRuns} pairs; overfit gap candidate=${fmt(overfitGap)} vs baseline=${fmt(baselineOverfitGap)}; median cost candidate=$${fmt(medianCandidateCost)} vs baseline=$${fmt(medianBaselineCost)}`,
7029
- rejectionCode: null
7030
- };
7013
+ return null;
7014
+ }
7015
+ /** The overfit-gap and cost-ceiling refusals, run after the quality gates so
7016
+ * a cost-driven rejection never masks a quality failure. */
7017
+ refuseOnOverfitOrCost(inputs, evidence, overfitGap, baselineOverfitGap) {
7018
+ const { medianCandidateCost, medianBaselineCost } = inputs;
7019
+ if (overfitGap > baselineOverfitGap + this.overfitGapThreshold) return rejectGate(inputs, evidence, "overfit_gap", `overfit_gap: candidate gap=${fmt(overfitGap)} exceeds baseline gap=${fmt(baselineOverfitGap)} by more than ${fmt(this.overfitGapThreshold)}`);
7020
+ if (this.costPerTaskCeiling !== void 0 && medianCandidateCost === null) return rejectGate(inputs, evidence, "missing_cost", "missing_cost: candidate cost evidence is incomplete");
7021
+ if (this.costPerTaskCeiling !== void 0 && medianCandidateCost !== null && medianCandidateCost > this.costPerTaskCeiling) return rejectGate(inputs, evidence, "cost_ceiling", `cost_ceiling: candidate median cost $${fmt(medianCandidateCost)} exceeds ceiling $${fmt(this.costPerTaskCeiling)} (baseline $${fmt(medianBaselineCost)})`);
7022
+ return null;
7031
7023
  }
7032
7024
  /**
7033
7025
  * Which paired statistic decides this data, and the shape facts behind it —
@@ -7039,60 +7031,15 @@ var HeldOutGate = class {
7039
7031
  return pairedDecisionShape(before, after, this.deltaStatistic === "median" ? "median" : "mean");
7040
7032
  }
7041
7033
  };
7042
- function inferCandidateId(candidate, baselineKey) {
7043
- for (const run of candidate) if (run.candidateId && run.candidateId !== baselineKey) return run.candidateId;
7044
- return candidate[0]?.candidateId ?? "(unknown candidate)";
7045
- }
7046
- function assertScenarioIdentities(runs) {
7047
- for (const run of runs) if (typeof run.scenarioId !== "string" || run.scenarioId.trim() === "") throw new Error(`HeldOutGate: run ${run.runId} is missing scenarioId`);
7048
- }
7049
- /**
7050
- * Rows carrying a finite score on one split. RAW (`observedSplitScore`) by
7051
- * choice: the gated runs are already removed by the caller, so applying the
7052
- * gate again here would only zero runs that are no longer in the set, and the
7053
- * reported means must describe the runs that actually decided the promotion.
7054
- */
7055
- function scoredRuns(runs, split) {
7056
- return runs.filter((run) => {
7057
- if (run.splitTag !== split) return false;
7058
- const v = observedSplitScore(run, split);
7059
- return typeof v === "number" && Number.isFinite(v);
7060
- });
7061
- }
7062
- /** Every row tagged to one split, scored or not — the work the split was
7063
- * DEALT, as opposed to the work it managed to measure. */
7064
- function dealtRuns(runs, split) {
7065
- return runs.filter((run) => run.splitTag === split);
7066
- }
7067
- function hasFiniteScore(run, split) {
7068
- const v = observedSplitScore(run, split);
7069
- return typeof v === "number" && Number.isFinite(v);
7070
- }
7071
- /**
7072
- * Partition one split's dealt work into answered / unscored / one-armed.
7073
- *
7074
- * Pairs the UNFILTERED rows with the same `pairRunRecords` the decision uses,
7075
- * so the denominator comes out of the same identity rules as the numerator and
7076
- * cannot drift from it. Deliberately a SECOND pairing rather than a widening of
7077
- * the decision's own: the decision keeps pairing exactly the rows it always
7078
- * paired, so this check can only ever add a refusal, never move a verdict that
7079
- * was already being made on a complete set.
7080
- */
7081
- function splitCoverage(baseline, candidate, split) {
7082
- const pairing = pairRunRecords(dealtRuns(baseline, split), dealtRuns(candidate, split));
7083
- let answered = 0;
7084
- for (const pair of pairing.pairs) if (hasFiniteScore(pair.baseline, split) && hasFiniteScore(pair.treatment, split)) answered += 1;
7085
- const unscoredPairs = pairing.pairs.length - answered;
7086
- const candidateOnly = pairing.unpairedTreatment.length;
7087
- const baselineOnly = pairing.unpairedBaseline.length;
7088
- const dealt = pairing.pairs.length + candidateOnly + baselineOnly;
7034
+ /** A refusal verdict carrying the evidence gathered so far. */
7035
+ function rejectGate(inputs, evidence, rejectionCode, reason) {
7089
7036
  return {
7090
- dealt,
7091
- answered,
7092
- unscoredPairs,
7093
- candidateOnly,
7094
- baselineOnly,
7095
- coverage: dealt === 0 ? 0 : answered / dealt
7037
+ promote: false,
7038
+ candidateId: inputs.candidateId,
7039
+ baselineId: inputs.baselineId,
7040
+ evidence,
7041
+ reason,
7042
+ rejectionCode
7096
7043
  };
7097
7044
  }
7098
7045
  function describeCoverage(split, cov) {
@@ -7103,36 +7050,12 @@ function describeCoverage(split, cov) {
7103
7050
  ].filter((part) => part !== null);
7104
7051
  return `${split} scored ${cov.answered} of ${cov.dealt} dealt items (coverage ${fmt(cov.coverage)})` + (dark.length > 0 ? ` — ${dark.join(", ")}` : "");
7105
7052
  }
7106
- function meanOrNull(xs) {
7107
- if (xs.length === 0) return null;
7108
- return xs.reduce((s, x) => s + x, 0) / xs.length;
7109
- }
7110
- function diffOrNull(a, b) {
7111
- if (a === null || b === null) return null;
7112
- return a - b;
7113
- }
7114
7053
  function medianDelta(before, after) {
7115
7054
  const ds = before.map((b, i) => after[i] - b).sort((x, y) => x - y);
7116
7055
  if (ds.length === 0) throw new Error("HeldOutGate: median delta requires at least one pair");
7117
7056
  const mid = Math.floor(ds.length / 2);
7118
7057
  return ds.length % 2 === 0 ? (ds[mid - 1] + ds[mid]) / 2 : ds[mid];
7119
7058
  }
7120
- function medianFinite(xs) {
7121
- const ys = xs.filter((x) => Number.isFinite(x)).sort((x, y) => x - y);
7122
- if (ys.length === 0) return null;
7123
- const mid = Math.floor(ys.length / 2);
7124
- return ys.length % 2 === 0 ? (ys[mid - 1] + ys[mid]) / 2 : ys[mid];
7125
- }
7126
- function completeCostMedian(runs) {
7127
- if (runs.length === 0) return null;
7128
- const costs = [];
7129
- for (const run of runs) {
7130
- const usd = run.costProvenance.usd;
7131
- if (usd === null) return null;
7132
- costs.push(usd);
7133
- }
7134
- return medianFinite(costs);
7135
- }
7136
7059
  function fmt(x) {
7137
7060
  if (x === null) return "n/a";
7138
7061
  return x.toFixed(4);