@tangle-network/agent-eval 0.117.1 → 0.118.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/analyst/index.d.ts +2772 -21
- package/dist/analyst/index.js +7 -6
- package/dist/analyst/index.js.map +1 -1
- package/dist/belief-state/index.d.ts +706 -10
- package/dist/belief-state/index.js +2 -1
- package/dist/belief-state/index.js.map +1 -1
- package/dist/benchmarks/index.d.ts +958 -14
- package/dist/benchmarks/index.js +12 -10
- package/dist/builder-eval/index.d.ts +449 -4
- package/dist/builder-eval/index.js +4 -3
- package/dist/builder-eval/index.js.map +1 -1
- package/dist/campaign/index.d.ts +4275 -73
- package/dist/campaign/index.js +12 -10
- package/dist/{chunk-VF3XSYTI.js → chunk-33JA4TFA.js} +6 -6
- package/dist/{chunk-4JLWXDYA.js → chunk-3EHHMC6E.js} +2 -2
- package/dist/{chunk-CCZIVI3F.js → chunk-BFW56GTT.js} +2 -2
- package/dist/{chunk-YZPO4UHR.js → chunk-FTUMG2U7.js} +124 -149
- package/dist/chunk-FTUMG2U7.js.map +1 -0
- package/dist/{chunk-E4BUPP7Z.js → chunk-HKUCJ437.js} +38 -66
- package/dist/chunk-HKUCJ437.js.map +1 -0
- package/dist/{chunk-HZHNRYHK.js → chunk-K6N6XJJX.js} +2 -2
- package/dist/chunk-KSDQVPLR.js +286 -0
- package/dist/chunk-KSDQVPLR.js.map +1 -0
- package/dist/chunk-MA6HLL3S.js +65 -0
- package/dist/chunk-MA6HLL3S.js.map +1 -0
- package/dist/{chunk-MGEHEHSN.js → chunk-OIIMMLRB.js} +11 -11
- package/dist/{chunk-DXZRATT5.js → chunk-OYZAPX5G.js} +3 -3
- package/dist/chunk-PXE2VKMX.js +140 -0
- package/dist/chunk-PXE2VKMX.js.map +1 -0
- package/dist/{chunk-JSJZ4PJ6.js → chunk-Q442S5AS.js} +17 -17
- package/dist/{chunk-ODVOOEWQ.js → chunk-QBRSJK47.js} +2 -2
- package/dist/{chunk-S2F4J57L.js → chunk-QKEGNI5B.js} +77 -32
- package/dist/chunk-QKEGNI5B.js.map +1 -0
- package/dist/{chunk-5UF54T55.js → chunk-S3UZOQ5Y.js} +34 -7
- package/dist/chunk-S3UZOQ5Y.js.map +1 -0
- package/dist/{chunk-FQNLDL4D.js → chunk-SVH2ANFD.js} +136 -4
- package/dist/chunk-SVH2ANFD.js.map +1 -0
- package/dist/{chunk-GQCZRZ7L.js → chunk-U5CHZ5M3.js} +9 -9
- package/dist/{chunk-TVVP3ZZQ.js → chunk-VQMK5FMP.js} +2 -1
- package/dist/{chunk-TVVP3ZZQ.js.map → chunk-VQMK5FMP.js.map} +1 -1
- package/dist/chunk-WDHBCA3M.js +31 -0
- package/dist/chunk-WDHBCA3M.js.map +1 -0
- package/dist/{chunk-HQPHZGL6.js → chunk-YLMUS4MM.js} +9 -9
- package/dist/{chunk-LQUTGLOZ.js → chunk-ZET2UAYW.js} +15 -65
- package/dist/chunk-ZET2UAYW.js.map +1 -0
- package/dist/contract/index.d.ts +4012 -38
- package/dist/contract/index.js +56 -29
- package/dist/contract/index.js.map +1 -1
- package/dist/control.d.ts +1013 -9
- package/dist/control.js +4 -3
- package/dist/fuzz.d.ts +194 -4
- package/dist/fuzz.js +3 -3
- package/dist/hosted/index.d.ts +498 -17
- package/dist/index.d.ts +10992 -1288
- package/dist/index.js +101 -80
- package/dist/index.js.map +1 -1
- package/dist/matrix/index.d.ts +139 -4
- package/dist/meta-eval/index.d.ts +862 -15
- package/dist/meta-eval/index.js +2 -1
- package/dist/meta-eval/index.js.map +1 -1
- package/dist/multishot/index.d.ts +214 -14
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.d.ts +392 -7
- package/dist/pipelines/index.js +5 -3
- package/dist/pipelines/index.js.map +1 -1
- package/dist/reporting.d.ts +1277 -17
- package/dist/rl.d.ts +2359 -28
- package/dist/rl.js +6 -5
- package/dist/rl.js.map +1 -1
- package/dist/storyboard/index.d.ts +86 -1
- package/dist/trace-attributes.d.ts +16 -0
- package/dist/trace-attributes.js +32 -0
- package/dist/trace-attributes.js.map +1 -0
- package/dist/traces.d.ts +1978 -697
- package/dist/traces.js +53 -32
- package/dist/wire/index.d.ts +655 -9
- package/docs/insight-report.md +44 -0
- package/package.json +9 -3
- package/dist/adversarial-B7loGVVX.d.ts +0 -19
- package/dist/analyst-C8HHvfJp.d.ts +0 -88
- package/dist/analyze-runs--2x39HZ7.d.ts +0 -81
- package/dist/baseline-DKq3gJpP.d.ts +0 -141
- package/dist/calibration-C8MTS7cw.d.ts +0 -101
- package/dist/chunk-5UF54T55.js.map +0 -1
- package/dist/chunk-E4BUPP7Z.js.map +0 -1
- package/dist/chunk-FQNLDL4D.js.map +0 -1
- package/dist/chunk-LQUTGLOZ.js.map +0 -1
- package/dist/chunk-S2F4J57L.js.map +0 -1
- package/dist/chunk-YZPO4UHR.js.map +0 -1
- package/dist/code-agent-session-CjZsVd19.d.ts +0 -87
- package/dist/control-6vuGfmDH.d.ts +0 -258
- package/dist/cost-ledger-DWy3XdJc.d.ts +0 -183
- package/dist/dataset-NENEzRgk.d.ts +0 -115
- package/dist/default-registry-DaK8b3fv.d.ts +0 -155
- package/dist/emitter-CjD7vUwv.d.ts +0 -122
- package/dist/errors-oeQrLqXC.d.ts +0 -74
- package/dist/failure-cluster-DOAcSJ87.d.ts +0 -76
- package/dist/feedback-trajectory-BUnM58xL.d.ts +0 -348
- package/dist/gepa-eESocoDi.d.ts +0 -642
- package/dist/index-PdX4VnPA.d.ts +0 -423
- package/dist/insight-report-DY4nDW9Q.d.ts +0 -310
- package/dist/integrity-DqlBiLyK.d.ts +0 -81
- package/dist/judge-calibration-7C-IDmKr.d.ts +0 -145
- package/dist/kind-factory-ClZmO25A.d.ts +0 -171
- package/dist/llm-client-qoDd18Qz.d.ts +0 -289
- package/dist/multi-layer-verifier-BsqKuLyN.d.ts +0 -150
- package/dist/off-policy-DiwuKKg7.d.ts +0 -132
- package/dist/outcome-store-rnXLEqSn.d.ts +0 -63
- package/dist/policy-edit-wG9uFEFm.d.ts +0 -455
- package/dist/pre-registration-BWQhJ3vz.d.ts +0 -761
- package/dist/provenance-DpjwyseI.d.ts +0 -541
- package/dist/query-CF7PG61p.d.ts +0 -35
- package/dist/raw-provider-sink-C46HDghv.d.ts +0 -132
- package/dist/release-report-C8G2i5Xi.d.ts +0 -236
- package/dist/researcher-C8XyxQsu.d.ts +0 -387
- package/dist/rubric-predictive-validity-p49lLVrE.d.ts +0 -105
- package/dist/run-record-BDH49H2E.d.ts +0 -360
- package/dist/runtime-trajectory-DGBIUt4B.d.ts +0 -49
- package/dist/schema-B3Q3l9Z_.d.ts +0 -201
- package/dist/semantic-concept-judge-CXnPEJbf.d.ts +0 -723
- package/dist/sequential-5iSVfzl2.d.ts +0 -139
- package/dist/series-convergence-D5OWMBg6.d.ts +0 -33
- package/dist/statistics-KUnG73jH.d.ts +0 -494
- package/dist/storage-DrX3v_5B.d.ts +0 -50
- package/dist/store-C1YxJDEK.d.ts +0 -248
- package/dist/store-DGqD0Pyo.d.ts +0 -116
- package/dist/summary-report-C5bKFfm-.d.ts +0 -445
- package/dist/test-graded-scenario-B0ybnPY7.d.ts +0 -166
- package/dist/types-BSw1rOUB.d.ts +0 -634
- package/dist/types-BUxNaJ8c.d.ts +0 -108
- package/dist/types-BkfcQnxV.d.ts +0 -313
- package/dist/verdict-C9MlYujm.d.ts +0 -35
- /package/dist/{chunk-VF3XSYTI.js.map → chunk-33JA4TFA.js.map} +0 -0
- /package/dist/{chunk-4JLWXDYA.js.map → chunk-3EHHMC6E.js.map} +0 -0
- /package/dist/{chunk-CCZIVI3F.js.map → chunk-BFW56GTT.js.map} +0 -0
- /package/dist/{chunk-HZHNRYHK.js.map → chunk-K6N6XJJX.js.map} +0 -0
- /package/dist/{chunk-MGEHEHSN.js.map → chunk-OIIMMLRB.js.map} +0 -0
- /package/dist/{chunk-DXZRATT5.js.map → chunk-OYZAPX5G.js.map} +0 -0
- /package/dist/{chunk-JSJZ4PJ6.js.map → chunk-Q442S5AS.js.map} +0 -0
- /package/dist/{chunk-ODVOOEWQ.js.map → chunk-QBRSJK47.js.map} +0 -0
- /package/dist/{chunk-GQCZRZ7L.js.map → chunk-U5CHZ5M3.js.map} +0 -0
- /package/dist/{chunk-HQPHZGL6.js.map → chunk-YLMUS4MM.js.map} +0 -0
package/dist/rl.d.ts
CHANGED
|
@@ -1,30 +1,446 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 };
|