@tangle-network/agent-eval 0.117.1 → 0.118.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 (143) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/analyst/index.d.ts +2772 -21
  3. package/dist/analyst/index.js +7 -6
  4. package/dist/analyst/index.js.map +1 -1
  5. package/dist/belief-state/index.d.ts +706 -10
  6. package/dist/belief-state/index.js +2 -1
  7. package/dist/belief-state/index.js.map +1 -1
  8. package/dist/benchmarks/index.d.ts +958 -14
  9. package/dist/benchmarks/index.js +12 -10
  10. package/dist/builder-eval/index.d.ts +449 -4
  11. package/dist/builder-eval/index.js +4 -3
  12. package/dist/builder-eval/index.js.map +1 -1
  13. package/dist/campaign/index.d.ts +4275 -73
  14. package/dist/campaign/index.js +12 -10
  15. package/dist/{chunk-VF3XSYTI.js → chunk-33JA4TFA.js} +6 -6
  16. package/dist/{chunk-4JLWXDYA.js → chunk-3EHHMC6E.js} +2 -2
  17. package/dist/{chunk-CCZIVI3F.js → chunk-BFW56GTT.js} +2 -2
  18. package/dist/{chunk-YZPO4UHR.js → chunk-DP3BBMYJ.js} +100 -143
  19. package/dist/chunk-DP3BBMYJ.js.map +1 -0
  20. package/dist/{chunk-E4BUPP7Z.js → chunk-HKUCJ437.js} +38 -66
  21. package/dist/chunk-HKUCJ437.js.map +1 -0
  22. package/dist/{chunk-HZHNRYHK.js → chunk-K6N6XJJX.js} +2 -2
  23. package/dist/chunk-KSDQVPLR.js +286 -0
  24. package/dist/chunk-KSDQVPLR.js.map +1 -0
  25. package/dist/chunk-MA6HLL3S.js +65 -0
  26. package/dist/chunk-MA6HLL3S.js.map +1 -0
  27. package/dist/{chunk-MGEHEHSN.js → chunk-OIIMMLRB.js} +11 -11
  28. package/dist/{chunk-DXZRATT5.js → chunk-OYZAPX5G.js} +3 -3
  29. package/dist/chunk-PXE2VKMX.js +140 -0
  30. package/dist/chunk-PXE2VKMX.js.map +1 -0
  31. package/dist/{chunk-JSJZ4PJ6.js → chunk-Q442S5AS.js} +17 -17
  32. package/dist/{chunk-ODVOOEWQ.js → chunk-QBRSJK47.js} +2 -2
  33. package/dist/{chunk-S2F4J57L.js → chunk-QKEGNI5B.js} +77 -32
  34. package/dist/chunk-QKEGNI5B.js.map +1 -0
  35. package/dist/{chunk-5UF54T55.js → chunk-S3UZOQ5Y.js} +34 -7
  36. package/dist/chunk-S3UZOQ5Y.js.map +1 -0
  37. package/dist/{chunk-FQNLDL4D.js → chunk-SVH2ANFD.js} +136 -4
  38. package/dist/chunk-SVH2ANFD.js.map +1 -0
  39. package/dist/{chunk-GQCZRZ7L.js → chunk-U5CHZ5M3.js} +9 -9
  40. package/dist/{chunk-TVVP3ZZQ.js → chunk-VQMK5FMP.js} +2 -1
  41. package/dist/{chunk-TVVP3ZZQ.js.map → chunk-VQMK5FMP.js.map} +1 -1
  42. package/dist/chunk-WDHBCA3M.js +31 -0
  43. package/dist/chunk-WDHBCA3M.js.map +1 -0
  44. package/dist/{chunk-HQPHZGL6.js → chunk-YLMUS4MM.js} +9 -9
  45. package/dist/{chunk-LQUTGLOZ.js → chunk-ZET2UAYW.js} +15 -65
  46. package/dist/chunk-ZET2UAYW.js.map +1 -0
  47. package/dist/contract/index.d.ts +4012 -38
  48. package/dist/contract/index.js +56 -29
  49. package/dist/contract/index.js.map +1 -1
  50. package/dist/control.d.ts +1013 -9
  51. package/dist/control.js +4 -3
  52. package/dist/fuzz.d.ts +194 -4
  53. package/dist/fuzz.js +3 -3
  54. package/dist/hosted/index.d.ts +498 -17
  55. package/dist/index.d.ts +10982 -1286
  56. package/dist/index.js +97 -80
  57. package/dist/index.js.map +1 -1
  58. package/dist/matrix/index.d.ts +139 -4
  59. package/dist/meta-eval/index.d.ts +862 -15
  60. package/dist/meta-eval/index.js +2 -1
  61. package/dist/meta-eval/index.js.map +1 -1
  62. package/dist/multishot/index.d.ts +214 -14
  63. package/dist/openapi.json +1 -1
  64. package/dist/pipelines/index.d.ts +392 -7
  65. package/dist/pipelines/index.js +5 -3
  66. package/dist/pipelines/index.js.map +1 -1
  67. package/dist/reporting.d.ts +1277 -17
  68. package/dist/rl.d.ts +2359 -28
  69. package/dist/rl.js +6 -5
  70. package/dist/rl.js.map +1 -1
  71. package/dist/storyboard/index.d.ts +86 -1
  72. package/dist/trace-attributes.d.ts +16 -0
  73. package/dist/trace-attributes.js +32 -0
  74. package/dist/trace-attributes.js.map +1 -0
  75. package/dist/traces.d.ts +1970 -697
  76. package/dist/traces.js +49 -32
  77. package/dist/wire/index.d.ts +655 -9
  78. package/docs/insight-report.md +44 -0
  79. package/package.json +9 -3
  80. package/dist/adversarial-B7loGVVX.d.ts +0 -19
  81. package/dist/analyst-C8HHvfJp.d.ts +0 -88
  82. package/dist/analyze-runs--2x39HZ7.d.ts +0 -81
  83. package/dist/baseline-DKq3gJpP.d.ts +0 -141
  84. package/dist/calibration-C8MTS7cw.d.ts +0 -101
  85. package/dist/chunk-5UF54T55.js.map +0 -1
  86. package/dist/chunk-E4BUPP7Z.js.map +0 -1
  87. package/dist/chunk-FQNLDL4D.js.map +0 -1
  88. package/dist/chunk-LQUTGLOZ.js.map +0 -1
  89. package/dist/chunk-S2F4J57L.js.map +0 -1
  90. package/dist/chunk-YZPO4UHR.js.map +0 -1
  91. package/dist/code-agent-session-CjZsVd19.d.ts +0 -87
  92. package/dist/control-6vuGfmDH.d.ts +0 -258
  93. package/dist/cost-ledger-DWy3XdJc.d.ts +0 -183
  94. package/dist/dataset-NENEzRgk.d.ts +0 -115
  95. package/dist/default-registry-DaK8b3fv.d.ts +0 -155
  96. package/dist/emitter-CjD7vUwv.d.ts +0 -122
  97. package/dist/errors-oeQrLqXC.d.ts +0 -74
  98. package/dist/failure-cluster-DOAcSJ87.d.ts +0 -76
  99. package/dist/feedback-trajectory-BUnM58xL.d.ts +0 -348
  100. package/dist/gepa-eESocoDi.d.ts +0 -642
  101. package/dist/index-PdX4VnPA.d.ts +0 -423
  102. package/dist/insight-report-DY4nDW9Q.d.ts +0 -310
  103. package/dist/integrity-DqlBiLyK.d.ts +0 -81
  104. package/dist/judge-calibration-7C-IDmKr.d.ts +0 -145
  105. package/dist/kind-factory-ClZmO25A.d.ts +0 -171
  106. package/dist/llm-client-qoDd18Qz.d.ts +0 -289
  107. package/dist/multi-layer-verifier-BsqKuLyN.d.ts +0 -150
  108. package/dist/off-policy-DiwuKKg7.d.ts +0 -132
  109. package/dist/outcome-store-rnXLEqSn.d.ts +0 -63
  110. package/dist/policy-edit-wG9uFEFm.d.ts +0 -455
  111. package/dist/pre-registration-BWQhJ3vz.d.ts +0 -761
  112. package/dist/provenance-DpjwyseI.d.ts +0 -541
  113. package/dist/query-CF7PG61p.d.ts +0 -35
  114. package/dist/raw-provider-sink-C46HDghv.d.ts +0 -132
  115. package/dist/release-report-C8G2i5Xi.d.ts +0 -236
  116. package/dist/researcher-C8XyxQsu.d.ts +0 -387
  117. package/dist/rubric-predictive-validity-p49lLVrE.d.ts +0 -105
  118. package/dist/run-record-BDH49H2E.d.ts +0 -360
  119. package/dist/runtime-trajectory-DGBIUt4B.d.ts +0 -49
  120. package/dist/schema-B3Q3l9Z_.d.ts +0 -201
  121. package/dist/semantic-concept-judge-CXnPEJbf.d.ts +0 -723
  122. package/dist/sequential-5iSVfzl2.d.ts +0 -139
  123. package/dist/series-convergence-D5OWMBg6.d.ts +0 -33
  124. package/dist/statistics-KUnG73jH.d.ts +0 -494
  125. package/dist/storage-DrX3v_5B.d.ts +0 -50
  126. package/dist/store-C1YxJDEK.d.ts +0 -248
  127. package/dist/store-DGqD0Pyo.d.ts +0 -116
  128. package/dist/summary-report-C5bKFfm-.d.ts +0 -445
  129. package/dist/test-graded-scenario-B0ybnPY7.d.ts +0 -166
  130. package/dist/types-BSw1rOUB.d.ts +0 -634
  131. package/dist/types-BUxNaJ8c.d.ts +0 -108
  132. package/dist/types-BkfcQnxV.d.ts +0 -313
  133. package/dist/verdict-C9MlYujm.d.ts +0 -35
  134. /package/dist/{chunk-VF3XSYTI.js.map → chunk-33JA4TFA.js.map} +0 -0
  135. /package/dist/{chunk-4JLWXDYA.js.map → chunk-3EHHMC6E.js.map} +0 -0
  136. /package/dist/{chunk-CCZIVI3F.js.map → chunk-BFW56GTT.js.map} +0 -0
  137. /package/dist/{chunk-HZHNRYHK.js.map → chunk-K6N6XJJX.js.map} +0 -0
  138. /package/dist/{chunk-MGEHEHSN.js.map → chunk-OIIMMLRB.js.map} +0 -0
  139. /package/dist/{chunk-DXZRATT5.js.map → chunk-OYZAPX5G.js.map} +0 -0
  140. /package/dist/{chunk-JSJZ4PJ6.js.map → chunk-Q442S5AS.js.map} +0 -0
  141. /package/dist/{chunk-ODVOOEWQ.js.map → chunk-QBRSJK47.js.map} +0 -0
  142. /package/dist/{chunk-GQCZRZ7L.js.map → chunk-U5CHZ5M3.js.map} +0 -0
  143. /package/dist/{chunk-HQPHZGL6.js.map → chunk-YLMUS4MM.js.map} +0 -0
package/dist/rl.d.ts CHANGED
@@ -1,30 +1,446 @@
1
- import { R as RunRecord, a as RunSplitTag } from './run-record-BDH49H2E.js';
2
- export { A as AdversarialMutation } from './adversarial-B7loGVVX.js';
3
- import { S as Span } from './schema-B3Q3l9Z_.js';
4
- import { T as TraceStore } from './store-DGqD0Pyo.js';
5
- export { O as OffPolicyEstimate, a as OffPolicyOptions, b as OffPolicyTrajectory, d as doublyRobust, i as inverseProbabilityWeighting, o as offPolicyEstimateAll, s as selfNormalizedImportanceWeighting } from './off-policy-DiwuKKg7.js';
6
- import { b as OutcomeStore } from './outcome-store-rnXLEqSn.js';
7
- export { D as DeploymentOutcome, F as FileSystemOutcomeStore, a as FileSystemOutcomeStoreOptions, I as InMemoryOutcomeStore } from './outcome-store-rnXLEqSn.js';
8
- import { b as RubricPredictiveValidityReport } from './rubric-predictive-validity-p49lLVrE.js';
9
- import { R as Researcher, F as FailureMode, S as SteeringChange, E as ExperimentPlan, a as ExperimentResult, b as EvalCampaignResult, c as EvalCampaignOptions } from './researcher-C8XyxQsu.js';
10
- export { r as runEvalCampaign } from './researcher-C8XyxQsu.js';
11
- import { a as VerificationReport } from './multi-layer-verifier-BsqKuLyN.js';
12
- import { I as InterimReleaseConfidence } from './sequential-5iSVfzl2.js';
13
- import { C as CampaignResult } from './types-BSw1rOUB.js';
14
- import '@tangle-network/agent-interface';
15
- import './errors-oeQrLqXC.js';
16
- import './llm-client-qoDd18Qz.js';
17
- import './cost-ledger-DWy3XdJc.js';
18
- import './raw-provider-sink-C46HDghv.js';
19
- import './summary-report-C5bKFfm-.js';
20
- import './failure-cluster-DOAcSJ87.js';
21
- import './emitter-CjD7vUwv.js';
22
- import './integrity-DqlBiLyK.js';
23
- import './verdict-C9MlYujm.js';
24
- import './policy-edit-wG9uFEFm.js';
25
- import './store-C1YxJDEK.js';
26
- import './types-BkfcQnxV.js';
27
- import '@tangle-network/tcloud';
1
+ type AgentProfileCellSchemaVersion = 'agent-profile-cell/v1';
2
+ type AgentProfileJson = string | number | boolean | null | AgentProfileJson[] | {
3
+ [key: string]: AgentProfileJson;
4
+ };
5
+ type AgentProfileDimensionValue = string | number | boolean | null;
6
+ interface AgentProfileSource {
7
+ /** Runtime/profile contract being fingerprinted, e.g. `agent-interface-profile`. */
8
+ kind: string;
9
+ /** sha256 over the canonical source profile object. */
10
+ hash: string;
11
+ }
12
+ interface AgentProfileSourceInput {
13
+ kind: string;
14
+ /** Precomputed sha256 for callers that already sign their profile artifact. */
15
+ hash?: string;
16
+ /** Full canonical runtime profile; hashed and then discarded from the cell. */
17
+ profile?: AgentProfileJson;
18
+ }
19
+ interface AgentProfileHarness {
20
+ id: string;
21
+ version?: string;
22
+ hash?: string;
23
+ }
24
+ interface AgentProfileCellInput {
25
+ profileId: string;
26
+ sourceProfile: AgentProfileSourceInput;
27
+ harness?: AgentProfileHarness;
28
+ model?: string;
29
+ promptHash?: string;
30
+ dimensions?: Record<string, AgentProfileDimensionValue>;
31
+ }
32
+ interface AgentProfileCell {
33
+ schemaVersion: AgentProfileCellSchemaVersion;
34
+ cellId: string;
35
+ profileId: string;
36
+ sourceProfile: AgentProfileSource;
37
+ harness?: AgentProfileHarness;
38
+ model?: string;
39
+ promptHash?: string;
40
+ dimensions?: Record<string, AgentProfileDimensionValue>;
41
+ }
42
+
43
+ type RunStatus = 'running' | 'completed' | 'failed' | 'aborted';
44
+ interface BudgetSpec {
45
+ tokens?: number;
46
+ wallMs?: number;
47
+ calls?: number;
48
+ usd?: number;
49
+ }
50
+ interface RunOutcome$1 {
51
+ score?: number;
52
+ pass?: boolean;
53
+ failureClass?: FailureClass;
54
+ notes?: string;
55
+ }
56
+ /**
57
+ * Layer — optional classification in a nested build workflow.
58
+ * `builder`: the meta-agent editing a project (e.g. agent-builder Forge chat).
59
+ * `app-build`: sandbox harness that compiled + tested the generated scaffold.
60
+ * `app-runtime`: a run of the generated agent against a domain scenario.
61
+ * `meta`: any meta-eval (judge replay, correlation analysis).
62
+ */
63
+ type RunLayer = 'builder' | 'app-build' | 'app-runtime' | 'meta' | 'custom';
64
+ interface Run {
65
+ runId: string;
66
+ /**
67
+ * Stable identifier of the scenario being executed.
68
+ *
69
+ * Always populated on the persisted Run — but `TraceEmitter.startRun` accepts
70
+ * input WITHOUT this field, substituting a sensible default
71
+ * (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) when the caller has no
72
+ * curated scenario to anchor to (runtime / operator / meta-eval runs). This
73
+ * keeps the persisted shape unambiguous for downstream filters + aggregations
74
+ * while removing the boilerplate of inventing placeholder ids at the call site.
75
+ */
76
+ scenarioId: string;
77
+ variantId?: string;
78
+ datasetVersion?: string;
79
+ /** Git SHA of agent code at run time. */
80
+ codeSha?: string;
81
+ /** Hash of the prompt template + any system prompt. */
82
+ promptSha?: string;
83
+ /** Model id + date + system-prompt hash, concatenated. */
84
+ modelFingerprint?: string;
85
+ seed?: number;
86
+ /** Arbitrary environment markers (shell, docker version, tz). */
87
+ envFingerprint?: Record<string, string>;
88
+ /** Version of the redaction rules applied to this run. */
89
+ redactionVersion?: string;
90
+ /** Parent run in a nested build workflow. A builder run's children are
91
+ * app-build runs; those children are app-runtime runs. */
92
+ parentRunId?: string;
93
+ /** Stable project identifier — groups runs across chats + sessions. */
94
+ projectId?: string;
95
+ /** Chat/conversation identifier within a project. */
96
+ chatId?: string;
97
+ /** Layer classification — hint for aggregation; not enforced. */
98
+ layer?: RunLayer;
99
+ startedAt: number;
100
+ endedAt?: number;
101
+ status: RunStatus;
102
+ outcome?: RunOutcome$1;
103
+ budget?: BudgetSpec;
104
+ /** Free-form labels for downstream grouping. */
105
+ tags?: Record<string, string>;
106
+ }
107
+ type SpanKind = 'agent' | 'llm' | 'tool' | 'retrieval' | 'judge' | 'sandbox' | 'custom';
108
+ type SpanStatus = 'ok' | 'error';
109
+ interface SpanBase {
110
+ spanId: string;
111
+ parentSpanId?: string;
112
+ runId: string;
113
+ kind: SpanKind;
114
+ name: string;
115
+ startedAt: number;
116
+ endedAt?: number;
117
+ status?: SpanStatus;
118
+ error?: string;
119
+ /** Anything not covered by typed fields. Kept deliberately free-form. */
120
+ attributes?: Record<string, unknown>;
121
+ }
122
+ interface Message {
123
+ role: 'system' | 'user' | 'assistant' | 'tool';
124
+ content: string;
125
+ tokens?: number;
126
+ /** Multi-modal content descriptors; blobs themselves live in Artifacts. */
127
+ images?: Array<{
128
+ artifactId?: string;
129
+ url?: string;
130
+ mime?: string;
131
+ }>;
132
+ }
133
+ interface LlmSpan extends SpanBase {
134
+ kind: 'llm';
135
+ model: string;
136
+ messages: Message[];
137
+ output?: string;
138
+ inputTokens?: number;
139
+ /** All generated tokens, including the reasoning subset when present. */
140
+ outputTokens?: number;
141
+ cachedTokens?: number;
142
+ cacheWriteTokens?: number;
143
+ /** Reasoning-token subset of `outputTokens`. */
144
+ reasoningTokens?: number;
145
+ costUsd?: number;
146
+ finishReason?: string;
147
+ }
148
+ interface ToolSpan extends SpanBase {
149
+ kind: 'tool';
150
+ toolName: string;
151
+ args: unknown;
152
+ /** False when the source observed the call but did not capture its arguments. */
153
+ argsCaptured?: boolean;
154
+ result?: unknown;
155
+ latencyMs?: number;
156
+ }
157
+ interface RetrievalSpan extends SpanBase {
158
+ kind: 'retrieval';
159
+ query: string;
160
+ hits: Array<{
161
+ docId: string;
162
+ score: number;
163
+ content?: string;
164
+ }>;
165
+ }
166
+ interface JudgeSpan extends SpanBase {
167
+ kind: 'judge';
168
+ judgeId: string;
169
+ /** Span this judgment applies to. */
170
+ targetSpanId: string;
171
+ dimension: string;
172
+ /** Numeric score (free-range; interpretation up to the judge). */
173
+ score: number;
174
+ rationale?: string;
175
+ evidence?: string;
176
+ }
177
+ interface SandboxSpan extends SpanBase {
178
+ kind: 'sandbox';
179
+ image?: string;
180
+ command?: string;
181
+ exitCode?: number;
182
+ testsTotal?: number;
183
+ testsPassed?: number;
184
+ stdoutHash?: string;
185
+ stderrHash?: string;
186
+ /** Duration in ms; the harness fills this explicitly (endedAt - startedAt may miss setup). */
187
+ wallMs?: number;
188
+ }
189
+ interface GenericSpan extends SpanBase {
190
+ kind: 'agent' | 'custom';
191
+ }
192
+ type Span = LlmSpan | ToolSpan | RetrievalSpan | JudgeSpan | SandboxSpan | GenericSpan;
193
+ type EventKind = 'log' | 'error' | 'budget_decrement' | 'budget_breach' | 'state_mutation' | 'policy_violation' | 'redaction_applied' | 'custom';
194
+ interface TraceEvent {
195
+ eventId: string;
196
+ runId: string;
197
+ spanId?: string;
198
+ kind: EventKind;
199
+ timestamp: number;
200
+ payload: Record<string, unknown>;
201
+ }
202
+ interface BudgetLedgerEntry {
203
+ runId: string;
204
+ dimension: keyof BudgetSpec;
205
+ limit: number;
206
+ consumed: number;
207
+ remaining: number;
208
+ timestamp: number;
209
+ breached: boolean;
210
+ /** Span that triggered this entry, if any. */
211
+ spanId?: string;
212
+ }
213
+ interface Artifact {
214
+ artifactId: string;
215
+ runId: string;
216
+ spanId?: string;
217
+ contentType: string;
218
+ sizeBytes: number;
219
+ /** sha256 in hex. */
220
+ hash: string;
221
+ /** External storage URL (R2, S3, filesystem path). */
222
+ storageUrl?: string;
223
+ /** Inline content for small blobs — keep under ~64KB. */
224
+ inlineContent?: string;
225
+ }
226
+ type FailureClass = 'success' | 'reasoning_error' | 'tool_selection_error' | 'tool_argument_error' | 'tool_recovery_failure' | 'hallucination' | 'instruction_following' | 'safety_refusal_miss' | 'policy_violation' | 'budget_exceeded' | 'format_drift' | 'permission_escalation' | 'pii_leak' | 'cost_overrun' | 'timeout' | 'sandbox_failure' | 'missing_user_data' | 'missing_domain_data' | 'missing_codebase_context' | 'missing_runtime_context' | 'missing_credentials' | 'missing_integration_connection' | 'missing_integration_scope' | 'integration_approval_required' | 'integration_auth_expired' | 'integration_provider_failure' | 'bad_integration_manifest' | 'unsafe_integration_write_denied' | 'stale_external_data' | 'bad_retrieval' | 'insufficient_evidence' | 'contradictory_evidence' | 'ambiguous_user_intent' | 'knowledge_readiness_blocked' | 'unknown';
227
+
228
+ /**
229
+ * Paper-grade RunRecord schema + runtime validator.
230
+ *
231
+ * Every run that participates in a promotion gate, paper table, or
232
+ * researcher loop SHOULD be recorded as a `RunRecord`. The mandatory
233
+ * fields are exactly those the paper "Two Loops, Three Roles" requires
234
+ * for reproducibility: who/what/when/cost/seed/hash, plus the search vs
235
+ * holdout split tag and either a `searchScore` or a `holdoutScore`.
236
+ *
237
+ * This is intentionally NOT a replacement for the rich `Run` /
238
+ * `ProposeReviewReport` / `ScenarioResult` types already in the
239
+ * package. Those are runtime structures with full provenance. A
240
+ * `RunRecord` is the analysis-time projection — the JSON-friendly
241
+ * row you'd put in a parquet file or paste into a notebook.
242
+ *
243
+ * Validate at the boundary:
244
+ *
245
+ * const rec = validateRunRecord(rawJson) // throws on missing
246
+ * const ok = isRunRecord(rawJson) // boolean check
247
+ * const rec = parseRunRecordSafe(rawJson) // { ok, value | error }
248
+ *
249
+ * The validator runs in pure TS — zod is intentionally NOT a
250
+ * dependency. Round-trip tested in `tests/run-record.test.ts`.
251
+ */
252
+
253
+ /** Search/dev/holdout split tag. 'search' is the paper-grade alias for the
254
+ * combined train+test pool that the optimizer is allowed to read. */
255
+ type RunSplitTag = 'search' | 'dev' | 'holdout';
256
+ interface RunTokenUsage {
257
+ input: number;
258
+ /** All generated tokens charged as output, including reasoning tokens. */
259
+ output: number;
260
+ /** Reasoning-token subset of `output`, when the provider reports it. */
261
+ reasoning?: number;
262
+ /** Prompt tokens served from a provider cache. */
263
+ cached?: number;
264
+ /** Prompt tokens written into a provider cache. */
265
+ cacheWrite?: number;
266
+ }
267
+ /**
268
+ * How a run's USD amount was obtained.
269
+ *
270
+ * `costUsd` remains mandatory for wire compatibility. New producers should
271
+ * always populate this discriminated union so a missing bill is never
272
+ * mistaken for an observed zero-dollar run. For `uncaptured`, `costUsd` uses
273
+ * the legacy `0` sentinel while this field carries the truthful null.
274
+ */
275
+ type RunCostProvenance = {
276
+ kind: 'observed';
277
+ usd: number;
278
+ } | {
279
+ kind: 'estimated';
280
+ usd: number;
281
+ } | {
282
+ kind: 'uncaptured';
283
+ usd: null;
284
+ };
285
+ interface RunJudgeMetadata {
286
+ model: string;
287
+ promptVersion: string;
288
+ /** [0,1] confidence the judge declared. Constant judge confidence
289
+ * across many runs is a fallback signal (see `canary.ts`). */
290
+ confidence: number;
291
+ /** True if the judge degraded to a fallback path (rules-only,
292
+ * prior-call cache, etc.). The canary uses this to alert. */
293
+ fallback: boolean;
294
+ }
295
+ /**
296
+ * Per-judge / per-dimension breakdown for runs scored by an ensemble of
297
+ * judges over a multi-dimensional rubric.
298
+ *
299
+ * The collapsed `outcome.searchScore` / `holdoutScore` carries the
300
+ * composite the gate uses. The full breakdown belongs here so consumers
301
+ * can answer "which judge disagreed?", "which dimension dragged the
302
+ * composite down?", and "did half the panel fail?" without re-running.
303
+ *
304
+ * `perJudge[judgeId][dim]` is the canonical source; `perDimMean` and
305
+ * `composite` are convenience projections — derivable but precomputed so
306
+ * downstream IRR primitives (`interRaterReliability`,
307
+ * `corpusInterRaterAgreement`) and reporters don't pay the same
308
+ * aggregation twice.
309
+ *
310
+ * Fail-loud discipline: judges that errored out land in `failedJudges`
311
+ * by id. A missing key in `perJudge` is ambiguous (silent zero vs not
312
+ * run); the explicit list makes a partial-failure recorded as such.
313
+ */
314
+ interface JudgeScoresRecord {
315
+ /** Per-judge per-dimension scores. `{ "kimi-k2.6": { helpfulness: 0.8, clarity: 0.7 }, ... }`. */
316
+ perJudge: Record<string, Record<string, number>>;
317
+ /** Per-dim mean across judges. Convenience — derivable from `perJudge`. */
318
+ perDimMean: Record<string, number>;
319
+ /** Composite mean across all dims and judges. Mirrors the score
320
+ * the gate sees on `outcome.searchScore` / `holdoutScore`. */
321
+ composite: number;
322
+ /** Judges that errored or returned an unparseable verdict. Recorded
323
+ * by id (e.g. `['glm-5.1']`) so a partial-failure case is explicit,
324
+ * not inferred from missing keys in `perJudge`. */
325
+ failedJudges?: string[];
326
+ /** Free-form notes the judges emitted (joined across judges or
327
+ * first-judge only — consumer's choice). */
328
+ notes?: string;
329
+ }
330
+ interface RunOutcome {
331
+ /** Score on the search/optimization split. Optional because a
332
+ * holdout-only evaluation only fills `holdoutScore`. */
333
+ searchScore?: number;
334
+ /** Score on the held-out split. Optional because a search-only run
335
+ * only fills `searchScore`. At least one must be present. */
336
+ holdoutScore?: number;
337
+ /** Bag of any other metric the run produced — judge dimensions,
338
+ * pass/fail counters, latency stats, etc. Numeric only — keeps
339
+ * reporters honest. */
340
+ raw: Record<string, number>;
341
+ /** Per-judge / per-dim breakdown. Consumers writing ensemble
342
+ * judgements populate this; substrate primitives like
343
+ * `interRaterReliability` and `corpusInterRaterAgreement` accept
344
+ * these records as input. Optional — single-judge or scalar-only
345
+ * runs leave it unset. */
346
+ judgeScores?: JudgeScoresRecord;
347
+ /** Authenticity / realness verdict — did the run build the REAL thing on the
348
+ * intended infra, or fake it (see `./authenticity`)? Optional: only domains
349
+ * with an authenticity config populate it. Carried in the corpus so the
350
+ * flywheel / off-policy learning can optimize for real completion, not gamed
351
+ * pass-rate. `score` is 0-1; `gated` is the anti-Goodhart flag — a gated run
352
+ * must not count as a real success regardless of `score`. */
353
+ realness?: {
354
+ score: number;
355
+ gated: boolean;
356
+ reason?: string;
357
+ };
358
+ }
359
+ /**
360
+ * Mandatory paper-grade fields for a single evaluation run. Optional
361
+ * fields are extension points; mandatory fields throw if missing.
362
+ *
363
+ * Hash discipline:
364
+ * - `promptHash` is the sha256 of the EFFECTIVE prompt sent to the
365
+ * model (after any steering bundle merge).
366
+ * - `configHash` is the sha256 of the effective run config (model,
367
+ * temperature, tools, judges, splits). The pair (promptHash,
368
+ * configHash) uniquely identifies an experiment cell.
369
+ *
370
+ * Model snapshot discipline:
371
+ * - `model` MUST encode a snapshot version. Bare aliases like
372
+ * `claude-sonnet-4` or `gpt-4o` are banned — they remap silently.
373
+ * Use `claude-sonnet-4-6@2025-04-15` or `gpt-4o-2024-11-20`.
374
+ */
375
+ interface RunRecord {
376
+ /** UUID for the run. */
377
+ runId: string;
378
+ /** Logical experiment grouping (a treatment vs a baseline within
379
+ * the same sweep should share `experimentId`). */
380
+ experimentId: string;
381
+ /** Stable identifier for the candidate (variant) being run. The
382
+ * promotion gate compares two `candidateId`s on matched items. */
383
+ candidateId: string;
384
+ /** RNG seed for the run. Always recorded — silent re-seeding is
385
+ * the most common cause of non-reproducible numbers. */
386
+ seed: number;
387
+ /** Model identifier WITH snapshot version. */
388
+ model: string;
389
+ /** sha256 of the effective prompt (post-steering). */
390
+ promptHash: string;
391
+ /** sha256 of the effective config. */
392
+ configHash: string;
393
+ /** Git SHA the harness was run from. */
394
+ commitSha: string;
395
+ /** End-to-end wall-clock duration in milliseconds. */
396
+ wallMs: number;
397
+ /** Time spent queued before execution started, if known. */
398
+ queueMs?: number;
399
+ /** Total USD cost. Mandatory — runs without a cost number are
400
+ * unbounded by definition and must not be admitted into the gate.
401
+ * `0` is retained as the compatibility sentinel for an uncaptured amount;
402
+ * inspect `costProvenance` before treating it as observed. */
403
+ costUsd: number;
404
+ /** Observed, model-priced estimate, or genuinely uncaptured USD amount.
405
+ * Optional only so existing serialized RunRecords remain valid. */
406
+ costProvenance?: RunCostProvenance;
407
+ /** Token usage breakdown. */
408
+ tokenUsage: RunTokenUsage;
409
+ /** Judge-side metadata, if a judge was used. */
410
+ judgeMetadata?: RunJudgeMetadata;
411
+ /** Per-split scores + raw bag. */
412
+ outcome: RunOutcome;
413
+ /** Canonical, cross-agent failure class drawn from the shared
414
+ * `FAILURE_CLASSES` taxonomy. This is the aggregation key that makes
415
+ * "which failure dominates across the whole fleet" answerable in ONE
416
+ * vocabulary — every agent classifies against the same enum. Producers
417
+ * set it via the substrate classifier; leave unset only when the failure
418
+ * genuinely can't be classified. */
419
+ failureClass?: FailureClass;
420
+ /** Free-form domain-specific failure detail, scoped UNDER `failureClass`
421
+ * (e.g. failureClass='tool_recovery_failure', failureMode='forge_build_unsatisfied').
422
+ * The within-agent drill-down; `failureClass` is the cross-agent key. */
423
+ failureMode?: string;
424
+ /** Which split this run was drawn from. */
425
+ splitTag: RunSplitTag;
426
+ /**
427
+ * Stable scenario identifier the run was scored against. Optional for
428
+ * backwards compatibility, but **strongly recommended**: every primitive
429
+ * that pairs runs by scenario (preferences, paired stats, BT tournament)
430
+ * keys on this. The campaign artifact populates it canonically; legacy
431
+ * runs without it fall back to inference from `outcome.raw.scenario_id`
432
+ * or `experimentId`.
433
+ */
434
+ scenarioId?: string;
435
+ /**
436
+ * Canonical identity for the agent profile cell that produced this row:
437
+ * profile artifact hash plus optional harness/model/prompt/reporting
438
+ * dimensions. Use `agentProfile.cellId` to group persona sweeps and
439
+ * longitudinal reports by the complete source profile, not by a loose
440
+ * candidate label or opaque config hash.
441
+ */
442
+ agentProfile?: AgentProfileCell;
443
+ }
28
444
 
29
445
  /**
30
446
  * Adaptive curriculum / active scenario selection.
@@ -245,6 +661,24 @@ declare function compareAdaptationCurves(a: AdaptationCurve, b: AdaptationCurve,
245
661
  /** First k at which the curve's per-scenario pass rate reliably hits the threshold. */
246
662
  declare function firstPassK(curve: AdaptationCurve, threshold?: number): number | null;
247
663
 
664
+ /**
665
+ * Adversarial mutation contract.
666
+ *
667
+ * `AdversarialMutation<S>` is the scenario-mutation strategy the fuzz harness
668
+ * (`fuzzAgent`, src/fuzz) drives: paraphrase, edge-case substitution, or
669
+ * compositional combination of a scenario the policy currently passes, looking
670
+ * for the tail inputs that break it. The harness supplies the loop; consumers
671
+ * supply the mutations and the failure detector.
672
+ */
673
+ interface AdversarialMutation<S> {
674
+ id: string;
675
+ /**
676
+ * Mutate one scenario. Return null to skip; return one or more new
677
+ * scenarios. The harness deduplicates by `mutateScenarioId(scenario)`.
678
+ */
679
+ mutate(parent: S, rng: () => number): Promise<S[]> | S[];
680
+ }
681
+
248
682
  /**
249
683
  * Test-time compute scaling curves.
250
684
  *
@@ -630,6 +1064,54 @@ declare function toAnthropicFormat(triples: PreferenceTriple[]): Array<{
630
1064
  margin: number;
631
1065
  }>;
632
1066
 
1067
+ interface RunFilter {
1068
+ scenarioId?: string;
1069
+ variantId?: string;
1070
+ status?: RunStatus;
1071
+ since?: number;
1072
+ until?: number;
1073
+ tag?: {
1074
+ key: string;
1075
+ value: string;
1076
+ };
1077
+ parentRunId?: string;
1078
+ projectId?: string;
1079
+ chatId?: string;
1080
+ layer?: RunLayer;
1081
+ }
1082
+ interface SpanFilter {
1083
+ runId?: string;
1084
+ parentSpanId?: string;
1085
+ kind?: SpanKind;
1086
+ name?: string;
1087
+ toolName?: string;
1088
+ judgeId?: string;
1089
+ since?: number;
1090
+ until?: number;
1091
+ }
1092
+ interface EventFilter {
1093
+ runId?: string;
1094
+ spanId?: string;
1095
+ kind?: EventKind;
1096
+ since?: number;
1097
+ until?: number;
1098
+ }
1099
+ interface TraceStore {
1100
+ appendRun(run: Run): Promise<void>;
1101
+ updateRun(runId: string, patch: Partial<Run>): Promise<void>;
1102
+ appendSpan(span: Span): Promise<void>;
1103
+ updateSpan(spanId: string, patch: Partial<Span>): Promise<void>;
1104
+ appendEvent(event: TraceEvent): Promise<void>;
1105
+ appendArtifact(artifact: Artifact): Promise<void>;
1106
+ appendBudgetEntry(entry: BudgetLedgerEntry): Promise<void>;
1107
+ getRun(runId: string): Promise<Run | undefined>;
1108
+ listRuns(filter?: RunFilter): Promise<Run[]>;
1109
+ spans(filter?: SpanFilter): Promise<Span[]>;
1110
+ events(filter?: EventFilter): Promise<TraceEvent[]>;
1111
+ budget(runId: string): Promise<BudgetLedgerEntry[]>;
1112
+ artifacts(runId: string): Promise<Artifact[]>;
1113
+ }
1114
+
633
1115
  /**
634
1116
  * Process reward extraction — step-level credit assignment from trace spans.
635
1117
  *
@@ -1044,6 +1526,451 @@ interface HarvestOptions {
1044
1526
  */
1045
1527
  declare function buildDatasetFromCorpus(corpusPath: string, config: RlDatasetConfig, opts?: HarvestOptions): Promise<RlDatasetBundle>;
1046
1528
 
1529
+ /**
1530
+ * Off-policy evaluation primitives.
1531
+ *
1532
+ * Standard inverse-probability-weighted (IPS), self-normalized
1533
+ * importance-weighted (SNIPS), and doubly-robust (DR) estimators for the
1534
+ * value of a *target* policy given trajectories collected under a
1535
+ * *behavior* policy. This is the canonical RL eval task: "we have last
1536
+ * week's runs, we changed the policy — how would the new one do without
1537
+ * re-running?"
1538
+ *
1539
+ * The math here is textbook (Dudík, Langford, Li 2011 for DR; Swaminathan
1540
+ * & Joachims 2015 for SNIPS) but the *application* to LLM-agent
1541
+ * evaluation needs care:
1542
+ *
1543
+ * - The "policy" is the (prompt, tool config, model snapshot) triple.
1544
+ * Two policies have the same probability over an action *iff* their
1545
+ * LLM call would emit the same token with the same probability —
1546
+ * which is generally unknowable without the model log-probs.
1547
+ * - For LLM agents, propensity scores must be supplied by the caller
1548
+ * (logged in the trace, recovered from token log-probs, or estimated
1549
+ * via a learned propensity model). We do NOT estimate propensity here.
1550
+ * - Doubly-robust requires a Q-function (model-based reward predictor).
1551
+ * We accept any callable; consumers pass either a tabular average,
1552
+ * a regression fit, or a learned reward model.
1553
+ *
1554
+ * Bias / variance tradeoffs:
1555
+ * - IPS: unbiased; high variance for small overlap, infinite variance
1556
+ * when target has support outside behavior.
1557
+ * - SNIPS: lower variance, slight bias; usually preferred in practice.
1558
+ * - DR: doubly-robust — unbiased if either propensity OR Q-function is
1559
+ * correct. Lowest practical variance when Q is decent. Use this.
1560
+ *
1561
+ * Caveat the panel will land: on the LLM-agent setting, propensity scores
1562
+ * recovered from token log-probs are noisy, the action space is enormous,
1563
+ * and overlap is often poor. These estimators are useful but not magic;
1564
+ * complement with `replayCampaign` (exact replay where the request hashes
1565
+ * match) for high-confidence answers and OPE for the gap.
1566
+ */
1567
+ interface OffPolicyTrajectory {
1568
+ /** Stable id, for traceability through the dataset. */
1569
+ runId: string;
1570
+ /** Reward observed under the behavior policy (the realized outcome). */
1571
+ reward: number;
1572
+ /**
1573
+ * Behavior-policy probability of the action that was taken. For LLM
1574
+ * agents this is typically `exp(sum(token_log_probs))` over the chosen
1575
+ * trajectory. Must be in (0, 1].
1576
+ */
1577
+ behaviorProb: number;
1578
+ /**
1579
+ * Target-policy probability of the same action. For replay-style
1580
+ * counterfactual evaluation this is what the *new* policy would have
1581
+ * assigned to the *old* trajectory. Must be in [0, 1].
1582
+ */
1583
+ targetProb: number;
1584
+ /**
1585
+ * Optional model-based reward prediction at the same context. Used by
1586
+ * `doublyRobust`. Set to `null` for IPS-only evaluation.
1587
+ */
1588
+ qHat?: number | null;
1589
+ }
1590
+ interface OffPolicyEstimate {
1591
+ /** Estimated value of the target policy. */
1592
+ value: number;
1593
+ /** Standard error of the estimate. */
1594
+ standardError: number;
1595
+ /** Effective sample size (Kong 1992). Lower = more reliance on a few high-weight samples. */
1596
+ effectiveSampleSize: number;
1597
+ /** Number of trajectories used. */
1598
+ n: number;
1599
+ /**
1600
+ * Diagnostic: maximum importance weight observed. Large values (>>10x
1601
+ * mean) are a red flag — variance is dominated by a few outliers.
1602
+ */
1603
+ maxImportanceWeight: number;
1604
+ }
1605
+ interface OffPolicyOptions {
1606
+ /**
1607
+ * Cap importance weights at this value (Ionides 2008 truncated IS) to
1608
+ * trade unbiasedness for variance reduction. Default `Infinity` (no cap).
1609
+ * Set e.g. `10` for stable estimates when the policies are close.
1610
+ */
1611
+ weightCap?: number;
1612
+ /** Reward clipping range. Default `[0, 1]`. */
1613
+ rewardClip?: {
1614
+ low: number;
1615
+ high: number;
1616
+ };
1617
+ }
1618
+ /**
1619
+ * Inverse Probability Weighting (Horvitz-Thompson). Unbiased estimator
1620
+ * of E[reward under target policy]. Variance scales with the spread of
1621
+ * target/behavior ratios.
1622
+ */
1623
+ declare function inverseProbabilityWeighting(trajectories: OffPolicyTrajectory[], opts?: OffPolicyOptions): OffPolicyEstimate;
1624
+ /**
1625
+ * Self-Normalized Importance Sampling. Lower variance than vanilla IPS at
1626
+ * the cost of small bias (vanishing as N grows). The right default for
1627
+ * LLM-agent evaluation where overlap is often poor.
1628
+ */
1629
+ declare function selfNormalizedImportanceWeighting(trajectories: OffPolicyTrajectory[], opts?: OffPolicyOptions): OffPolicyEstimate;
1630
+ /**
1631
+ * Doubly-robust off-policy estimator (Dudík, Langford, Li 2011).
1632
+ *
1633
+ * V_DR = (1/N) * sum_i [ q_hat_i + (target_prob_i / behavior_prob_i) * (r_i - q_hat_i) ]
1634
+ *
1635
+ * Unbiased if EITHER:
1636
+ * - the importance ratios are correct (IPS-style validity), OR
1637
+ * - the Q-hat function is correct (model-based validity).
1638
+ *
1639
+ * In practice both are imperfect, but the residual bias is the *product*
1640
+ * of both errors — much smaller than either alone. This is why DR is the
1641
+ * default in production OPE pipelines.
1642
+ *
1643
+ * Requires `qHat` on every trajectory. If any are `null`, the estimator
1644
+ * falls back to SNIPS for those entries (loud-fallback behavior; the
1645
+ * report's `n` reflects the full set but `effectiveSampleSize` accounts
1646
+ * for the lost variance reduction).
1647
+ */
1648
+ declare function doublyRobust(trajectories: OffPolicyTrajectory[], opts?: OffPolicyOptions): OffPolicyEstimate;
1649
+ /**
1650
+ * Convenience: run all three estimators and return them side-by-side.
1651
+ * The recommended diagnostic — agreement across estimators is a much
1652
+ * stronger signal than any single one.
1653
+ */
1654
+ declare function offPolicyEstimateAll(trajectories: OffPolicyTrajectory[], opts?: OffPolicyOptions): {
1655
+ ips: OffPolicyEstimate;
1656
+ snips: OffPolicyEstimate;
1657
+ dr: OffPolicyEstimate;
1658
+ };
1659
+
1660
+ /**
1661
+ * OutcomeStore — deployment outcomes attached to Run IDs.
1662
+ *
1663
+ * Outcomes arrive asynchronously from production telemetry after the
1664
+ * eval run completed: user ratings, retention flags, conversion events,
1665
+ * revenue, support-ticket rate, anything a product team can measure.
1666
+ * The store is a peer to TraceStore — separate lifecycle, same runId
1667
+ * foreign key.
1668
+ *
1669
+ * The whole point of this module is to make the meta-eval correlation
1670
+ * question computable: `correlate(evalMetric, outcomeMetric) → r, ρ, n, CI`.
1671
+ */
1672
+ interface DeploymentOutcome {
1673
+ runId: string;
1674
+ capturedAt: number;
1675
+ /** Numeric outcomes keyed by name — retention_7d, csat, revenue_usd, etc. */
1676
+ metrics: Record<string, number>;
1677
+ /** Dimensions for stratified analysis — cohort, region, user_segment. */
1678
+ labels?: Record<string, string>;
1679
+ /** Free-form provenance (source system, pipeline version). */
1680
+ source?: string;
1681
+ }
1682
+ interface OutcomeFilter {
1683
+ runIds?: string[];
1684
+ since?: number;
1685
+ until?: number;
1686
+ label?: {
1687
+ key: string;
1688
+ value: string;
1689
+ };
1690
+ source?: string;
1691
+ }
1692
+ interface OutcomeStore {
1693
+ append(outcome: DeploymentOutcome): Promise<void>;
1694
+ /** All outcomes attached to this run (a single run can have many — multiple
1695
+ * capture windows over deployment time). */
1696
+ forRun(runId: string): Promise<DeploymentOutcome[]>;
1697
+ list(filter?: OutcomeFilter): Promise<DeploymentOutcome[]>;
1698
+ }
1699
+ declare class InMemoryOutcomeStore implements OutcomeStore {
1700
+ private items;
1701
+ append(outcome: DeploymentOutcome): Promise<void>;
1702
+ forRun(runId: string): Promise<DeploymentOutcome[]>;
1703
+ list(filter?: OutcomeFilter): Promise<DeploymentOutcome[]>;
1704
+ }
1705
+ interface FileSystemOutcomeStoreOptions {
1706
+ dir: string;
1707
+ maxBytes?: number;
1708
+ }
1709
+ declare class FileSystemOutcomeStore implements OutcomeStore {
1710
+ private dir;
1711
+ private maxBytes;
1712
+ private memo?;
1713
+ private loaded;
1714
+ constructor(options: FileSystemOutcomeStoreOptions);
1715
+ private ensureDir;
1716
+ append(outcome: DeploymentOutcome): Promise<void>;
1717
+ private load;
1718
+ forRun(runId: string): Promise<DeploymentOutcome[]>;
1719
+ list(filter?: OutcomeFilter): Promise<DeploymentOutcome[]>;
1720
+ }
1721
+
1722
+ /**
1723
+ * Rubric predictive validity — does our eval rubric predict deployment
1724
+ * outcomes?
1725
+ *
1726
+ * `correlationStudy` (already in this package) joins a `TraceStore` to an
1727
+ * `OutcomeStore` and computes Pearson + Spearman + bootstrap CI for each
1728
+ * (eval-metric, outcome-metric) pair. That answers "does X correlate with
1729
+ * Y at all." `rubricPredictiveValidity` is the campaign-shaped wrapper
1730
+ * around it: take a sequence of `RunRecord`s (the canonical campaign
1731
+ * artifact) and a `DeploymentOutcomeStore`, join on `runId`, return a
1732
+ * ranked verdict on every rubric whose dimension scores were captured in
1733
+ * `outcome.raw`.
1734
+ *
1735
+ * The point — quoting the methodology doc — is that **without this loop
1736
+ * every rubric is faith-based**. Once it's wired, you know which rubrics
1737
+ * have earned their promotion power and which ones are decoration.
1738
+ *
1739
+ * const validity = await rubricPredictiveValidity({
1740
+ * runs: lastQuarter,
1741
+ * outcomes: shipFlagOutcomeStore,
1742
+ * outcomeMetrics: ['revenue_lift', 'retention_30d', 'csat'],
1743
+ * rubrics: ['anti_slop', 'semantic_concept', 'tool_recovery'],
1744
+ * })
1745
+ * for (const r of validity.ranked) {
1746
+ * console.log(`${r.rubric} → ${r.bestOutcome}: ρ=${r.spearman.toFixed(2)}`)
1747
+ * }
1748
+ *
1749
+ * The function is intentionally read-only. Use the verdict to deprecate
1750
+ * decorative rubrics, re-weight composite scores, or trigger a
1751
+ * recalibration sweep when predictive validity drops below a threshold.
1752
+ */
1753
+
1754
+ interface RubricOutcomePair {
1755
+ rubric: string;
1756
+ outcome: string;
1757
+ n: number;
1758
+ pearson: number;
1759
+ spearman: number;
1760
+ ci95: {
1761
+ low: number;
1762
+ high: number;
1763
+ };
1764
+ /**
1765
+ * Verdict bucket. `load_bearing` ≥ 0.7, `informative` ≥ 0.4,
1766
+ * `decorative` < 0.4 in absolute correlation. A negative correlation
1767
+ * with a desired outcome is also `decorative` — actively misleading
1768
+ * is worse than uninformative.
1769
+ */
1770
+ verdict: 'load_bearing' | 'informative' | 'decorative';
1771
+ }
1772
+ interface RubricRanking {
1773
+ rubric: string;
1774
+ /** Outcome metric this rubric correlated best with. */
1775
+ bestOutcome: string;
1776
+ spearman: number;
1777
+ pearson: number;
1778
+ n: number;
1779
+ verdict: RubricOutcomePair['verdict'];
1780
+ }
1781
+ interface RubricPredictiveValidityReport {
1782
+ pairs: RubricOutcomePair[];
1783
+ /** Per-rubric best pair, sorted descending by |spearman|. */
1784
+ ranked: RubricRanking[];
1785
+ joinedSamples: number;
1786
+ skippedRuns: number;
1787
+ /** Rubrics that were declared but never produced a usable score. */
1788
+ rubricsWithoutData: string[];
1789
+ }
1790
+
1791
+ /**
1792
+ * HeldOutGate — first-class held-out paired-delta promotion gate.
1793
+ *
1794
+ * Encodes the "honesty override" pattern that lived inline in
1795
+ * `~/webb/redteam/scripts/agent-eval-autoresearch.ts:138–171`.
1796
+ * The optimizer's best-guess is one thing; what we should actually
1797
+ * ship is another. The gate is the line between them.
1798
+ *
1799
+ * A candidate is promoted iff ALL three pass:
1800
+ *
1801
+ * 1. **Productive runs**: the candidate has at least
1802
+ * `minProductiveRuns` paired observations on items where BOTH
1803
+ * candidate and baseline produced a real (non-silent) score.
1804
+ * 2. **Paired delta**: the lower bound of the bootstrap CI on the
1805
+ * median per-item delta (candidate − baseline) on the HOLDOUT
1806
+ * split is strictly greater than `pairedDeltaThreshold`.
1807
+ * 3. **Overfit gap**: the candidate's gap between search-split
1808
+ * score and holdout-split score is no worse (more positive)
1809
+ * than the baseline's gap by more than `overfitGapThreshold`.
1810
+ * "Better on search, worse on holdout" is the canonical
1811
+ * overfit pattern; this catches it.
1812
+ *
1813
+ * The decision carries a machine-readable `rejectionCode` plus an
1814
+ * `evidence` block with every number the gate looked at, so the
1815
+ * downstream researcher / paper / dashboard can re-derive the
1816
+ * verdict without re-running.
1817
+ *
1818
+ * See also:
1819
+ * - `src/statistics.ts` for `pairedBootstrap` + `wilcoxonSignedRank`
1820
+ * - `src/run-record.ts` for the input row schema
1821
+ * - `src/reference-replay.ts` for the older, reference-replay-
1822
+ * specific promotion path (still useful for replay-style evals).
1823
+ */
1824
+
1825
+ type HeldOutGateRejectionCode = 'few_runs' | 'negative_delta' | 'overfit_gap' | 'cost_ceiling';
1826
+ interface GateEvidence {
1827
+ /** Number of paired (candidate, baseline) holdout observations used. */
1828
+ productiveRuns: number;
1829
+ /** Median of (candidate − baseline) paired holdout deltas. */
1830
+ medianPairedDelta: number;
1831
+ /** Bootstrap CI on the median paired holdout delta. */
1832
+ pairedCI: {
1833
+ low: number;
1834
+ high: number;
1835
+ };
1836
+ /** Wilcoxon signed-rank p-value on the paired holdout deltas. */
1837
+ pairedPValue: number;
1838
+ /** Mean candidate score on the search split (NaN if none). */
1839
+ searchScore: number;
1840
+ /** Mean candidate score on the holdout split (NaN if none). */
1841
+ holdoutScore: number;
1842
+ /** Candidate (search − holdout) gap. */
1843
+ overfitGap: number;
1844
+ /** Baseline (search − holdout) gap. */
1845
+ baselineOverfitGap: number;
1846
+ /** Median per-task USD cost across the candidate's runs. Recorded
1847
+ * even when no `costPerTaskCeiling` is configured so downstream
1848
+ * dashboards (intelligence.tangle.tools) can render \$/task per
1849
+ * generation regardless of gating policy. */
1850
+ medianCandidateCost: number;
1851
+ /** Median per-task USD cost across the baseline runs, for
1852
+ * symmetric reporting. */
1853
+ medianBaselineCost: number;
1854
+ }
1855
+ interface GateDecision$1 {
1856
+ /** Final promote/no-promote verdict. */
1857
+ promote: boolean;
1858
+ /** The candidate that was evaluated. */
1859
+ candidateId: string;
1860
+ /** The baseline it was compared against. */
1861
+ baselineId: string;
1862
+ /** Every number the gate looked at, for audit + paper export. */
1863
+ evidence: GateEvidence;
1864
+ /** Human-readable reason. */
1865
+ reason: string;
1866
+ /** Machine-readable rejection code, or null on promote. */
1867
+ rejectionCode: HeldOutGateRejectionCode | null;
1868
+ }
1869
+
1870
+ /**
1871
+ * Researcher interface — stable hook for an external autonomous-research
1872
+ * agent to drive the meta-loop.
1873
+ *
1874
+ * Implementations live downstream (typically in a private repo that
1875
+ * runs the actual LLM). This package ships only the contract + a
1876
+ * `NoopResearcher` so consumers can wire the surface without being
1877
+ * forced to implement every method up front.
1878
+ *
1879
+ * The four methods mirror the four stages of the paper "Two Loops,
1880
+ * Three Roles":
1881
+ *
1882
+ * inspectFailures — given the observed runs, what failure modes
1883
+ * are present? (data → diagnosis)
1884
+ * proposeChange — given diagnosed failure modes, what
1885
+ * structural changes should we try?
1886
+ * (diagnosis → plan delta)
1887
+ * applyChange — fold the proposed deltas into a concrete
1888
+ * experiment plan against an existing baseline.
1889
+ * (plan delta → executable plan)
1890
+ * evaluateChange — run the plan, return runs + the gate verdict.
1891
+ * (executable plan → verdict)
1892
+ *
1893
+ * Composition is the discipline: a Researcher implementation MUST
1894
+ * keep these four steps separate and inspectable. Conflating
1895
+ * "diagnose + propose + run" into a single LLM call defeats the
1896
+ * point of the framework — you can't audit which step lied.
1897
+ *
1898
+ * THIS INTERFACE IS STABLE. Breaking changes require a new module
1899
+ * (e.g. `Researcher2`) so existing implementations keep working.
1900
+ */
1901
+
1902
+ /** A diagnosed failure mode with the run-IDs that exhibit it. */
1903
+ interface FailureMode {
1904
+ /** Short machine-readable code. Must be stable across runs of the
1905
+ * same researcher to enable longitudinal tracking. */
1906
+ code: string;
1907
+ /** Human-readable description for the paper / dashboard. */
1908
+ description: string;
1909
+ evidence: {
1910
+ /** Run IDs (from `RunRecord.runId`) where this failure mode was
1911
+ * observed. */
1912
+ runIds: string[];
1913
+ /** Number of run samples that informed the diagnosis. */
1914
+ samples: number;
1915
+ };
1916
+ }
1917
+ /** A single steering change the researcher wants to try. */
1918
+ interface SteeringChange {
1919
+ kind: 'reviewer_prompt' | 'skill_add' | 'skill_remove' | 'threshold' | 'budget';
1920
+ /** Implementation-specific payload. Researcher implementations
1921
+ * define the schema — keep this `unknown` here to avoid coupling
1922
+ * the public interface to any one researcher's internal model. */
1923
+ payload: unknown;
1924
+ /** Why the researcher proposed this change. Goes into the audit
1925
+ * trail next to the failure-mode evidence. */
1926
+ rationale: string;
1927
+ /** Optional self-reported expected delta on the headline metric. */
1928
+ expectedDelta?: number;
1929
+ }
1930
+ /** A single experiment plan, mapped onto the search/holdout splits. */
1931
+ interface ExperimentPlan {
1932
+ baselineCandidateId: string;
1933
+ proposedCandidateId: string;
1934
+ changes: SteeringChange[];
1935
+ /** USD ceiling for the entire experiment. The runner must stop
1936
+ * before exceeding this and report a partial result. */
1937
+ evaluationBudgetUsd: number;
1938
+ /** Item IDs (your dataset keys) for the search vs holdout splits. */
1939
+ splits: {
1940
+ search: string[];
1941
+ holdout: string[];
1942
+ };
1943
+ }
1944
+ /** Result of running a plan: every run, plus the gate verdict. */
1945
+ interface ExperimentResult {
1946
+ plan: ExperimentPlan;
1947
+ runs: RunRecord[];
1948
+ gateDecision: GateDecision$1;
1949
+ }
1950
+ /**
1951
+ * The researcher loop. Stable, four-step, inspectable.
1952
+ *
1953
+ * ┌──────────┐ inspectFailures ┌──────────┐ proposeChange ┌──────────┐
1954
+ * │ runs │ ─────────────────▶│ failures │ ──────────────▶│ changes │
1955
+ * └──────────┘ └──────────┘ └────┬─────┘
1956
+ * │
1957
+ * ▼
1958
+ * ┌────────────────┐ applyChange ┌────────┐
1959
+ * │ ExperimentPlan │ ◀────────────│ base │
1960
+ * └────────┬───────┘ └────────┘
1961
+ * │
1962
+ * evaluateChange ▼
1963
+ * ┌────────────────┐
1964
+ * │ ExperimentResult│
1965
+ * └────────────────┘
1966
+ */
1967
+ interface Researcher {
1968
+ inspectFailures(runs: RunRecord[]): Promise<FailureMode[]>;
1969
+ proposeChange(failures: FailureMode[]): Promise<SteeringChange[]>;
1970
+ applyChange(changes: SteeringChange[], baseline: ExperimentPlan): Promise<ExperimentPlan>;
1971
+ evaluateChange(plan: ExperimentPlan): Promise<ExperimentResult>;
1972
+ }
1973
+
1047
1974
  /**
1048
1975
  * `PredictiveValidityResearcher` — concrete `Researcher` implementation
1049
1976
  * that drives selection from outcome-anchored predictive validity.
@@ -1110,6 +2037,121 @@ declare class PredictiveValidityResearcher implements Researcher {
1110
2037
  getLastReport(): RubricPredictiveValidityReport | null;
1111
2038
  }
1112
2039
 
2040
+ /**
2041
+ * Validator-output verdict — substrate primitive for "did this output pass,
2042
+ * and how well?"
2043
+ *
2044
+ * Used by:
2045
+ * - `@tangle-network/agent-eval/matrix` — verdict per cell in the cartesian.
2046
+ * - `@tangle-network/agent-runtime` — Validator<Output, Verdict = DefaultVerdict>.
2047
+ * Runtime keeps `Validator` because it's coupled to runtime-shaped
2048
+ * `ValidationCtx` (iteration, signal, traceEmitter); the verdict TYPE
2049
+ * itself is a substrate concept and lives here.
2050
+ *
2051
+ * Repo layering: agent-eval is the substrate (no upward deps). Both
2052
+ * agent-runtime and agent-knowledge consume this type FROM agent-eval —
2053
+ * never the other way around. See CLAUDE.md "Repo layering" for the rule.
2054
+ */
2055
+ /**
2056
+ * Minimal verdict shape — `valid` + `score` are required; `scores` +
2057
+ * `notes` are optional surface. Validators that need richer shapes
2058
+ * parameterise `Validator<Output, MyVerdict>` with their own type.
2059
+ *
2060
+ * Need structured extras? Extend DefaultVerdict with typed fields — never
2061
+ * serialize extras into `notes`.
2062
+ */
2063
+ interface DefaultVerdict {
2064
+ /** Whether the output meets the validator's pass criteria. */
2065
+ valid: boolean;
2066
+ /** Aggregate score in [0, 1]. Drivers use this for winner selection. */
2067
+ score: number;
2068
+ /** Per-dimension scores. Free-form; weighted into `score` by the validator. */
2069
+ scores?: Record<string, number>;
2070
+ /** Human-readable rationale; surfaces in trace + final-result `winner.verdict`. */
2071
+ notes?: string;
2072
+ }
2073
+
2074
+ /**
2075
+ * Multi-layer verifier — ordered pipeline of verification layers.
2076
+ *
2077
+ * Different contract from {@link JudgeRunner} (which runs parallel
2078
+ * specs against a sandbox). MultiLayerVerifier is a DAG of layers
2079
+ * (install → typecheck → build → lint → serve → semantic → …) with
2080
+ * dependency-based skip, per-layer findings, soft-fail semantics, and
2081
+ * an aggregated `blendedScore` across all passed layers.
2082
+ *
2083
+ * Use when you want:
2084
+ * - ordered stages where a failing upstream stage skips downstream ones
2085
+ * - each stage produces rich `findings` (severity + message + evidence)
2086
+ * - a single composite score across stages with per-stage weights
2087
+ * - soft-fail stages whose failure doesn't abort the pipeline
2088
+ *
2089
+ * Use {@link JudgeRunner} when you want:
2090
+ * - N independent judges running in parallel against the same artifact
2091
+ * - no inter-judge dependencies
2092
+ * - boolean `passed` per judge + overall
2093
+ *
2094
+ * Both primitives compose — JudgeRunner can be invoked as a single
2095
+ * layer inside a MultiLayerVerifier if that suits the caller.
2096
+ */
2097
+
2098
+ type LayerStatus = 'pass' | 'fail' | 'skipped' | 'error' | 'timeout';
2099
+ type Severity = 'critical' | 'major' | 'minor' | 'info';
2100
+ interface Finding {
2101
+ severity: Severity;
2102
+ message: string;
2103
+ evidence?: string;
2104
+ /** Optional layer name the finding belongs to (set by the verifier if omitted). */
2105
+ layer?: string;
2106
+ /**
2107
+ * Free-form structured payload — used by `multiToolchainLayer` to attach
2108
+ * `{ adapter: 'pnpm' }`, by judges to attach evidence pointers, etc.
2109
+ * Renderers MAY interrogate; agent-eval primitives never assume shape.
2110
+ */
2111
+ detail?: Record<string, unknown>;
2112
+ }
2113
+ interface LayerResult {
2114
+ layer: string;
2115
+ status: LayerStatus;
2116
+ /** 0..1 score, optional — layers that don't produce a numeric score omit. */
2117
+ score?: number;
2118
+ durationMs: number;
2119
+ findings: Finding[];
2120
+ /** Short human-readable summary (one line). */
2121
+ reason?: string;
2122
+ /**
2123
+ * Numeric layer-level diagnostics: error counts, warning counts,
2124
+ * cyclomatic complexity, total adapter wall-time, etc. Keyed by
2125
+ * diagnostic name; null = "diagnostic not applicable / not measured."
2126
+ * Renderers that know the keys can display them; ones that don't,
2127
+ * ignore. Free-form on purpose — consumers type the value shape in
2128
+ * their own namespace.
2129
+ */
2130
+ diagnostics?: Record<string, number | null>;
2131
+ /** Any rich per-layer detail — rendered as-is by consumers that know the layer. */
2132
+ detail?: Record<string, unknown>;
2133
+ }
2134
+ /** Extends the substrate verdict spine: `valid` = `allPass` and `score` =
2135
+ * `blendedScore` — derived where the report is aggregated, so spine
2136
+ * consumers (drivers, gates) read this report without an adapter. */
2137
+ interface VerificationReport extends DefaultVerdict {
2138
+ layers: LayerResult[];
2139
+ passCount: number;
2140
+ failCount: number;
2141
+ skippedCount: number;
2142
+ errorCount: number;
2143
+ /** True iff at least one scored layer ran AND every scored layer passed. */
2144
+ allPass: boolean;
2145
+ /**
2146
+ * Weighted mean of `score` across contributing layers. 0 when no layers
2147
+ * contributed. See {@link Layer.failContributesToScore} for fail semantics.
2148
+ */
2149
+ blendedScore: number;
2150
+ durationMs: number;
2151
+ startedAt: string;
2152
+ finishedAt: string;
2153
+ }
2154
+
1113
2155
  /**
1114
2156
  * Verifiable reward channel.
1115
2157
  *
@@ -1336,6 +2378,994 @@ interface DetectRewardHackingInput {
1336
2378
  }
1337
2379
  declare function detectRewardHacking(input: DetectRewardHackingInput): RewardHackingReport;
1338
2380
 
2381
+ type CostChannel = 'agent' | 'judge' | 'verifier' | 'analyst' | 'driver' | (string & {});
2382
+ interface ChannelRollup {
2383
+ channel: CostChannel;
2384
+ calls: number;
2385
+ inputTokens: number;
2386
+ outputTokens: number;
2387
+ cachedTokens: number;
2388
+ costUsd: number;
2389
+ unpricedCalls: number;
2390
+ unknownUsageCalls: number;
2391
+ }
2392
+ interface CostLedgerSummary {
2393
+ totalCalls: number;
2394
+ pendingCalls: number;
2395
+ unresolvedCalls: number;
2396
+ reservedCostUsd: number;
2397
+ inputTokens: number;
2398
+ outputTokens: number;
2399
+ cachedTokens: number;
2400
+ totalCostUsd: number;
2401
+ byChannel: ChannelRollup[];
2402
+ unpricedModels: string[];
2403
+ fullyPriced: boolean;
2404
+ usageComplete: boolean;
2405
+ accountingComplete: boolean;
2406
+ incompleteReasons: string[];
2407
+ }
2408
+
2409
+ /**
2410
+ * RawProviderSink — first-class persistence for the actual HTTP-level
2411
+ * request/response bodies of every LLM provider call.
2412
+ *
2413
+ * Why this is a separate sink from the structured `LlmSpan`:
2414
+ *
2415
+ * - `LlmSpan` records the *intent* — model name, messages, output text,
2416
+ * usage. It's what dashboards read; it's NOT enough for forensics.
2417
+ * - When a downstream consumer reports "the verifier used the wrong route"
2418
+ * or "tokens look right but reasoning was missing," the only way to
2419
+ * answer is the raw HTTP body. Span fields can lie (a proxy can echo
2420
+ * a different `model` value than what actually answered); the raw
2421
+ * response is ground truth.
2422
+ *
2423
+ * Default behaviour: opt-in. Pass `rawSink` to `LlmClientOptions` (or the
2424
+ * matrix runner / BuilderSession sets it up automatically) and every
2425
+ * request, response, and error is recorded — including retries, with the
2426
+ * attempt index attached so a flaky call's full event chain is recoverable.
2427
+ *
2428
+ * Redaction is enforced at sink time. The default redactor strips
2429
+ * `Authorization`, `X-Api-Key`, `X-Auth-Token`, `Cookie` headers and any
2430
+ * payload field whose key matches `apiKey | api_key | bearer | password |
2431
+ * secret | token` (case-insensitive). Override via the sink constructor or
2432
+ * the per-call `redactor`. The `redactedFields` array on the persisted
2433
+ * event lets a reviewer see what was stripped without exposing the values.
2434
+ */
2435
+ type RawProviderDirection = 'request' | 'response' | 'error';
2436
+ interface RawProviderEvent {
2437
+ /** Stable id. Generated by the sink if omitted. */
2438
+ eventId: string;
2439
+ /** Trace context populated by `LlmClient` when the call is wrapped in a span. */
2440
+ runId?: string;
2441
+ spanId?: string;
2442
+ /**
2443
+ * Logical provider name. Free-form so callers can use whatever id matches
2444
+ * their topology (`'openai'`, `'anthropic'`, `'tangle-router'`, …). When
2445
+ * omitted, derived from `baseUrl` in `LlmClientOptions`.
2446
+ */
2447
+ provider: string;
2448
+ model: string;
2449
+ /** Endpoint path, e.g. `'/v1/chat/completions'`. */
2450
+ endpoint: string;
2451
+ /** Base URL used for the call (already-normalised — no trailing slash). */
2452
+ baseUrl: string;
2453
+ /** 0-indexed retry attempt. The first attempt is 0; a retried call gets 1, 2, … */
2454
+ attemptIndex: number;
2455
+ direction: RawProviderDirection;
2456
+ /** Unix ms. */
2457
+ timestamp: number;
2458
+ /** Wall-clock duration of the call leg. Set on `response` and `error` events; null on `request`. */
2459
+ durationMs?: number;
2460
+ statusCode?: number;
2461
+ requestHeaders?: Record<string, string>;
2462
+ requestBody?: unknown;
2463
+ responseHeaders?: Record<string, string>;
2464
+ responseBody?: unknown;
2465
+ /** Set on `direction: 'error'` events. */
2466
+ errorMessage?: string;
2467
+ /** Field paths the redactor stripped from this event ('header:Authorization', 'body.apiKey', …). */
2468
+ redactedFields: string[];
2469
+ }
2470
+ interface RawProviderSinkFilter {
2471
+ runId?: string;
2472
+ spanId?: string;
2473
+ direction?: RawProviderDirection;
2474
+ attemptIndex?: number;
2475
+ }
2476
+ interface RawProviderSink {
2477
+ record(event: RawProviderEvent): Promise<void>;
2478
+ /** Optional listing — implementations that durably persist (file, db) should support this. */
2479
+ list?(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>;
2480
+ /** Optional teardown for backed implementations. */
2481
+ close?(): Promise<void>;
2482
+ }
2483
+ type ProviderRedactor = (event: RawProviderEvent) => RawProviderEvent;
2484
+
2485
+ /**
2486
+ * LLM client with graceful degrade.
2487
+ *
2488
+ * OpenAI-compatible `/v1/chat/completions` client with:
2489
+ * - Exponential-backoff retry on 429 + 5xx gateway errors (502/503/504).
2490
+ * - Retry on transient network errors (fetch failed, AbortError, ECONNRESET).
2491
+ * - Graceful json_schema → json_object degrade on 400 with schema-reject body.
2492
+ * - Fenced-JSON stripping (```json ... ```) for models that wrap structured output.
2493
+ * - Configurable base URL + api key / bearer, works with LiteLLM proxies, OpenAI
2494
+ * directly, cli-bridge subscriptions, and any router that speaks the spec.
2495
+ *
2496
+ * Usage:
2497
+ * const { value, result } = await callLlmJson<MyType>(
2498
+ * { model: 'gpt-4o', messages: [...], jsonSchema: { name: 'x', schema: {...} } },
2499
+ * { baseUrl: 'https://router.tangle.tools/v1', apiKey: process.env.KEY },
2500
+ * )
2501
+ *
2502
+ * This is THE llm-calling seam for agent-eval primitives that need structured
2503
+ * output (semantic concept judge, reviewer directives, critic scores). Primitives
2504
+ * that need free-form text use `callLlm` and parse output themselves.
2505
+ */
2506
+
2507
+ interface LlmUsage {
2508
+ promptTokens: number;
2509
+ completionTokens: number;
2510
+ totalTokens: number;
2511
+ /** False when the provider omitted or malformed prompt/completion usage. */
2512
+ captured?: boolean;
2513
+ /** Proxies populate this when prompt caching is on. */
2514
+ cachedPromptTokens?: number;
2515
+ }
2516
+ interface LlmCallResult {
2517
+ /** The text content of the first choice. Empty string if none. */
2518
+ content: string;
2519
+ usage: LlmUsage;
2520
+ /**
2521
+ * Cost in USD. Pulled from proxy's `_response_cost` field when present;
2522
+ * `null` when neither the proxy nor the caller can derive it.
2523
+ */
2524
+ costUsd: number | null;
2525
+ /** Model name actually used (echoed from response). */
2526
+ model: string;
2527
+ /** Wall-clock duration of the HTTP call (last attempt, if retried). */
2528
+ durationMs: number;
2529
+ /**
2530
+ * `finish_reason` echoed from the first choice (`stop`, `length`,
2531
+ * `content_filter`, `tool_calls`, ...). `null` when the provider omits it.
2532
+ * Exposed so a free-form `callLlm` caller CAN detect a truncated answer
2533
+ * (`length`) instead of treating a cut-off completion as complete. Note:
2534
+ * `callLlm` does not itself reject on it — acting on this signal is the
2535
+ * caller's responsibility (in-repo free-form drivers do not yet enforce it).
2536
+ */
2537
+ finishReason?: string | null;
2538
+ /**
2539
+ * True when `content.trim()` is empty. An empty completion is a silent zero
2540
+ * for free-form `callLlm` callers; this flag is the signal a caller can
2541
+ * inspect to fail loud rather than proceed on an empty string. `callLlm`
2542
+ * surfaces it but does not throw on it.
2543
+ */
2544
+ contentEmpty?: boolean;
2545
+ /** Raw response body. */
2546
+ raw: Record<string, unknown>;
2547
+ }
2548
+ type LlmCallMetadata = Pick<LlmCallResult, 'usage' | 'costUsd' | 'model' | 'durationMs'>;
2549
+ interface LlmClientOptions {
2550
+ /** Base URL (without trailing slash). Must end at the `/v1` prefix. */
2551
+ baseUrl?: string;
2552
+ /** Bearer token — either `apiKey` or `bearer` populates `Authorization: Bearer ...`. */
2553
+ apiKey?: string;
2554
+ bearer?: string;
2555
+ /** Override for the `Authorization` header (e.g. `X-Auth: ...`). Takes precedence over apiKey/bearer. */
2556
+ authHeader?: {
2557
+ name: string;
2558
+ value: string;
2559
+ };
2560
+ /** Stable provider idempotency key, reused across retries of this logical call. */
2561
+ idempotencyKey?: string;
2562
+ /** Default timeout in ms. Per-call can override. */
2563
+ defaultTimeoutMs?: number;
2564
+ /**
2565
+ * Caller-supplied abort signal — e.g. a campaign-wide cancel. Linked to
2566
+ * each attempt's per-attempt timeout controller, so aborting it cancels
2567
+ * the in-flight fetch. A caller abort is FATAL: it is not retried even
2568
+ * though an AbortError otherwise matches the transient patterns.
2569
+ */
2570
+ signal?: AbortSignal;
2571
+ /**
2572
+ * Cross-attempt wall-clock budget in ms, measured from the first attempt.
2573
+ * Before launching each attempt the loop checks the remaining budget and
2574
+ * stops retrying once it is exhausted, rather than waiting the full
2575
+ * per-attempt timeout on every retry. Bounds total time independent of
2576
+ * total attempts × `timeoutMs`.
2577
+ */
2578
+ deadlineMs?: number;
2579
+ /** Total provider attempts. Legacy option name; default 3 (1 initial + 2 retries). */
2580
+ maxRetries?: number;
2581
+ /** Fetch implementation — defaults to global `fetch`. Override for custom transport (e.g. tests). */
2582
+ fetch?: typeof fetch;
2583
+ /**
2584
+ * Optional raw HTTP capture sink. When provided, every request, response,
2585
+ * and error (across all retry attempts) is recorded to the sink, with auth
2586
+ * headers and credential-shaped body fields redacted by default. This is
2587
+ * the layer-1 forensics primitive: structured `LlmSpan`s record intent,
2588
+ * raw events record what actually crossed the wire.
2589
+ */
2590
+ rawSink?: RawProviderSink;
2591
+ /**
2592
+ * Logical provider id attached to raw events. When omitted, derived from
2593
+ * `baseUrl` via `providerFromBaseUrl`.
2594
+ */
2595
+ provider?: string;
2596
+ /** Trace context attached to raw events; populated by emitter-aware callers. */
2597
+ traceContext?: {
2598
+ runId?: string;
2599
+ spanId?: string;
2600
+ };
2601
+ /** Override the redaction strategy for this call. Defaults to `defaultProviderRedactor`. */
2602
+ redactor?: ProviderRedactor;
2603
+ }
2604
+ interface LlmRouteRequirements {
2605
+ /**
2606
+ * Throw if `opts.baseUrl` is undefined, i.e. the call would fall back to
2607
+ * `DEFAULT_BASE_URL`. Set this for evaluation runs where silently using
2608
+ * the public/free-tier router is a defect — the launch reviewer needs to
2609
+ * know exactly which provider answered.
2610
+ */
2611
+ requireExplicitBaseUrl?: boolean;
2612
+ /**
2613
+ * Allowlist of acceptable base URLs. Strings match by prefix
2614
+ * (case-insensitive); RegExps test against the full base URL.
2615
+ */
2616
+ allowedBaseUrls?: Array<string | RegExp>;
2617
+ /** Blocklist that takes precedence over `allowedBaseUrls`. */
2618
+ blockedBaseUrls?: Array<string | RegExp>;
2619
+ /** Throw if no auth header / api key is configured. */
2620
+ requireAuth?: boolean;
2621
+ /**
2622
+ * Logical provider id the configured `baseUrl` is expected to match (via
2623
+ * `providerFromBaseUrl`). Mainly useful when paired with `requireExplicitBaseUrl`.
2624
+ */
2625
+ expectedProvider?: string;
2626
+ }
2627
+
2628
+ /**
2629
+ * FailureClusterView — groups failed runs by (failureClass, triggerTool,
2630
+ * argHash-prefix) so weekly reviews can prioritize the top-N clusters.
2631
+ *
2632
+ * Each cluster includes: N runs, scenarios affected, representative
2633
+ * error message, a proposed mitigation hint (rule → action table).
2634
+ */
2635
+
2636
+ interface FailureCluster {
2637
+ failureClass: FailureClass;
2638
+ /** Tool name when the trigger was a tool span, else undefined. */
2639
+ toolName?: string;
2640
+ /** First 16 chars of argHash — clusters similar args. */
2641
+ argPrefix?: string;
2642
+ /**
2643
+ * Source dimension when the trigger was a judge span (e.g. `'format'`,
2644
+ * `'safety'`, `'correctness'`). Lets cross-template aggregators
2645
+ * group failures by the dimension that fired without overloading
2646
+ * `argPrefix`. Optional — clusters without this field deserialize cleanly.
2647
+ */
2648
+ dimension?: string;
2649
+ runCount: number;
2650
+ scenarioIds: string[];
2651
+ exampleError?: string;
2652
+ exampleRunId: string;
2653
+ }
2654
+ interface FailureClusterReport {
2655
+ clusters: FailureCluster[];
2656
+ totalFailures: number;
2657
+ totalRuns: number;
2658
+ }
2659
+
2660
+ /**
2661
+ * Reporting helpers — production summaries and paper-quality figures — sit alongside `reporter.ts` rather
2662
+ * than replacing it.
2663
+ *
2664
+ * Three artefacts:
2665
+ *
2666
+ * - `summaryTable` Markdown table of per-candidate means,
2667
+ * 95% bootstrap CIs, BH-adjusted Wilcoxon
2668
+ * p-values, and Cohen's d versus a
2669
+ * comparator candidate.
2670
+ * - `paretoChart` Abstract spec for a cost vs quality
2671
+ * scatter, with gate decisions overlaid.
2672
+ * Returns numbers + labels — caller
2673
+ * chooses the plotting library.
2674
+ * - `gainHistogram`
2675
+ * Per-item paired holdout deltas as a
2676
+ * histogram spec (bins + counts + median +
2677
+ * CI). Same "data, not images" contract.
2678
+ *
2679
+ * The figure types are PlotSpecs — JSON-friendly, library-agnostic.
2680
+ * They aren't React components and they aren't PNGs; they are
2681
+ * what you'd hand to vega-lite, plotly, matplotlib, or your own
2682
+ * Canvas renderer to draw the actual figure.
2683
+ */
2684
+
2685
+ interface SummaryTableRow {
2686
+ candidateId: string;
2687
+ n: number;
2688
+ mean: number;
2689
+ ciLow: number;
2690
+ ciHigh: number;
2691
+ /** BH-adjusted q-value vs comparator. NaN if no comparator. */
2692
+ qValue: number;
2693
+ /** Cohen's d vs comparator. NaN if no comparator. */
2694
+ cohensD: number;
2695
+ }
2696
+ interface SummaryTable {
2697
+ rows: SummaryTableRow[];
2698
+ comparator: string | null;
2699
+ split: 'search' | 'holdout';
2700
+ /** Pre-rendered markdown — drop into a paper or PR. */
2701
+ markdown: string;
2702
+ }
2703
+ interface ParetoPoint {
2704
+ candidateId: string;
2705
+ /** Mean USD cost per run on the chosen split. */
2706
+ cost: number;
2707
+ /** Mean score on the chosen split. */
2708
+ quality: number;
2709
+ /** Number of runs that informed this point. */
2710
+ n: number;
2711
+ /** Whether this candidate is on the Pareto frontier — high
2712
+ * quality, low cost, no dominator. */
2713
+ onFrontier: boolean;
2714
+ /** Optional gate verdict for this candidate, if a `GateDecision`
2715
+ * for it was passed in. */
2716
+ gate?: 'promote' | 'reject_few_runs' | 'reject_negative_delta' | 'reject_overfit_gap' | null;
2717
+ }
2718
+ interface ParetoFigureSpec {
2719
+ kind: 'pareto-cost-quality';
2720
+ split: 'search' | 'holdout';
2721
+ points: ParetoPoint[];
2722
+ axes: {
2723
+ x: 'costUsd';
2724
+ y: 'score';
2725
+ };
2726
+ }
2727
+ interface GainDistributionBin {
2728
+ /** Inclusive lower edge. */
2729
+ lo: number;
2730
+ /** Exclusive upper edge (or inclusive if it's the last bin). */
2731
+ hi: number;
2732
+ /** Number of pairs whose delta lands in this bin. */
2733
+ count: number;
2734
+ }
2735
+ interface GainDistributionFigureSpec {
2736
+ kind: 'gain-distribution';
2737
+ candidateId: string;
2738
+ comparator: string;
2739
+ split: 'search' | 'holdout';
2740
+ /** Number of pairs used. */
2741
+ n: number;
2742
+ bins: GainDistributionBin[];
2743
+ median: number;
2744
+ ci: {
2745
+ low: number;
2746
+ high: number;
2747
+ };
2748
+ }
2749
+ type ResearchReportDecision = 'promote' | 'hold' | 'reject' | 'equivalent' | 'needs_more_data';
2750
+ interface ResearchReportOptions {
2751
+ /** Human-readable report title. */
2752
+ title?: string;
2753
+ /** Comparator candidate id. Required for statistical decision guidance. */
2754
+ comparator?: string;
2755
+ /** Which split to use for the primary decision. Default 'holdout'. */
2756
+ split?: 'search' | 'holdout';
2757
+ /** Confidence level used by lower-level report helpers. Default 0.95. */
2758
+ confidence?: number;
2759
+ /** FDR threshold for q-values. Default 0.05. */
2760
+ fdr?: number;
2761
+ /**
2762
+ * Soft floor on paired observations before issuing a directional
2763
+ * promote / reject. Below this we report `needs_more_data` and surface the
2764
+ * minimum detectable effect at the current N. Default 20 — chosen so the
2765
+ * Wilcoxon signed-rank approximation is reasonable and so the paired
2766
+ * bootstrap CI has non-degenerate coverage. Hard floor is enforced at
2767
+ * `RESEARCH_REPORT_HARD_PAIR_FLOOR` (6) regardless of this value.
2768
+ */
2769
+ minPairs?: number;
2770
+ /**
2771
+ * Region of Practical Equivalence on the paired delta. When a candidate's
2772
+ * paired-delta CI is fully contained in `[low, high]`, the decision is
2773
+ * `equivalent` rather than `hold`. Sourced from the domain owner — there is
2774
+ * no statistically-defensible default.
2775
+ */
2776
+ rope?: {
2777
+ low: number;
2778
+ high: number;
2779
+ };
2780
+ /**
2781
+ * Power for the minimum detectable effect (MDE) reported on each candidate.
2782
+ * Default 0.8.
2783
+ */
2784
+ mdePower?: number;
2785
+ /**
2786
+ * Two-sided alpha for the MDE. Default matches `fdr` so the reported MDE
2787
+ * lines up with the test the report actually runs.
2788
+ */
2789
+ mdeAlpha?: number;
2790
+ /** Optional held-out gate decisions keyed by candidate id. */
2791
+ gateDecisions?: Record<string, GateDecision$1>;
2792
+ /** Optional failure clusters from failureClusterView. */
2793
+ failureClusters?: FailureClusterReport;
2794
+ /** Build gain histograms for these candidates. Defaults to all non-comparator candidates. */
2795
+ candidateIds?: string[];
2796
+ /** Deterministic bootstrap seed passed to gainHistogram and the posterior helper. */
2797
+ seed?: number;
2798
+ /** Report timestamp. Defaults to current time. */
2799
+ generatedAt?: string;
2800
+ /**
2801
+ * Hash of a preregistered protocol (e.g. `signManifest({...}).contentHash`).
2802
+ * Embedded verbatim in the report so the analysis can be cited as the
2803
+ * preregistered one rather than a post-hoc fishing expedition.
2804
+ */
2805
+ preregistrationHash?: string;
2806
+ }
2807
+ interface ResearchReportRecommendation {
2808
+ decision: ResearchReportDecision;
2809
+ candidateId: string | null;
2810
+ rationale: string[];
2811
+ risks: string[];
2812
+ nextActions: string[];
2813
+ }
2814
+ interface ResearchReportCandidate {
2815
+ candidateId: string;
2816
+ n: number;
2817
+ mean: number;
2818
+ ciLow: number;
2819
+ ciHigh: number;
2820
+ qValue: number;
2821
+ cohensD: number;
2822
+ meanDeltaVsComparator: number | null;
2823
+ pairedN: number;
2824
+ medianGain: number | null;
2825
+ meanGain: number | null;
2826
+ gainCi: {
2827
+ low: number;
2828
+ high: number;
2829
+ } | null;
2830
+ /**
2831
+ * Bayesian-bootstrap-style posterior summaries on the paired delta. Computed
2832
+ * from the same resamples that produce the gain CI; interpretable as
2833
+ * "fraction of resamples in which the candidate beats the comparator on
2834
+ * matched pairs."
2835
+ */
2836
+ prGreaterThanZero: number | null;
2837
+ prInRope: number | null;
2838
+ /**
2839
+ * Minimum detectable effect (in score units) at the candidate's paired N,
2840
+ * the configured power, and the configured alpha. Standardised by the
2841
+ * observed paired-delta SD and inverted via `requiredSampleSize`. Reported
2842
+ * for every candidate so a `needs_more_data` verdict is actionable.
2843
+ */
2844
+ mde: number | null;
2845
+ onParetoFrontier: boolean;
2846
+ gate?: ParetoPoint['gate'];
2847
+ decision: ResearchReportDecision;
2848
+ decisionReason: string;
2849
+ }
2850
+ interface ResearchReportMethodology {
2851
+ /**
2852
+ * Plain-language assumptions the report depends on. Read these first when
2853
+ * deciding whether the verdict is load-bearing for a launch decision.
2854
+ */
2855
+ assumptions: string[];
2856
+ /** Tests and estimators the verdict was computed from. */
2857
+ methods: string[];
2858
+ /** Alternatives the author considered and why this report didn't take them. */
2859
+ alternatives: string[];
2860
+ /** Failure modes — when this report should NOT drive a decision. */
2861
+ whenNotToApply: string[];
2862
+ /** Citations for the methodological choices above. */
2863
+ citations: string[];
2864
+ }
2865
+ interface ResearchReport {
2866
+ kind: 'agent-eval-research-report';
2867
+ title: string;
2868
+ generatedAt: string;
2869
+ split: 'search' | 'holdout';
2870
+ comparator: string | null;
2871
+ /**
2872
+ * SHA-256 over the canonicalised set of `(runId, candidateId, split)` triples
2873
+ * the report was computed from, plus the comparator and split. Stable across
2874
+ * key insertion order; recomputable by the reader to verify provenance.
2875
+ */
2876
+ runFingerprint: string;
2877
+ preregistrationHash: string | null;
2878
+ rope: {
2879
+ low: number;
2880
+ high: number;
2881
+ } | null;
2882
+ executiveSummary: string[];
2883
+ recommendation: ResearchReportRecommendation;
2884
+ candidates: ResearchReportCandidate[];
2885
+ summary: SummaryTable;
2886
+ charts: {
2887
+ pareto: ParetoFigureSpec;
2888
+ gains: GainDistributionFigureSpec[];
2889
+ };
2890
+ methodology: ResearchReportMethodology;
2891
+ failureClusters?: FailureClusterReport;
2892
+ markdown: string;
2893
+ html: string;
2894
+ }
2895
+
2896
+ /**
2897
+ * TraceEmitter — hierarchical span builder that auto-parents using an
2898
+ * internal stack. One emitter per Run; emitters do NOT share state.
2899
+ *
2900
+ * Convenience methods (`llm`, `tool`, `retrieval`, `judge`, `sandbox`)
2901
+ * return a `SpanHandle` with `.end()` / `.fail()` so callers don't
2902
+ * have to thread spanIds manually. For async workflows that can't use
2903
+ * the stack (e.g. fan-out parallel calls), pass `parentSpanId`
2904
+ * explicitly.
2905
+ */
2906
+
2907
+ interface SpanHandle<S extends Span = Span> {
2908
+ span: S;
2909
+ end(patch?: Partial<S>): Promise<void>;
2910
+ fail(error: string | Error, patch?: Partial<S>): Promise<void>;
2911
+ }
2912
+ interface RunCompleteHookContext {
2913
+ runId: string;
2914
+ emitter: TraceEmitter;
2915
+ store: TraceStore;
2916
+ /** Outcome the caller passed to `endRun` (undefined for `abortRun`). */
2917
+ outcome?: RunOutcome$1;
2918
+ /** Final run status. */
2919
+ status: 'completed' | 'failed' | 'aborted';
2920
+ }
2921
+ type RunCompleteHook = (ctx: RunCompleteHookContext) => Promise<void> | void;
2922
+ interface TraceEmitterOptions {
2923
+ runId?: string;
2924
+ /** Inject a clock for deterministic tests. */
2925
+ now?: () => number;
2926
+ /** Inject an id generator for deterministic tests. */
2927
+ id?: () => string;
2928
+ /**
2929
+ * Hooks fired after `endRun` / `abortRun` writes the final run state.
2930
+ * Designed for trace-analyst auto-execution, integrity assertions, and
2931
+ * outbound notifications. Hooks run sequentially in the order supplied.
2932
+ *
2933
+ * By default a hook that throws is swallowed and logged as a `note` event
2934
+ * on the run — auto-orchestration must not crash the underlying flow.
2935
+ * Set `hookErrors: 'throw'` to propagate.
2936
+ */
2937
+ onRunComplete?: RunCompleteHook[];
2938
+ /** `'swallow'` (default) | `'throw'`. */
2939
+ hookErrors?: 'swallow' | 'throw';
2940
+ }
2941
+ declare class TraceEmitter {
2942
+ private store;
2943
+ private stack;
2944
+ private _runId;
2945
+ private now;
2946
+ private id;
2947
+ private hooks;
2948
+ private hookErrors;
2949
+ constructor(store: TraceStore, options?: TraceEmitterOptions);
2950
+ get runId(): string;
2951
+ get traceStore(): TraceStore;
2952
+ /** Append a hook after construction (e.g. attach the trace analyst). */
2953
+ addRunCompleteHook(hook: RunCompleteHook): void;
2954
+ /**
2955
+ * Begin a Run.
2956
+ *
2957
+ * `scenarioId` is required on the persisted Run shape — every Run downstream
2958
+ * gets a non-empty scenarioId so filters and aggregations stay simple — but
2959
+ * the INPUT here accepts it as optional. When omitted, startRun substitutes
2960
+ * a sensible default (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) so
2961
+ * runtime / operator / meta-eval runs that have no curated-scenario corpus
2962
+ * to anchor to don't have to invent placeholder strings at the call site.
2963
+ */
2964
+ startRun(run: Omit<Run, 'runId' | 'scenarioId' | 'startedAt' | 'status'> & {
2965
+ scenarioId?: string;
2966
+ }): Promise<Run>;
2967
+ endRun(outcome?: RunOutcome$1): Promise<void>;
2968
+ abortRun(reason: string): Promise<void>;
2969
+ private runHooks;
2970
+ span<S extends Span = Span>(init: {
2971
+ kind: SpanKind;
2972
+ name: string;
2973
+ parentSpanId?: string;
2974
+ attributes?: Record<string, unknown>;
2975
+ } & Partial<Omit<S, 'spanId' | 'runId' | 'startedAt' | 'kind' | 'name'>>): Promise<SpanHandle<S>>;
2976
+ private handle;
2977
+ private pop;
2978
+ llm(init: Omit<LlmSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<LlmSpan>>;
2979
+ tool(init: Omit<ToolSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<ToolSpan>>;
2980
+ retrieval(init: Omit<RetrievalSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<RetrievalSpan>>;
2981
+ recordJudge(verdict: Omit<JudgeSpan, 'spanId' | 'runId' | 'kind' | 'startedAt' | 'endedAt'>): Promise<JudgeSpan>;
2982
+ sandbox(init: Omit<SandboxSpan, 'spanId' | 'runId' | 'kind' | 'startedAt'>): Promise<SpanHandle<SandboxSpan>>;
2983
+ emit(event: {
2984
+ kind: EventKind;
2985
+ spanId?: string;
2986
+ payload?: Record<string, unknown>;
2987
+ }): Promise<TraceEvent>;
2988
+ recordBudget(entry: Omit<BudgetLedgerEntry, 'runId' | 'timestamp'> & {
2989
+ timestamp?: number;
2990
+ }): Promise<BudgetLedgerEntry>;
2991
+ recordArtifact(artifact: Omit<Artifact, 'artifactId' | 'runId'>): Promise<Artifact>;
2992
+ /**
2993
+ * Runs `fn` inside a span; auto-ends on success, auto-fails on throw.
2994
+ * Returns the fn's return value. Use this for the 95% case.
2995
+ */
2996
+ within<T>(init: Parameters<TraceEmitter['span']>[0], fn: (handle: SpanHandle) => Promise<T>): Promise<T>;
2997
+ }
2998
+
2999
+ /**
3000
+ * Run-completion integrity check — at end of run, verify the expected event
3001
+ * types were actually captured. The point is the launch-review failure mode:
3002
+ * a run *appears* successful but the raw provider events were never written,
3003
+ * so a downstream reviewer can't reconstruct what happened.
3004
+ *
3005
+ * Pattern:
3006
+ *
3007
+ * const report = await assertRunCaptured(store, runId, {
3008
+ * llmSpansMin: 1,
3009
+ * judgeSpansMin: 1,
3010
+ * rawSink: providerSink, // must have ≥ 1 event for this run
3011
+ * requireRawCoverageOfLlmSpans: true, // every llm span has matching raw events
3012
+ * })
3013
+ * if (!report.ok) throwIfRunIncomplete(report) // or mark run failed and continue
3014
+ *
3015
+ * The function is read-only on the store and returns a structured report;
3016
+ * the caller chooses the failure mode (throw, mark run failed, log warning).
3017
+ * `throwIfRunIncomplete` is the convenient strict mode.
3018
+ */
3019
+
3020
+ interface RunIntegrityExpectations {
3021
+ /** Minimum LLM span count. Default 0 (no requirement). */
3022
+ llmSpansMin?: number;
3023
+ /** Minimum judge span count. Default 0. */
3024
+ judgeSpansMin?: number;
3025
+ /** Minimum tool span count. Default 0. */
3026
+ toolSpansMin?: number;
3027
+ /**
3028
+ * Raw provider sink to consult for capture verification. When present,
3029
+ * the check requires at least one raw event for the run.
3030
+ */
3031
+ rawSink?: RawProviderSink;
3032
+ /** Minimum raw provider event count. Default 0; ignored when `rawSink` absent. */
3033
+ rawProviderEventsMin?: number;
3034
+ /**
3035
+ * Every LLM span must have at least one matching raw `request` event
3036
+ * (matched by spanId). Catches the common bug where the structured span
3037
+ * was emitted but the raw HTTP capture was wired to a different sink.
3038
+ */
3039
+ requireRawCoverageOfLlmSpans?: boolean;
3040
+ /** Run outcome must be set (not null/undefined). Default false. */
3041
+ requireOutcome?: boolean;
3042
+ }
3043
+ type RunIntegrityIssueCode = 'no_run' | 'missing_llm_spans' | 'missing_judge_spans' | 'missing_tool_spans' | 'missing_raw_events' | 'no_raw_sink' | 'orphan_llm_span' | 'missing_outcome';
3044
+ interface RunIntegrityIssue {
3045
+ code: RunIntegrityIssueCode;
3046
+ message: string;
3047
+ detail?: Record<string, unknown>;
3048
+ }
3049
+ interface RunIntegrityReport {
3050
+ ok: boolean;
3051
+ runId: string;
3052
+ llmSpanCount: number;
3053
+ judgeSpanCount: number;
3054
+ toolSpanCount: number;
3055
+ rawProviderEventCount: number;
3056
+ /**
3057
+ * Coverage of LLM spans by raw provider events keyed on spanId.
3058
+ * `total` is the number of LLM spans; `covered` is the count with at
3059
+ * least one matching `request` raw event.
3060
+ */
3061
+ rawSpanCoverage: {
3062
+ covered: number;
3063
+ total: number;
3064
+ };
3065
+ issues: RunIntegrityIssue[];
3066
+ }
3067
+
3068
+ /**
3069
+ * EvalCampaign — opinionated matrix runner that wires the four
3070
+ * capture-integrity directives by construction.
3071
+ *
3072
+ * The canonical benchmark shape — matrix runner → for each
3073
+ * (variant, scenario, seed) → start a TraceEmitter → call LLMs → end the
3074
+ * run → analyze — has a bug class at the integration boundary: raw
3075
+ * events not captured, route silently wrong, integrity not asserted,
3076
+ * analyst never run. The directives in `SKILL.md § Capture integrity`
3077
+ * are the mitigations.
3078
+ *
3079
+ * `EvalCampaign` is the structural fix — consumers don't wire the
3080
+ * integrity surface themselves; the campaign owns it. Specifically:
3081
+ *
3082
+ * - calls `assertLlmRoute` once at preflight before any work runs
3083
+ * - constructs a per-run `TraceStore` and `RawProviderSink` via factories
3084
+ * - constructs the `TraceEmitter` with `onRunComplete: [analyst hook]`
3085
+ * - hands the runner an `LlmClientOptions` pre-wired with the sink and
3086
+ * trace context — the runner can't accidentally call an LLM without
3087
+ * capturing the raw HTTP envelope
3088
+ * - calls `assertRunCaptured` after every `endRun` and routes failures
3089
+ * through a configurable policy (`throw` / `mark_failed` / `log`)
3090
+ * - assembles per-run `RunRecord`s and runs `researchReport` at the end
3091
+ * so the campaign artifact is launch-decision-grade by default
3092
+ * - embeds the campaign fingerprint (a SHA-256 over the canonicalised
3093
+ * run set) and optional `preregistrationHash` in the report
3094
+ *
3095
+ * The runner contract is intentionally narrow: produce a `CampaignRunOutcome`
3096
+ * given a fully-wired `CampaignRunContext`. Everything orchestration-shaped
3097
+ * lives in the campaign. This is the inversion-of-control point — consumers
3098
+ * stop writing matrix runners and start writing scenario-runners.
3099
+ *
3100
+ * Out of scope for v1 (tracked in `docs/research-report-methodology.md`):
3101
+ *
3102
+ * - Distributed/cluster execution (concurrency is local async)
3103
+ * - Adaptive sampling / sequential interim looks
3104
+ * - Resume from partial state across crashes
3105
+ * - LLM-call retry beyond what `LlmClient` already does
3106
+ */
3107
+
3108
+ interface CampaignVariant<V> {
3109
+ id: string;
3110
+ payload: V;
3111
+ }
3112
+ interface CampaignScenario {
3113
+ scenarioId: string;
3114
+ /** Free-form metadata propagated to runs and reports. */
3115
+ tags?: Record<string, string>;
3116
+ }
3117
+ interface CampaignRunContext<V> {
3118
+ /** Stable run id. The campaign generates this; the runner does not. */
3119
+ runId: string;
3120
+ /** Logical experiment id (campaignId by default; overridable per-run via opts). */
3121
+ experimentId: string;
3122
+ variant: V;
3123
+ variantId: string;
3124
+ scenarioId: string;
3125
+ scenarioTags: Record<string, string>;
3126
+ seed: number;
3127
+ splitTag: RunSplitTag;
3128
+ /**
3129
+ * The TraceEmitter for this run, with `onRunComplete` hooks pre-wired
3130
+ * (analyst auto-execution if configured, plus integrity check). The
3131
+ * runner MUST call `emitter.startRun` before doing any work and either
3132
+ * `emitter.endRun` or `emitter.abortRun` before returning.
3133
+ */
3134
+ emitter: TraceEmitter;
3135
+ store: TraceStore;
3136
+ rawSink: RawProviderSink;
3137
+ /**
3138
+ * Pre-wired LLM client options — `rawSink` and `traceContext` are populated
3139
+ * so any `callLlm(req, ctx.llmOpts)` automatically captures raw HTTP. The
3140
+ * runner can spread additional fields if needed.
3141
+ */
3142
+ llmOpts: LlmClientOptions;
3143
+ }
3144
+ interface CampaignRunOutcome {
3145
+ /** Did the run pass? Mirrors `RunOutcome.pass` semantics. */
3146
+ pass: boolean;
3147
+ /** Score for the run on its split. Maps to `searchScore` or `holdoutScore`. */
3148
+ score: number;
3149
+ /** Mandatory cost in USD. Use 0 + raw.cost_unknown=1 only if truly unknown. */
3150
+ costUsd: number;
3151
+ tokenUsage: RunTokenUsage;
3152
+ /** Snapshot model id (e.g. `claude-sonnet-4-6@2025-04-15`). */
3153
+ model: string;
3154
+ /** sha256 of the effective prompt sent to the model. */
3155
+ promptHash: string;
3156
+ /** sha256 of the effective config (model, temperature, tools, judges, splits). */
3157
+ configHash: string;
3158
+ /** Optional extra numeric metrics to land in `outcome.raw`. */
3159
+ raw?: Record<string, number>;
3160
+ /** Canonical cross-agent failure class from the shared `FAILURE_CLASSES`
3161
+ * taxonomy. Propagated to `RunRecord.failureClass` so campaign runs
3162
+ * aggregate failures in the same vocabulary as every other producer. */
3163
+ failureClass?: FailureClass;
3164
+ /** Optional free-form failure detail, scoped under `failureClass`. */
3165
+ failureMode?: string;
3166
+ /** Optional judge metadata when a judge was used. */
3167
+ judgeMetadata?: RunJudgeMetadata;
3168
+ /**
3169
+ * Optional per-judge / per-dim breakdown for ensemble-judged runs.
3170
+ * Propagated to `outcome.judgeScores` on the resulting `RunRecord`.
3171
+ * Single-judge or scalar-only runs leave this unset.
3172
+ */
3173
+ judgeScores?: JudgeScoresRecord;
3174
+ /**
3175
+ * Agent profile cell observed by the runner. When supplied, it overrides
3176
+ * `EvalCampaignOptions.agentProfile` for this run and must match the
3177
+ * outcome's `model` and `promptHash`.
3178
+ */
3179
+ agentProfile?: AgentProfileCell | AgentProfileCellInput;
3180
+ }
3181
+ type CampaignRunner<V> = (ctx: CampaignRunContext<V>) => Promise<CampaignRunOutcome>;
3182
+ type CampaignIntegrityPolicy = 'throw' | 'mark_failed' | 'log';
3183
+ interface EvalCampaignOptions<V> {
3184
+ /**
3185
+ * Stable id for the campaign. Used as the default `experimentId` on
3186
+ * every run, and folded into the campaign fingerprint.
3187
+ */
3188
+ campaignId: string;
3189
+ variants: CampaignVariant<V>[];
3190
+ scenarios: CampaignScenario[];
3191
+ /** Default `[0, 1, 2]`. */
3192
+ seeds?: number[];
3193
+ /** Default `'holdout'` — the split that anchors a launch decision. */
3194
+ splitTag?: RunSplitTag;
3195
+ /** Git SHA the campaign is run against. Mandatory; `RunRecord` rejects unset. */
3196
+ commitSha: string;
3197
+ /**
3198
+ * LLM client config. Augmented per-run with `rawSink` and `traceContext`
3199
+ * before being passed to the runner. The campaign asserts this config
3200
+ * matches `routeRequirements` once at preflight.
3201
+ */
3202
+ llmOpts: LlmClientOptions;
3203
+ /**
3204
+ * Default `{ requireExplicitBaseUrl: true, requireAuth: true }` — fail
3205
+ * loud if the campaign would silently fall back to the public router or
3206
+ * run unauthenticated. Override with an empty object to disable.
3207
+ */
3208
+ routeRequirements?: LlmRouteRequirements;
3209
+ /**
3210
+ * Per-run TraceStore factory. Common shape: a fresh store per run keyed
3211
+ * on `runId`. Implementations that share a store across the campaign
3212
+ * are valid — the campaign only writes through `emitter`.
3213
+ */
3214
+ storeFactory: (params: CampaignFactoryParams) => TraceStore;
3215
+ /**
3216
+ * Per-run RawProviderSink factory. Defaults to `FileSystemRawProviderSink`
3217
+ * rooted at `${workDir}/raw-events/${runId}` if `workDir` is supplied;
3218
+ * otherwise required. Forensic capture is non-negotiable in a campaign
3219
+ * run — pass `NoopRawProviderSink` explicitly if you want to opt out.
3220
+ */
3221
+ rawSinkFactory?: (params: CampaignFactoryParams) => RawProviderSink;
3222
+ /**
3223
+ * Filesystem root for default `rawSinkFactory`. Ignored if
3224
+ * `rawSinkFactory` is supplied.
3225
+ */
3226
+ workDir?: string;
3227
+ /**
3228
+ * Extra `onRunComplete` hooks the campaign appends (after its own
3229
+ * integrity-check hook). Pass `traceAnalystOnRunComplete(...)` here.
3230
+ */
3231
+ onRunComplete?: RunCompleteHook[];
3232
+ /**
3233
+ * Per-run integrity expectations. Defaults to:
3234
+ * `{ llmSpansMin: 1, requireRawCoverageOfLlmSpans: true, requireOutcome: true }`.
3235
+ * Override (e.g. `{ llmSpansMin: 0 }`) for runs that don't call LLMs.
3236
+ */
3237
+ integrity?: RunIntegrityExpectations;
3238
+ /** Behaviour when integrity fails. Default `'mark_failed'`. */
3239
+ onIntegrityFailure?: CampaignIntegrityPolicy;
3240
+ /**
3241
+ * Per-run runner. Receives a fully-wired context; produces an outcome
3242
+ * the campaign converts into a `RunRecord`.
3243
+ */
3244
+ runner: CampaignRunner<V>;
3245
+ /**
3246
+ * If set, the campaign computes `researchReport` at the end. `comparator`
3247
+ * is a `variantId`. Other fields are forwarded verbatim.
3248
+ */
3249
+ report?: {
3250
+ comparator?: string;
3251
+ } & Omit<ResearchReportOptions, 'comparator' | 'preregistrationHash' | 'generatedAt'>;
3252
+ /**
3253
+ * Hash of a signed `HypothesisManifest` (see `pre-registration.ts`).
3254
+ * Embedded in the campaign fingerprint and the research report.
3255
+ */
3256
+ preregistrationHash?: string;
3257
+ /** Local concurrency. Default `1` (sequential). */
3258
+ concurrency?: number;
3259
+ /**
3260
+ * Override the time source. Tests pass a mock to make wallMs deterministic.
3261
+ */
3262
+ now?: () => number;
3263
+ /** Override the runId generator. Tests pin this. */
3264
+ runId?: (params: CampaignFactoryParams) => string;
3265
+ /**
3266
+ * Agent profile cell for campaign runs. Static profiles can pass an object;
3267
+ * routers or variant-specific harnesses can pass a factory. The campaign
3268
+ * stamps the built cell onto every `RunRecord` and rejects profile/model or
3269
+ * profile/prompt contradictions.
3270
+ */
3271
+ agentProfile?: AgentProfileCell | AgentProfileCellInput | ((params: CampaignFactoryParams & {
3272
+ variant: V;
3273
+ scenarioTags: Record<string, string>;
3274
+ }) => AgentProfileCell | AgentProfileCellInput | Promise<AgentProfileCell | AgentProfileCellInput>);
3275
+ }
3276
+ interface CampaignFactoryParams {
3277
+ campaignId: string;
3278
+ runId: string;
3279
+ variantId: string;
3280
+ scenarioId: string;
3281
+ seed: number;
3282
+ }
3283
+ interface FailedRun {
3284
+ runId: string;
3285
+ variantId: string;
3286
+ scenarioId: string;
3287
+ seed: number;
3288
+ reason: string;
3289
+ error?: string;
3290
+ }
3291
+ interface EvalCampaignResult {
3292
+ campaignId: string;
3293
+ /** SHA-256 over canonicalised `(variantIds, scenarioIds, seeds, comparator, splitTag, baseUrl, provider, preregistrationHash)`. */
3294
+ campaignFingerprint: string;
3295
+ preregistrationHash: string | null;
3296
+ /** Successful runs only. Failed runs land in `failedRuns`. */
3297
+ runs: RunRecord[];
3298
+ /** Integrity reports for every successful run. */
3299
+ integrityReports: RunIntegrityReport[];
3300
+ failedRuns: FailedRun[];
3301
+ /** Computed when `report` is set on options. */
3302
+ report?: ResearchReport;
3303
+ startedAt: string;
3304
+ endedAt: string;
3305
+ }
3306
+ declare function runEvalCampaign<V>(opts: EvalCampaignOptions<V>): Promise<EvalCampaignResult>;
3307
+
3308
+ /**
3309
+ * Always-valid sequential evaluation.
3310
+ *
3311
+ * `researchReport` assumes a single pre-specified analysis. Real
3312
+ * consumers run campaigns weekly / nightly / per-PR; each new run silently
3313
+ * inflates the false-discovery rate, because the BH-FDR guarantee is for
3314
+ * the *first* look, not the 47th. Without time-uniform inference,
3315
+ * launch-decision teams either (a) don't peek, which forfeits the cost
3316
+ * advantage of stop-when-decisive, or (b) peek and pretend they didn't,
3317
+ * which forfeits scientific validity.
3318
+ *
3319
+ * This module ships **e-value-based confidence sequences** for paired
3320
+ * bounded outcomes. The methodology is the predictable plug-in betting
3321
+ * martingale of Waudby-Smith & Ramdas (2024) — provably valid at *any*
3322
+ * stopping time. Concretely:
3323
+ *
3324
+ * For paired deltas D_1, D_2, … ∈ [-c, c] with the null H_0: E[D] ≤ 0,
3325
+ * a betting fraction λ_i is chosen using only D_{1..i-1} (predictable
3326
+ * plug-in), and the running e-value is
3327
+ *
3328
+ * E_t = ∏_{i=1}^{t} (1 + λ_i · D_i)
3329
+ *
3330
+ * E_t is a non-negative martingale under H_0 with E[E_t] ≤ 1, so by
3331
+ * Ville's inequality, P(∃ t : E_t ≥ 1/α) ≤ α — we can reject the null
3332
+ * at any time without inflating the type-I error.
3333
+ *
3334
+ * Combined with `runEvalCampaign`, every consumer running rolling
3335
+ * campaigns gains the ability to ship the moment evidence is decisive,
3336
+ * stop-early on dead-on-arrival variants, and accumulate evidence across
3337
+ * partial runs without spending the FDR budget. No new sweep is wasted.
3338
+ *
3339
+ * References:
3340
+ * - Howard, S. R., Ramdas, A., McAuliffe, J., Sekhon, J. (2021).
3341
+ * Time-uniform, nonparametric, nonasymptotic confidence sequences.
3342
+ * Annals of Statistics, 49(2), 1055–1080.
3343
+ * - Waudby-Smith, I., Ramdas, A. (2024). Estimating means of bounded
3344
+ * random variables by betting. JRSS B, 86(1), 1–27.
3345
+ */
3346
+ type SequentialDecision = 'promote_now' | 'continue' | 'reject_now' | 'equivalent';
3347
+ interface InterimReleaseConfidence {
3348
+ candidates: Array<{
3349
+ candidateId: string;
3350
+ decision: SequentialDecision;
3351
+ decisionFiredAt: number | null;
3352
+ finalEvalue: number;
3353
+ finalPValue: number;
3354
+ pairs: number;
3355
+ csLow: number;
3356
+ csHigh: number;
3357
+ }>;
3358
+ /**
3359
+ * Campaign-level recommendation: pick the strongest 'promote_now', else
3360
+ * 'continue' if any candidate is still live, else 'reject_now' if every
3361
+ * candidate is dead, else 'equivalent'.
3362
+ */
3363
+ recommendation: {
3364
+ decision: SequentialDecision;
3365
+ candidateId: string | null;
3366
+ };
3367
+ }
3368
+
1339
3369
  /**
1340
3370
  * `runRLCampaign` — top-level orchestrator that runs the matrix and
1341
3371
  * produces every RL-ready artifact in one call.
@@ -1413,6 +3443,307 @@ interface RLCampaignResult<V> {
1413
3443
  }
1414
3444
  declare function runRLCampaign<V>(opts: RunRLCampaignOptions<V>): Promise<RLCampaignResult<V>>;
1415
3445
 
3446
+ /**
3447
+ * Analyst contract — the missing orchestration layer over agent-eval's
3448
+ * existing analyzers (analyzeTraces, MultiLayerVerifier, RunCritic,
3449
+ * SemanticConceptJudge, JudgeFn, ...).
3450
+ *
3451
+ * Each existing primitive returns its own output shape. The Analyst
3452
+ * contract is the single envelope every primitive lifts into, so a
3453
+ * registry can run N analysts against a run and a single renderer can
3454
+ * compose findings without knowing which analyzer produced them.
3455
+ *
3456
+ * The contract is intentionally domain-agnostic: nothing here knows
3457
+ * about code, voice, RAG, or any particular agent stack. Analysts
3458
+ * declare what INPUT KIND they need (a trace store, an artifact dir,
3459
+ * a RunRecord, a JudgeInput, or `custom`), and the registry routes
3460
+ * the matching input from `AnalystRunInputs`.
3461
+ */
3462
+
3463
+ interface EvidenceRef {
3464
+ /**
3465
+ * Where the evidence lives. `span` and `event` refer to OTLP trace
3466
+ * elements; `artifact` to a file inside the run's artifact tree;
3467
+ * `finding` to another AnalystFinding (cross-analyst chaining);
3468
+ * `metric` to a named scalar reading the renderer knows how to read.
3469
+ */
3470
+ kind: 'span' | 'event' | 'artifact' | 'finding' | 'metric';
3471
+ uri: string;
3472
+ excerpt?: string;
3473
+ }
3474
+
3475
+ type PolicyEditSchemaVersion = 'policy-edit/v1';
3476
+ declare const POLICY_EDIT_AXES: readonly ["carrier", "representation", "budget", "sampling", "output_contract", "tool_contract", "routing", "memory", "agent_profile", "deployment_target"];
3477
+ type PolicyEditAxis = (typeof POLICY_EDIT_AXES)[number];
3478
+ declare const POLICY_EDIT_TARGET_SURFACES: readonly ["prompt", "tool-contract", "runtime-config", "memory", "agent-profile", "code", "deployment"];
3479
+ type PolicyEditTargetSurface = (typeof POLICY_EDIT_TARGET_SURFACES)[number];
3480
+ type PolicyEditRisk = 'low' | 'medium' | 'high' | 'unknown';
3481
+ type PolicyEditGainDirection = 'increase' | 'decrease';
3482
+ type PolicyEditGainUnit = 'absolute' | 'relative' | 'percent' | 'score';
3483
+ interface PolicyEditTarget {
3484
+ surface: PolicyEditTargetSurface;
3485
+ /** Stable path inside the target surface, for example `system-prompt:tools`
3486
+ * or `budget.maxTurns`. */
3487
+ path?: string;
3488
+ /** Optional canonical deployment identity. Store the existing cell, not a
3489
+ * local profile shape. */
3490
+ agentProfileCell?: AgentProfileCell;
3491
+ /** Human label when the path is not enough for a readable audit trail. */
3492
+ label?: string;
3493
+ }
3494
+ type PolicyEditChange = {
3495
+ kind: 'text';
3496
+ mode: 'append' | 'prepend' | 'replace';
3497
+ value: string;
3498
+ /** Required when `mode === 'replace'`; exact match only. */
3499
+ find?: string;
3500
+ } | {
3501
+ kind: 'json';
3502
+ mode: 'set' | 'merge' | 'remove';
3503
+ path: string;
3504
+ value?: AgentProfileJson;
3505
+ };
3506
+ interface PolicyEditExpectedGain {
3507
+ /** Metric this edit is expected to move, e.g. `holdout.composite`. */
3508
+ metric: string;
3509
+ direction: PolicyEditGainDirection;
3510
+ /** Positive magnitude in the metric's native units. */
3511
+ amount: number;
3512
+ unit?: PolicyEditGainUnit;
3513
+ rationale?: string;
3514
+ }
3515
+ interface PolicyEditSource {
3516
+ findingIds: string[];
3517
+ analystIds: string[];
3518
+ evidenceRefs: EvidenceRef[];
3519
+ /** Mirrors `AnalystFinding.derived_from_judge`; admission rejects it. */
3520
+ derivedFromJudge?: boolean;
3521
+ }
3522
+ interface PolicyEdit {
3523
+ schemaVersion: PolicyEditSchemaVersion;
3524
+ editId: string;
3525
+ axis: PolicyEditAxis;
3526
+ target: PolicyEditTarget;
3527
+ change: PolicyEditChange;
3528
+ claim: string;
3529
+ expectedGain: PolicyEditExpectedGain;
3530
+ confidence: number;
3531
+ risk: PolicyEditRisk;
3532
+ source: PolicyEditSource;
3533
+ rationale?: string;
3534
+ validationPlan?: string;
3535
+ metadata?: Record<string, unknown>;
3536
+ }
3537
+ declare const POLICY_EDIT_CANDIDATE_RECORD_SCHEMA: "tangle.policy-edit-candidate.v1";
3538
+ /** JSON-safe attribution carried with a measured candidate and its scores. */
3539
+ interface PolicyEditCandidateRecord {
3540
+ schema: typeof POLICY_EDIT_CANDIDATE_RECORD_SCHEMA;
3541
+ policyEdit: PolicyEdit;
3542
+ }
3543
+
3544
+ /**
3545
+ * Pass A substrate types — `runCampaign` is the one primitive every
3546
+ * eval flow composes from. Three contracts in this file:
3547
+ *
3548
+ * - `Scenario` input set
3549
+ * - `DispatchFn` how to run one scenario → artifact
3550
+ * - `CampaignResult` defined output schema (the contract downstream tools depend on)
3551
+ *
3552
+ * Three more lifted from earlier substrate work (re-exported):
3553
+ *
3554
+ * - `JudgeConfig` pluggable dimensional scorer (0.38)
3555
+ * - `Mutator` optimization-loop surface mutator
3556
+ * - `Gate` promotion gate (`HeldOutGate` and friends adapt to this)
3557
+ *
3558
+ * No new architecture vs 0.38 — Pass A formalizes the shapes so consumers
3559
+ * can build dashboards / CI gates / regression diffs against a stable schema.
3560
+ */
3561
+
3562
+ /** Stable identifier + kind tag for any scenario. Consumers
3563
+ * extend with their per-domain payload (persona, task, requirement, ...). */
3564
+ interface Scenario {
3565
+ id: string;
3566
+ kind: string;
3567
+ tags?: string[];
3568
+ }
3569
+ /** The canonical judge verdict shape — one declaration, shared by campaign
3570
+ * judges and the multishot judge runner (which re-exports this type).
3571
+ *
3572
+ * Scale is PRODUCER-DEFINED: campaign convention is [0,1]; the legacy
3573
+ * multishot runner emits 0-10. Cross-scale comparison must go through
3574
+ * `detectScale` (src/campaign/gates/statistical-heldout.ts, used by
3575
+ * promotion-policy) — never renormalize a producer's values in place, as
3576
+ * downstream thresholds (`composite >= 5` in multishot/matrix.ts, live-soak
3577
+ * `>= 7` gates) key on the producer's native scale. */
3578
+ interface JudgeScore {
3579
+ dimensions: Record<string, number>;
3580
+ composite: number;
3581
+ notes: string;
3582
+ /** Provider metadata for display and diagnostics; accounting uses CostLedger receipts. */
3583
+ llmCall?: LlmCallMetadata;
3584
+ /** Set when the judge itself failed (call error, unparseable output).
3585
+ * `composite`/`dimensions` carry no signal — aggregators MUST exclude
3586
+ * failed scores from means instead of folding them into zeros. */
3587
+ failed?: true;
3588
+ /** Ensemble extras (populated by `ensembleJudge`): max per-dimension
3589
+ * spread across surviving judges — the inter-rater signal. */
3590
+ maxDisagreement?: number;
3591
+ /** Ensemble extras: judge identities whose verdict failed. */
3592
+ failedJudges?: string[];
3593
+ /** Ensemble extras: each surviving judge's per-dimension scores. */
3594
+ perJudge?: Record<string, Record<string, number>>;
3595
+ }
3596
+ /** Five-valued verdict taxonomy (MOSS-paper alignment). */
3597
+ type GateDecision = 'ship' | 'hold' | 'need_more_work' | 'model_ceiling' | 'arch_ceiling';
3598
+ interface GateResult {
3599
+ decision: GateDecision;
3600
+ reasons: string[];
3601
+ contributingGates: Array<{
3602
+ name: string;
3603
+ passed: boolean;
3604
+ detail: unknown;
3605
+ }>;
3606
+ delta?: number;
3607
+ }
3608
+ /** Token usage accumulated for a cell. Aliased to the canonical `RunTokenUsage`
3609
+ * (run-record.ts, same package) so a cell maps onto a `RunRecord` for the
3610
+ * backend-integrity guard with ONE source of truth — a field added to
3611
+ * `RunTokenUsage` is a compile error here, not a silent drift. */
3612
+ type CampaignTokenUsage = RunTokenUsage;
3613
+ interface CampaignCellResult<TArtifact> {
3614
+ /** Manifest that produced this cell. Resumability refuses to reuse a cell
3615
+ * whose manifest differs from the current run. */
3616
+ manifestHash?: string;
3617
+ cellId: string;
3618
+ scenarioId: string;
3619
+ rep: number;
3620
+ generation?: number;
3621
+ artifact: TArtifact;
3622
+ judgeScores: Record<string, JudgeScore>;
3623
+ costUsd: number;
3624
+ /** True when at least one priced receipt used the model table instead of a provider bill. */
3625
+ costEstimated?: boolean;
3626
+ /** Exact durable receipts required to reuse this cached result. */
3627
+ costCallIds?: string[];
3628
+ /** Agent-call token usage committed by `ctx.cost.runPaidCall`.
3629
+ * `{ input: 0, output: 0 }` when no paid agent call was recorded. */
3630
+ tokenUsage: CampaignTokenUsage;
3631
+ /** Concrete model from the latest committed agent receipt. Consumed by
3632
+ * `buildRunRecord` to pin the model when the declared profile uses a
3633
+ * runtime-resolved sentinel. */
3634
+ resolvedModel?: string;
3635
+ durationMs: number;
3636
+ seed: number;
3637
+ cached: boolean;
3638
+ error?: string;
3639
+ }
3640
+ interface JudgeAggregate {
3641
+ mean: number;
3642
+ stdev: number;
3643
+ ci95: [number, number];
3644
+ n: number;
3645
+ }
3646
+ interface ScenarioAggregate {
3647
+ meanComposite: number;
3648
+ ci95: [number, number];
3649
+ n: number;
3650
+ }
3651
+ interface GenerationRecord {
3652
+ generationIndex: number;
3653
+ candidates: GenerationCandidate[];
3654
+ promoted: string[];
3655
+ }
3656
+ /** One scored candidate surface in a generation. `dimensions` + `scenarios`
3657
+ * let a reflective proposer ground its next proposal on WHICH
3658
+ * dimensions the candidate is weakest on and WHICH scenarios it best/worst
3659
+ * handled — the evidence a blind `Mutator` cannot see. */
3660
+ interface GenerationCandidate {
3661
+ surfaceHash: string;
3662
+ composite: number;
3663
+ ci95: [number, number];
3664
+ /** Exact surface this candidate mutated. */
3665
+ parentSurfaceHash?: string;
3666
+ /** Measured search-split composite of the exact parent surface. */
3667
+ parentComposite?: number;
3668
+ /** Candidate composite minus its parent's composite. Present only when the
3669
+ * candidate completed the designed denominator. */
3670
+ observedDeltaFromParent?: number;
3671
+ /** Whether this candidate had a scorable result for every designed campaign
3672
+ * cell and was therefore eligible for ranking, promotion, and Pareto
3673
+ * selection. Older externally-authored records may omit this field; loop
3674
+ * records always populate it. */
3675
+ eligibleForPromotion?: boolean;
3676
+ /** Exact denominator receipt for selection eligibility. Scores stay
3677
+ * descriptive: an incomplete candidate is retained with its observed score
3678
+ * and errors instead of receiving an invented penalty. */
3679
+ coverage?: {
3680
+ expectedCells: number;
3681
+ scorableCells: number;
3682
+ unscorableCells: Array<{
3683
+ cellId: string;
3684
+ reason: string;
3685
+ }>;
3686
+ };
3687
+ /** Mean score per judge dimension across all cells (scenarios × reps ×
3688
+ * judges that reported the dimension). */
3689
+ dimensions: Record<string, number>;
3690
+ /** Per-scenario composite (mean over reps + judges), plus the judge's
3691
+ * free-form `notes` for that scenario — the "why it scored low" evidence a
3692
+ * reflective proposer grounds its next edit on. Keep `notes` GENERALIZABLE
3693
+ * (which checks/lines/dimensions failed and how), NOT case-specific ground
3694
+ * truth: leaking expected answers into the prompt is memorization, and the
3695
+ * held-out gate would reject it anyway. */
3696
+ scenarios: Array<{
3697
+ scenarioId: string;
3698
+ composite: number;
3699
+ notes?: string;
3700
+ }>;
3701
+ /** Proposer-supplied short label for the change. Present when the proposer
3702
+ * returned a `ProposedCandidate`; absent for bare-surface mutators. */
3703
+ label?: string;
3704
+ /** Proposer-supplied rationale — WHY this candidate was proposed. The
3705
+ * "because rationale Z" the audit requires to survive to the result.
3706
+ * Present when the proposer returned a `ProposedCandidate`. */
3707
+ rationale?: string;
3708
+ /** Exact structured cause threaded from the proposer, when available. */
3709
+ candidateRecord?: PolicyEditCandidateRecord;
3710
+ }
3711
+ interface CampaignAggregates {
3712
+ byJudge: Record<string, JudgeAggregate>;
3713
+ byScenario: Record<string, ScenarioAggregate>;
3714
+ /** Canonical campaign accounting, including worker and judge calls. */
3715
+ cost: CostLedgerSummary;
3716
+ /** Compatibility alias of `cost.totalCostUsd`. */
3717
+ totalCostUsd: number;
3718
+ cellsExecuted: number;
3719
+ cellsSkipped: number;
3720
+ cellsCached: number;
3721
+ cellsFailed: number;
3722
+ }
3723
+ interface CampaignResult<TArtifact = unknown, TScenario extends Scenario = Scenario> {
3724
+ /** sha256(scenarios, judges, dispatch source ref, optimizer config, seed). Stable identity for reruns. */
3725
+ manifestHash: string;
3726
+ seed: number;
3727
+ startedAt: string;
3728
+ endedAt: string;
3729
+ durationMs: number;
3730
+ cells: Array<CampaignCellResult<TArtifact>>;
3731
+ aggregates: CampaignAggregates;
3732
+ optimization?: {
3733
+ generations: GenerationRecord[];
3734
+ winnerSurfaceHash?: string;
3735
+ };
3736
+ gate?: GateResult;
3737
+ prUrl?: string;
3738
+ runDir: string;
3739
+ artifactsByPath: Record<string, string>;
3740
+ /** Substrate strips the input scenarios to id+kind for the result manifest;
3741
+ * consumers needing full payload look it up via the original input. The
3742
+ * type parameter `TScenario` is propagated for downstream consumers that
3743
+ * want narrowed types when extending `CampaignResult`. */
3744
+ scenarios: Array<Pick<TScenario, 'id' | 'kind'>>;
3745
+ }
3746
+
1416
3747
  /**
1417
3748
  * Adapters: convert measurement outputs into the canonical `RunRecord[]`
1418
3749
  * artifact that `replayCache`, `pairedEvalueSequence`, and
@@ -1748,4 +4079,4 @@ interface BuildPairwiseFromCampaignInput {
1748
4079
  }
1749
4080
  declare function buildPairwiseFromCampaign(input: BuildPairwiseFromCampaignInput): PairwiseOutcome[];
1750
4081
 
1751
- export { ABSENT_CATEGORY, type AdaptationCurve, type AdaptationPoint, type AdaptationRunner, type AdapterContext, type BehaviorFeatures, type BradleyTerryFit, type BradleyTerryRating, type BuildPairwiseFromCampaignInput, type CellObservation, type CompareCurvesResult, type ComputeBestOfNOptions, type ComputeBestOfNResult, type ComputeCurve, type ComputeCurveBudget, type ComputeCurvePoint, type ContaminationProbeInput, type ContaminationProbeOptions, type ContaminationProbeReport, type CorpusAppendResult, type CorpusRecord, type CurriculumAllocation, DEFAULT_MIN_N_PER_FEATURE, DEFAULT_QUANTILE_BUCKETS, type DatasetFormat, type DetectRewardHackingInput, type DpoExportRow, type DpoLookups, type EasyModeOptions, type EasyModeReport, type EloOptions, type ExtractPreferencesOptions, type ExtractStepRewardsOptions, type FeatureDivergence, type FeatureShift, type FidelityReport, type FidelityVerdict, type GrpoExportRow, type GrpoLookups, type HarvestOptions, OutcomeStore, type PairwiseOutcome, type ParetoPointInput, PredictiveValidityResearcher, type PredictiveValidityResearcherOptions, type PreferenceExtractionReport, type PreferenceStrategy, type PreferenceTriple, type PrmExportRow, type PrmLookups, type PrmTrainingTriple, REPRESENTATIVE_MIN_FIDELITY, type RLCampaignResult, type RewardHackingFinding, type RewardHackingReport, type RewardHackingSignal, type RewardKind, type RewardStats, type RlDatasetBundle, type RlDatasetConfig, type RlDatasetManifest, type RlDatasetStats, type RunAdaptationCurveOptions, type RunComputeCurveOptions, type RunRLCampaignOptions, type RunwiseStepSummary, type ScenarioPerturbation, type ScenarioPerturbationKind, type SelfConsistencyOptions, type SelfConsistencyResult, type SftExportRow, type SftLookups, type SimFidelityOptions, type StepReward, type StepRewardJsonlRow, type StepScorer, type ThompsonCurriculumOptions, type VarianceCurriculumOptions, type VerifiableReward, type VerifiableRewardExtractionOptions, type VerifiableRewardSource, appendToCorpus, applyEloUpdate, bestOfN, bucketLabel, buildDatasetFromCorpus, buildPairwiseFromCampaign, buildRlDataset, campaignToRunRecords, compareAdaptationCurves, datasheetToMarkdown, defaultBehaviorFeatures, detectRewardHacking, easyModeCheck, extractPreferences, extractStepRewards, extractVerifiableReward, extractVerifiableRewardsFromRecords, filterDeterministicallyRewarded, firstPassK, fitBradleyTerry, injectIrrelevantClause, jsDivergence, observationsFromRunRecords, paretoFrontier, prmTrainingPairs, quantileEdges, readCorpus, renameVariables, runAdaptationCurve, runComputeCurve, runContaminationProbe, runRLCampaign, runwiseStepRewardSummary, selfConsistency, shuffleOrder, simFidelityReport, stepRewardsToJsonl, thompsonCurriculum, toAnthropicFormat, toDpoJsonl, toDpoRows, toGrpoJsonl, toGrpoRows, toPrmJsonl, toPrmRows, toSftJsonl, toSftRows, toTRLFormat, varianceBasedCurriculum, verificationReportToRunRecord };
4082
+ export { ABSENT_CATEGORY, type AdaptationCurve, type AdaptationPoint, type AdaptationRunner, type AdapterContext, type AdversarialMutation, type BehaviorFeatures, type BradleyTerryFit, type BradleyTerryRating, type BuildPairwiseFromCampaignInput, type CellObservation, type CompareCurvesResult, type ComputeBestOfNOptions, type ComputeBestOfNResult, type ComputeCurve, type ComputeCurveBudget, type ComputeCurvePoint, type ContaminationProbeInput, type ContaminationProbeOptions, type ContaminationProbeReport, type CorpusAppendResult, type CorpusRecord, type CurriculumAllocation, DEFAULT_MIN_N_PER_FEATURE, DEFAULT_QUANTILE_BUCKETS, type DatasetFormat, type DeploymentOutcome, type DetectRewardHackingInput, type DpoExportRow, type DpoLookups, type EasyModeOptions, type EasyModeReport, type EloOptions, type ExtractPreferencesOptions, type ExtractStepRewardsOptions, type FeatureDivergence, type FeatureShift, type FidelityReport, type FidelityVerdict, FileSystemOutcomeStore, type FileSystemOutcomeStoreOptions, type GrpoExportRow, type GrpoLookups, type HarvestOptions, InMemoryOutcomeStore, type OffPolicyEstimate, type OffPolicyOptions, type OffPolicyTrajectory, type OutcomeStore, type PairwiseOutcome, type ParetoPointInput, PredictiveValidityResearcher, type PredictiveValidityResearcherOptions, type PreferenceExtractionReport, type PreferenceStrategy, type PreferenceTriple, type PrmExportRow, type PrmLookups, type PrmTrainingTriple, REPRESENTATIVE_MIN_FIDELITY, type RLCampaignResult, type RewardHackingFinding, type RewardHackingReport, type RewardHackingSignal, type RewardKind, type RewardStats, type RlDatasetBundle, type RlDatasetConfig, type RlDatasetManifest, type RlDatasetStats, type RunAdaptationCurveOptions, type RunComputeCurveOptions, type RunRLCampaignOptions, type RunwiseStepSummary, type ScenarioPerturbation, type ScenarioPerturbationKind, type SelfConsistencyOptions, type SelfConsistencyResult, type SftExportRow, type SftLookups, type SimFidelityOptions, type StepReward, type StepRewardJsonlRow, type StepScorer, type ThompsonCurriculumOptions, type VarianceCurriculumOptions, type VerifiableReward, type VerifiableRewardExtractionOptions, type VerifiableRewardSource, appendToCorpus, applyEloUpdate, bestOfN, bucketLabel, buildDatasetFromCorpus, buildPairwiseFromCampaign, buildRlDataset, campaignToRunRecords, compareAdaptationCurves, datasheetToMarkdown, defaultBehaviorFeatures, detectRewardHacking, doublyRobust, easyModeCheck, extractPreferences, extractStepRewards, extractVerifiableReward, extractVerifiableRewardsFromRecords, filterDeterministicallyRewarded, firstPassK, fitBradleyTerry, injectIrrelevantClause, inverseProbabilityWeighting, jsDivergence, observationsFromRunRecords, offPolicyEstimateAll, paretoFrontier, prmTrainingPairs, quantileEdges, readCorpus, renameVariables, runAdaptationCurve, runComputeCurve, runContaminationProbe, runEvalCampaign, runRLCampaign, runwiseStepRewardSummary, selfConsistency, selfNormalizedImportanceWeighting, shuffleOrder, simFidelityReport, stepRewardsToJsonl, thompsonCurriculum, toAnthropicFormat, toDpoJsonl, toDpoRows, toGrpoJsonl, toGrpoRows, toPrmJsonl, toPrmRows, toSftJsonl, toSftRows, toTRLFormat, varianceBasedCurriculum, verificationReportToRunRecord };