@tangle-network/agent-eval 0.123.7 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -4
- package/dist/analyst/index.d.ts +7 -0
- package/dist/analyst/index.js +4 -4
- package/dist/benchmarks/index.d.ts +2 -0
- package/dist/benchmarks/index.js +4 -4
- package/dist/campaign/index.d.ts +13 -0
- package/dist/campaign/index.js +4 -4
- package/dist/chunk-4Y7AAATF.js +1151 -0
- package/dist/chunk-4Y7AAATF.js.map +1 -0
- package/dist/{chunk-VPDOSN3L.js → chunk-5PVZVCZB.js} +7 -5
- package/dist/chunk-5PVZVCZB.js.map +1 -0
- package/dist/{chunk-WP3GEXKI.js → chunk-A6GT67HT.js} +3 -3
- package/dist/{chunk-MHPEGJHC.js → chunk-DT7OXY3C.js} +2 -2
- package/dist/{chunk-MK3Q6GCL.js → chunk-EQUK3RFS.js} +14 -5
- package/dist/chunk-EQUK3RFS.js.map +1 -0
- package/dist/{chunk-N7ZS6PEF.js → chunk-GID26AN4.js} +2 -2
- package/dist/{chunk-4KO6D2BO.js → chunk-HM6V7F3M.js} +2 -2
- package/dist/chunk-IPYXE555.js +594 -0
- package/dist/chunk-IPYXE555.js.map +1 -0
- package/dist/chunk-MAX3TN3C.js +249 -0
- package/dist/chunk-MAX3TN3C.js.map +1 -0
- package/dist/chunk-MGGFVCJ7.js +288 -0
- package/dist/chunk-MGGFVCJ7.js.map +1 -0
- package/dist/{chunk-KKPPFIDS.js → chunk-PMITBABE.js} +7 -5
- package/dist/{chunk-KKPPFIDS.js.map → chunk-PMITBABE.js.map} +1 -1
- package/dist/{chunk-J3LHTAAB.js → chunk-QOTFXW5L.js} +1 -47
- package/dist/chunk-QOTFXW5L.js.map +1 -0
- package/dist/chunk-R7ZRE2KV.js +138 -0
- package/dist/chunk-R7ZRE2KV.js.map +1 -0
- package/dist/chunk-RZTMDUO7.js +49 -0
- package/dist/chunk-RZTMDUO7.js.map +1 -0
- package/dist/{chunk-QNVBALEX.js → chunk-W5B3ZGP3.js} +3 -3
- package/dist/cli.js +13 -2
- package/dist/cli.js.map +1 -1
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +4 -4
- package/dist/index.d.ts +701 -82
- package/dist/index.js +57 -121
- package/dist/index.js.map +1 -1
- package/dist/multishot/index.d.ts +2 -0
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.js +4 -2
- package/dist/pipelines/index.js.map +1 -1
- package/dist/rl.d.ts +5 -0
- package/dist/rl.js +2 -2
- package/dist/rollout/index.d.ts +1053 -0
- package/dist/rollout/index.js +109 -0
- package/dist/rollout/index.js.map +1 -0
- package/dist/supervisor-run/index.d.ts +705 -0
- package/dist/supervisor-run/index.js +52 -0
- package/dist/supervisor-run/index.js.map +1 -0
- package/dist/wire/index.d.ts +3 -0
- package/dist/wire/index.js +2 -2
- package/docs/rollout.md +48 -0
- package/package.json +11 -1
- package/dist/chunk-J3LHTAAB.js.map +0 -1
- package/dist/chunk-MK3Q6GCL.js.map +0 -1
- package/dist/chunk-VPDOSN3L.js.map +0 -1
- /package/dist/{chunk-WP3GEXKI.js.map → chunk-A6GT67HT.js.map} +0 -0
- /package/dist/{chunk-MHPEGJHC.js.map → chunk-DT7OXY3C.js.map} +0 -0
- /package/dist/{chunk-N7ZS6PEF.js.map → chunk-GID26AN4.js.map} +0 -0
- /package/dist/{chunk-4KO6D2BO.js.map → chunk-HM6V7F3M.js.map} +0 -0
- /package/dist/{chunk-QNVBALEX.js.map → chunk-W5B3ZGP3.js.map} +0 -0
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `tangle.rollout.v1` — THE canonical rollout serialization, owned by
|
|
5
|
+
* agent-eval. One JSONL line per agent invocation (a solo eval run, a
|
|
6
|
+
* supervisor episode, a worker session, a proposer shot, a judge call, an
|
|
7
|
+
* analyst pass), labeled with its task/split coordinates and a single
|
|
8
|
+
* scalar reward, carrying the FULL message transcript inline.
|
|
9
|
+
*
|
|
10
|
+
* This schema is the reconciliation of two prior producers:
|
|
11
|
+
* - agent-eval's RunRecord-joined rollout rows (PR #410): identity,
|
|
12
|
+
* provenance hashes, the realness gate travelling into the reward,
|
|
13
|
+
* trace-derived steps.
|
|
14
|
+
* - the bench rollout-ledger (agent-runtime PR #591): the wire shape —
|
|
15
|
+
* role, task.split/rep, parent_rollout_id, policy provenance, capture
|
|
16
|
+
* provenance, inline canonical chat-with-tools messages.
|
|
17
|
+
* Where the two conflicted, RunRecord-derived semantics won; the wire
|
|
18
|
+
* field names follow the ledger (snake_case). See `docs/rollout.md` for
|
|
19
|
+
* the field-by-field decision table.
|
|
20
|
+
*
|
|
21
|
+
* Messages are inlined — never referenced — because every harness store a
|
|
22
|
+
* rollout can be recovered from is mutable or garbage-collected. A line
|
|
23
|
+
* must stay a complete training/eval example on its own.
|
|
24
|
+
*
|
|
25
|
+
* `outcome.reward` is THE single scalar (null = no verdict exists — a
|
|
26
|
+
* labeled gap, never 0). `outcome.realness_gated` is the anti-Goodhart
|
|
27
|
+
* flag: a gated line must never export as a positive training example.
|
|
28
|
+
*/
|
|
29
|
+
declare const ROLLOUT_SCHEMA = "tangle.rollout.v1";
|
|
30
|
+
/** @deprecated alias kept for consumers of the pre-unification constant name. */
|
|
31
|
+
declare const ROLLOUT_FORMAT = "tangle.rollout.v1";
|
|
32
|
+
/** `agent` = a solo evaluation run (no multi-agent topology). */
|
|
33
|
+
type RolloutRole = 'agent' | 'supervisor' | 'worker' | 'proposer' | 'judge' | 'analyst';
|
|
34
|
+
declare const ROLLOUT_ROLES: readonly RolloutRole[];
|
|
35
|
+
/**
|
|
36
|
+
* Split vocabulary follows `RunRecord.splitTag` ('search' is the pool the
|
|
37
|
+
* optimizer may read — the trainable split), extended with the ledger's
|
|
38
|
+
* 'canary'. 'train' is a legacy alias for 'search' emitted by
|
|
39
|
+
* pre-unification ledgers; it validates and counts as trainable, but new
|
|
40
|
+
* producers must emit 'search'.
|
|
41
|
+
*/
|
|
42
|
+
type RolloutSplit = 'search' | 'dev' | 'holdout' | 'canary' | 'train';
|
|
43
|
+
declare const ROLLOUT_SPLITS: readonly RolloutSplit[];
|
|
44
|
+
/** Splits that may ship in training exports. Everything else is fail-closed excluded. */
|
|
45
|
+
declare const TRAINABLE_SPLITS: readonly RolloutSplit[];
|
|
46
|
+
declare function isTrainableSplit(split: RolloutSplit): boolean;
|
|
47
|
+
/** 'mint' = joined live from RunRecord + trace by `mintRolloutRows`. */
|
|
48
|
+
type RolloutCapture = 'mint' | 'settle-time' | 'backfill';
|
|
49
|
+
declare const ROLLOUT_CAPTURES: readonly RolloutCapture[];
|
|
50
|
+
type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
|
|
51
|
+
declare const CHAT_ROLES: readonly ChatRole[];
|
|
52
|
+
interface ChatToolCall {
|
|
53
|
+
id: string;
|
|
54
|
+
type: 'function';
|
|
55
|
+
function: {
|
|
56
|
+
name: string;
|
|
57
|
+
/** JSON-encoded argument object, exactly as the model emitted it. */
|
|
58
|
+
arguments: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
interface ChatMessage {
|
|
62
|
+
role: ChatRole;
|
|
63
|
+
content: string | null;
|
|
64
|
+
/** Reasoning/thinking channel where the harness captured it (full fidelity). */
|
|
65
|
+
reasoning_content?: string;
|
|
66
|
+
tool_calls?: ChatToolCall[];
|
|
67
|
+
/** Required on role:"tool" — the ChatToolCall this result answers. */
|
|
68
|
+
tool_call_id?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
}
|
|
71
|
+
interface ToolDef {
|
|
72
|
+
type: 'function';
|
|
73
|
+
function: {
|
|
74
|
+
name: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
parameters?: Record<string, unknown>;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Compact trace-span projection (llm/tool step) carried alongside the
|
|
81
|
+
* conversation when the line was minted from a trace. Optional: lines
|
|
82
|
+
* recovered from harness stores have no span structure.
|
|
83
|
+
*/
|
|
84
|
+
interface RolloutStep {
|
|
85
|
+
kind: string;
|
|
86
|
+
name: string;
|
|
87
|
+
/** llm: last-message summary · tool: stringified args. Scrubbed. */
|
|
88
|
+
input?: string;
|
|
89
|
+
/** llm: output text · tool: stringified result. Scrubbed. */
|
|
90
|
+
output?: string;
|
|
91
|
+
status?: 'ok' | 'error';
|
|
92
|
+
durationMs?: number;
|
|
93
|
+
}
|
|
94
|
+
interface RolloutTask {
|
|
95
|
+
/** Benchmark/suite id (e.g. "swe-bench-verified") or the experiment id. */
|
|
96
|
+
suite: string;
|
|
97
|
+
instance_id: string;
|
|
98
|
+
split: RolloutSplit;
|
|
99
|
+
/** Sampling seed the campaign pinned; null = not recorded. */
|
|
100
|
+
seed: number | null;
|
|
101
|
+
/** Replicate index (0-based). */
|
|
102
|
+
rep: number;
|
|
103
|
+
}
|
|
104
|
+
interface RolloutPolicy {
|
|
105
|
+
/** Harness that drove the invocation (e.g. "opencode", "claude", "pi-loops"). */
|
|
106
|
+
harness: string | null;
|
|
107
|
+
harness_version: string | null;
|
|
108
|
+
model: string | null;
|
|
109
|
+
provider: string | null;
|
|
110
|
+
/** Commit of the agent profile / candidate under evaluation. */
|
|
111
|
+
profile_commit: string | null;
|
|
112
|
+
/** sha256 of the effective prompt (post-steering), when recorded. */
|
|
113
|
+
prompt_hash?: string | null;
|
|
114
|
+
/** sha256 of the effective run config, when recorded. */
|
|
115
|
+
config_hash?: string | null;
|
|
116
|
+
/** Canonical agent-profile cell identity, when the run carries one. */
|
|
117
|
+
agent_profile_cell_id?: string | null;
|
|
118
|
+
/** Sampling params (temperature, top_p, max_tokens…); null = not recorded. */
|
|
119
|
+
sampling: Record<string, unknown> | null;
|
|
120
|
+
}
|
|
121
|
+
interface RolloutOutcome {
|
|
122
|
+
/**
|
|
123
|
+
* THE single scalar training signal — the official verdict.
|
|
124
|
+
* null = no verdict exists for this invocation (a labeled gap, never 0).
|
|
125
|
+
*/
|
|
126
|
+
reward: number | null;
|
|
127
|
+
/** Where the reward came from (judge id; "/inherited" = parent episode's). */
|
|
128
|
+
reward_source: string | null;
|
|
129
|
+
/** Raw judge verdict record, verbatim. */
|
|
130
|
+
verdict: unknown;
|
|
131
|
+
/** Everything that is NOT the scalar reward. */
|
|
132
|
+
metrics: Record<string, unknown>;
|
|
133
|
+
is_completed: boolean;
|
|
134
|
+
is_truncated: boolean;
|
|
135
|
+
error: string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Anti-Goodhart flag from `RunRecord.outcome.realness.gated`: the run
|
|
138
|
+
* faked its success signal. Reward is forced to 0 at mint time and the
|
|
139
|
+
* line never qualifies for SFT. Optional on the wire (absent = false)
|
|
140
|
+
* so pre-unification ledgers stay readable.
|
|
141
|
+
*/
|
|
142
|
+
realness_gated?: boolean;
|
|
143
|
+
}
|
|
144
|
+
interface RolloutCostBlock {
|
|
145
|
+
usd: number | null;
|
|
146
|
+
tokens_in: number | null;
|
|
147
|
+
tokens_out: number | null;
|
|
148
|
+
tokens_reasoning: number | null;
|
|
149
|
+
cache_read: number | null;
|
|
150
|
+
cache_write: number | null;
|
|
151
|
+
wall_s: number | null;
|
|
152
|
+
}
|
|
153
|
+
interface RolloutArtifacts {
|
|
154
|
+
patch_path: string | null;
|
|
155
|
+
run_dir: string | null;
|
|
156
|
+
/** Source-of-truth transcript pointer (session id / jsonl path) for audit. */
|
|
157
|
+
transcript_ref: string | null;
|
|
158
|
+
}
|
|
159
|
+
interface RolloutProvenance {
|
|
160
|
+
captured_at: string;
|
|
161
|
+
capture: RolloutCapture;
|
|
162
|
+
/** Present on gap lines: why `messages` could not be recovered. */
|
|
163
|
+
gap?: string;
|
|
164
|
+
}
|
|
165
|
+
interface RolloutLine {
|
|
166
|
+
schema: typeof ROLLOUT_SCHEMA;
|
|
167
|
+
rollout_id: string;
|
|
168
|
+
/** Spawning invocation within the same episode (worker → supervisor). */
|
|
169
|
+
parent_rollout_id: string | null;
|
|
170
|
+
run_id: string;
|
|
171
|
+
/** Logical experiment grouping from `RunRecord.experimentId`. Optional on
|
|
172
|
+
* the wire (pre-unification ledgers lack it); null = not recorded. */
|
|
173
|
+
experiment_id?: string | null;
|
|
174
|
+
/** Stable candidate identity from `RunRecord.candidateId`; null = not recorded. */
|
|
175
|
+
candidate_id?: string | null;
|
|
176
|
+
/** Improvement-loop generation (-1 = baseline); null = not an improvement loop. */
|
|
177
|
+
generation: number | null;
|
|
178
|
+
/** Improvement-loop candidate index (-1 = baseline); null = not an improvement loop. */
|
|
179
|
+
candidate_index: number | null;
|
|
180
|
+
role: RolloutRole;
|
|
181
|
+
task: RolloutTask;
|
|
182
|
+
policy: RolloutPolicy;
|
|
183
|
+
/** Full transcript, inline. [] = gap line (see provenance.gap). */
|
|
184
|
+
messages: ChatMessage[];
|
|
185
|
+
tool_defs: ToolDef[];
|
|
186
|
+
/** Trace-span projections, when minted from a trace. */
|
|
187
|
+
steps?: RolloutStep[];
|
|
188
|
+
outcome: RolloutOutcome;
|
|
189
|
+
cost: RolloutCostBlock;
|
|
190
|
+
artifacts: RolloutArtifacts;
|
|
191
|
+
provenance: RolloutProvenance;
|
|
192
|
+
}
|
|
193
|
+
declare function validateRolloutLine(value: unknown): string[];
|
|
194
|
+
declare function assertRolloutLine(value: unknown, context?: string): asserts value is RolloutLine;
|
|
195
|
+
declare function isRolloutLine(value: unknown): value is RolloutLine;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Pure exporters over `tangle.rollout.v1` lines → the training-data shapes
|
|
199
|
+
* the improvement loops feed:
|
|
200
|
+
* - SFT chat JSONL (clean trainable successes, {messages, metadata})
|
|
201
|
+
* - reward rows (every scored line, success or failure, with steps)
|
|
202
|
+
* - Prime Intellect verifiers RolloutOutput (prompt/completion split + reward)
|
|
203
|
+
* - OpenAI RFT items (prompt turns + verdict reference fields)
|
|
204
|
+
*
|
|
205
|
+
* All exporters are pure functions of the lines — filtering (never train on
|
|
206
|
+
* holdout, reward thresholds, the realness gate) happens HERE, on inline
|
|
207
|
+
* labels, no joins.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
interface SftExportOptions {
|
|
211
|
+
/** Export only lines with reward ≥ this (default 1 = clean successes only). */
|
|
212
|
+
minReward?: number;
|
|
213
|
+
}
|
|
214
|
+
interface SftRow {
|
|
215
|
+
messages: ChatMessage[];
|
|
216
|
+
metadata: {
|
|
217
|
+
rollout_id: string;
|
|
218
|
+
run_id: string;
|
|
219
|
+
candidate_id: string | null;
|
|
220
|
+
instance_id: string;
|
|
221
|
+
reward: number;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Supervised fine-tune rows: the completed conversation of each qualifying
|
|
226
|
+
* line. Fail-closed filters: trainable split only (never holdout/canary),
|
|
227
|
+
* reward ≥ minReward, realness-gated lines never qualify, gap lines carry
|
|
228
|
+
* no trainable content.
|
|
229
|
+
*/
|
|
230
|
+
declare function toSftRows(lines: RolloutLine[], options?: SftExportOptions): SftRow[];
|
|
231
|
+
interface RewardRow {
|
|
232
|
+
/** First user turn — the task prompt. */
|
|
233
|
+
prompt: string;
|
|
234
|
+
steps: RolloutStep[];
|
|
235
|
+
reward: number;
|
|
236
|
+
metadata: {
|
|
237
|
+
rollout_id: string;
|
|
238
|
+
run_id: string;
|
|
239
|
+
candidate_id: string | null;
|
|
240
|
+
instance_id: string;
|
|
241
|
+
split: RolloutSplit;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Reward-labeled rows: every line with a scalar reward, success or
|
|
246
|
+
* failure. Failures are signal here — only the realness-gate zeroing
|
|
247
|
+
* (applied at mint time) touches the reward, never filtering. Lines with
|
|
248
|
+
* no verdict (reward null) are excluded: an unlabeled example is a gap,
|
|
249
|
+
* not a zero.
|
|
250
|
+
*/
|
|
251
|
+
declare function toRewardRows(lines: RolloutLine[]): RewardRow[];
|
|
252
|
+
interface VerifiersTokenUsage {
|
|
253
|
+
input_tokens: number | null;
|
|
254
|
+
output_tokens: number | null;
|
|
255
|
+
reasoning_tokens: number | null;
|
|
256
|
+
cache_read_tokens: number | null;
|
|
257
|
+
cache_write_tokens: number | null;
|
|
258
|
+
}
|
|
259
|
+
interface VerifiersRolloutOutput {
|
|
260
|
+
/** Messages through the last turn BEFORE the first assistant turn. */
|
|
261
|
+
prompt: ChatMessage[];
|
|
262
|
+
/** The first assistant turn onward — what the policy produced. */
|
|
263
|
+
completion: ChatMessage[];
|
|
264
|
+
reward: number | null;
|
|
265
|
+
metrics: Record<string, unknown>;
|
|
266
|
+
tool_defs: ToolDef[];
|
|
267
|
+
token_usage: VerifiersTokenUsage;
|
|
268
|
+
info: {
|
|
269
|
+
task: RolloutLine['task'];
|
|
270
|
+
policy: RolloutLine['policy'];
|
|
271
|
+
rollout_id: string;
|
|
272
|
+
run_id: string;
|
|
273
|
+
experiment_id: string | null;
|
|
274
|
+
candidate_id: string | null;
|
|
275
|
+
generation: number | null;
|
|
276
|
+
candidate_index: number | null;
|
|
277
|
+
role: RolloutLine['role'];
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
declare function toVerifiersRolloutOutput(line: RolloutLine): VerifiersRolloutOutput;
|
|
281
|
+
declare function toVerifiersRolloutOutputs(lines: RolloutLine[]): VerifiersRolloutOutput[];
|
|
282
|
+
interface RftItem {
|
|
283
|
+
/** Prompt turns only — the graded completion is re-sampled during RFT. */
|
|
284
|
+
messages: ChatMessage[];
|
|
285
|
+
/** Verdict/label fields the grader references as item.reference.* */
|
|
286
|
+
reference: {
|
|
287
|
+
reward: number | null;
|
|
288
|
+
reward_source: string | null;
|
|
289
|
+
verdict: unknown;
|
|
290
|
+
instance_id: string;
|
|
291
|
+
suite: string;
|
|
292
|
+
split: RolloutSplit;
|
|
293
|
+
rollout_id: string;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
declare function toRftItem(line: RolloutLine): RftItem;
|
|
297
|
+
/** RFT needs a real prompt: lines whose transcript starts with prompt turns. */
|
|
298
|
+
declare function toRftItems(lines: RolloutLine[]): RftItem[];
|
|
299
|
+
declare function toJsonl(rows: ReadonlyArray<unknown>): string;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Rollout-ledger file API — append-only JSONL of validated `tangle.rollout.v1`
|
|
303
|
+
* lines. Writes validate BEFORE touching disk (a bad line never lands);
|
|
304
|
+
* reads validate line-by-line and fail loud with the line number, because a
|
|
305
|
+
* silently-skipped rollout is a corrupted dataset.
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/** Replace the ledger file with exactly `lines`. */
|
|
309
|
+
declare function writeRolloutLedger(path: string, lines: RolloutLine[]): Promise<void>;
|
|
310
|
+
/** Append `lines` to the ledger file (created if absent). */
|
|
311
|
+
declare function appendRolloutLines(path: string, lines: RolloutLine[]): Promise<void>;
|
|
312
|
+
/**
|
|
313
|
+
* Read and validate every line. Throws on the first malformed/invalid line
|
|
314
|
+
* (with its 1-based line number) — fail-closed, never a silent drop.
|
|
315
|
+
*/
|
|
316
|
+
declare function readRolloutLedger(path: string): Promise<RolloutLine[]>;
|
|
317
|
+
|
|
318
|
+
type AgentProfileCellSchemaVersion = 'agent-profile-cell/v1';
|
|
319
|
+
type AgentProfileDimensionValue = string | number | boolean | null;
|
|
320
|
+
interface AgentProfileSource {
|
|
321
|
+
/** Runtime/profile contract being fingerprinted, e.g. `agent-interface-profile`. */
|
|
322
|
+
kind: string;
|
|
323
|
+
/** sha256 over the canonical source profile object. */
|
|
324
|
+
hash: string;
|
|
325
|
+
}
|
|
326
|
+
interface AgentProfileHarness {
|
|
327
|
+
id: string;
|
|
328
|
+
version?: string;
|
|
329
|
+
hash?: string;
|
|
330
|
+
}
|
|
331
|
+
interface AgentProfileCell {
|
|
332
|
+
schemaVersion: AgentProfileCellSchemaVersion;
|
|
333
|
+
cellId: string;
|
|
334
|
+
profileId: string;
|
|
335
|
+
sourceProfile: AgentProfileSource;
|
|
336
|
+
harness?: AgentProfileHarness;
|
|
337
|
+
model?: string;
|
|
338
|
+
promptHash?: string;
|
|
339
|
+
dimensions?: Record<string, AgentProfileDimensionValue>;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
type RunStatus = 'running' | 'completed' | 'failed' | 'aborted';
|
|
343
|
+
interface BudgetSpec {
|
|
344
|
+
tokens?: number;
|
|
345
|
+
wallMs?: number;
|
|
346
|
+
calls?: number;
|
|
347
|
+
usd?: number;
|
|
348
|
+
}
|
|
349
|
+
interface RunOutcome$1 {
|
|
350
|
+
score?: number;
|
|
351
|
+
pass?: boolean;
|
|
352
|
+
failureClass?: FailureClass;
|
|
353
|
+
notes?: string;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Layer — optional classification in a nested build workflow.
|
|
357
|
+
* `builder`: the meta-agent editing a project (e.g. agent-builder Forge chat).
|
|
358
|
+
* `app-build`: sandbox harness that compiled + tested the generated scaffold.
|
|
359
|
+
* `app-runtime`: a run of the generated agent against a domain scenario.
|
|
360
|
+
* `meta`: any meta-eval (judge replay, correlation analysis).
|
|
361
|
+
*/
|
|
362
|
+
type RunLayer = 'builder' | 'app-build' | 'app-runtime' | 'meta' | 'custom';
|
|
363
|
+
interface Run {
|
|
364
|
+
runId: string;
|
|
365
|
+
/**
|
|
366
|
+
* Stable identifier of the scenario being executed.
|
|
367
|
+
*
|
|
368
|
+
* Always populated on the persisted Run — but `TraceEmitter.startRun` accepts
|
|
369
|
+
* input WITHOUT this field, substituting a sensible default
|
|
370
|
+
* (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) when the caller has no
|
|
371
|
+
* curated scenario to anchor to (runtime / operator / meta-eval runs). This
|
|
372
|
+
* keeps the persisted shape unambiguous for downstream filters + aggregations
|
|
373
|
+
* while removing the boilerplate of inventing placeholder ids at the call site.
|
|
374
|
+
*/
|
|
375
|
+
scenarioId: string;
|
|
376
|
+
variantId?: string;
|
|
377
|
+
datasetVersion?: string;
|
|
378
|
+
/** Git SHA of agent code at run time. */
|
|
379
|
+
codeSha?: string;
|
|
380
|
+
/** Hash of the prompt template + any system prompt. */
|
|
381
|
+
promptSha?: string;
|
|
382
|
+
/** Model id + date + system-prompt hash, concatenated. */
|
|
383
|
+
modelFingerprint?: string;
|
|
384
|
+
seed?: number;
|
|
385
|
+
/** Arbitrary environment markers (shell, docker version, tz). */
|
|
386
|
+
envFingerprint?: Record<string, string>;
|
|
387
|
+
/** Version of the redaction rules applied to this run. */
|
|
388
|
+
redactionVersion?: string;
|
|
389
|
+
/** Parent run in a nested build workflow. A builder run's children are
|
|
390
|
+
* app-build runs; those children are app-runtime runs. */
|
|
391
|
+
parentRunId?: string;
|
|
392
|
+
/** Stable project identifier — groups runs across chats + sessions. */
|
|
393
|
+
projectId?: string;
|
|
394
|
+
/** Chat/conversation identifier within a project. */
|
|
395
|
+
chatId?: string;
|
|
396
|
+
/** Layer classification — hint for aggregation; not enforced. */
|
|
397
|
+
layer?: RunLayer;
|
|
398
|
+
startedAt: number;
|
|
399
|
+
endedAt?: number;
|
|
400
|
+
status: RunStatus;
|
|
401
|
+
outcome?: RunOutcome$1;
|
|
402
|
+
budget?: BudgetSpec;
|
|
403
|
+
/** Free-form labels for downstream grouping. */
|
|
404
|
+
tags?: Record<string, string>;
|
|
405
|
+
}
|
|
406
|
+
type SpanKind = 'agent' | 'llm' | 'tool' | 'retrieval' | 'judge' | 'sandbox' | 'custom';
|
|
407
|
+
type SpanStatus = 'ok' | 'error';
|
|
408
|
+
interface SpanBase {
|
|
409
|
+
spanId: string;
|
|
410
|
+
parentSpanId?: string;
|
|
411
|
+
runId: string;
|
|
412
|
+
kind: SpanKind;
|
|
413
|
+
name: string;
|
|
414
|
+
startedAt: number;
|
|
415
|
+
endedAt?: number;
|
|
416
|
+
status?: SpanStatus;
|
|
417
|
+
error?: string;
|
|
418
|
+
/** Anything not covered by typed fields. Kept deliberately free-form. */
|
|
419
|
+
attributes?: Record<string, unknown>;
|
|
420
|
+
}
|
|
421
|
+
interface Message {
|
|
422
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
423
|
+
content: string;
|
|
424
|
+
tokens?: number;
|
|
425
|
+
/** Multi-modal content descriptors; blobs themselves live in Artifacts. */
|
|
426
|
+
images?: Array<{
|
|
427
|
+
artifactId?: string;
|
|
428
|
+
url?: string;
|
|
429
|
+
mime?: string;
|
|
430
|
+
}>;
|
|
431
|
+
}
|
|
432
|
+
interface LlmSpan extends SpanBase {
|
|
433
|
+
kind: 'llm';
|
|
434
|
+
model: string;
|
|
435
|
+
messages: Message[];
|
|
436
|
+
output?: string;
|
|
437
|
+
inputTokens?: number;
|
|
438
|
+
/** All generated tokens, including the reasoning subset when present. */
|
|
439
|
+
outputTokens?: number;
|
|
440
|
+
cachedTokens?: number;
|
|
441
|
+
cacheWriteTokens?: number;
|
|
442
|
+
/** Reasoning-token subset of `outputTokens`. */
|
|
443
|
+
reasoningTokens?: number;
|
|
444
|
+
costUsd?: number;
|
|
445
|
+
finishReason?: string;
|
|
446
|
+
}
|
|
447
|
+
interface ToolSpan extends SpanBase {
|
|
448
|
+
kind: 'tool';
|
|
449
|
+
toolName: string;
|
|
450
|
+
args: unknown;
|
|
451
|
+
/** False when the source observed the call but did not capture its arguments. */
|
|
452
|
+
argsCaptured?: boolean;
|
|
453
|
+
result?: unknown;
|
|
454
|
+
latencyMs?: number;
|
|
455
|
+
}
|
|
456
|
+
interface RetrievalSpan extends SpanBase {
|
|
457
|
+
kind: 'retrieval';
|
|
458
|
+
query: string;
|
|
459
|
+
hits: Array<{
|
|
460
|
+
docId: string;
|
|
461
|
+
score: number;
|
|
462
|
+
content?: string;
|
|
463
|
+
}>;
|
|
464
|
+
}
|
|
465
|
+
interface JudgeSpan extends SpanBase {
|
|
466
|
+
kind: 'judge';
|
|
467
|
+
judgeId: string;
|
|
468
|
+
/** Span this judgment applies to. */
|
|
469
|
+
targetSpanId: string;
|
|
470
|
+
dimension: string;
|
|
471
|
+
/** Numeric score (free-range; interpretation up to the judge). */
|
|
472
|
+
score: number;
|
|
473
|
+
rationale?: string;
|
|
474
|
+
evidence?: string;
|
|
475
|
+
}
|
|
476
|
+
interface SandboxSpan extends SpanBase {
|
|
477
|
+
kind: 'sandbox';
|
|
478
|
+
image?: string;
|
|
479
|
+
command?: string;
|
|
480
|
+
exitCode?: number;
|
|
481
|
+
testsTotal?: number;
|
|
482
|
+
testsPassed?: number;
|
|
483
|
+
stdoutHash?: string;
|
|
484
|
+
stderrHash?: string;
|
|
485
|
+
/** Duration in ms; the harness fills this explicitly (endedAt - startedAt may miss setup). */
|
|
486
|
+
wallMs?: number;
|
|
487
|
+
}
|
|
488
|
+
interface GenericSpan extends SpanBase {
|
|
489
|
+
kind: 'agent' | 'custom';
|
|
490
|
+
}
|
|
491
|
+
type Span = LlmSpan | ToolSpan | RetrievalSpan | JudgeSpan | SandboxSpan | GenericSpan;
|
|
492
|
+
type EventKind = 'log' | 'error' | 'budget_decrement' | 'budget_breach' | 'state_mutation' | 'policy_violation' | 'redaction_applied' | 'custom';
|
|
493
|
+
interface TraceEvent {
|
|
494
|
+
eventId: string;
|
|
495
|
+
runId: string;
|
|
496
|
+
spanId?: string;
|
|
497
|
+
kind: EventKind;
|
|
498
|
+
timestamp: number;
|
|
499
|
+
payload: Record<string, unknown>;
|
|
500
|
+
}
|
|
501
|
+
interface BudgetLedgerEntry {
|
|
502
|
+
runId: string;
|
|
503
|
+
dimension: keyof BudgetSpec;
|
|
504
|
+
limit: number;
|
|
505
|
+
consumed: number;
|
|
506
|
+
remaining: number;
|
|
507
|
+
timestamp: number;
|
|
508
|
+
breached: boolean;
|
|
509
|
+
/** Span that triggered this entry, if any. */
|
|
510
|
+
spanId?: string;
|
|
511
|
+
}
|
|
512
|
+
interface Artifact {
|
|
513
|
+
artifactId: string;
|
|
514
|
+
runId: string;
|
|
515
|
+
spanId?: string;
|
|
516
|
+
contentType: string;
|
|
517
|
+
sizeBytes: number;
|
|
518
|
+
/** sha256 in hex. */
|
|
519
|
+
hash: string;
|
|
520
|
+
/** External storage URL (R2, S3, filesystem path). */
|
|
521
|
+
storageUrl?: string;
|
|
522
|
+
/** Inline content for small blobs — keep under ~64KB. */
|
|
523
|
+
inlineContent?: string;
|
|
524
|
+
}
|
|
525
|
+
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';
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Paper-grade RunRecord schema + runtime validator.
|
|
529
|
+
*
|
|
530
|
+
* Every run that participates in a promotion gate, paper table, or
|
|
531
|
+
* researcher loop SHOULD be recorded as a `RunRecord`. The mandatory
|
|
532
|
+
* fields are exactly those the paper "Two Loops, Three Roles" requires
|
|
533
|
+
* for reproducibility: who/what/when/cost/seed/hash, plus the search vs
|
|
534
|
+
* holdout split tag and either a `searchScore` or a `holdoutScore`.
|
|
535
|
+
*
|
|
536
|
+
* This is intentionally NOT a replacement for the rich `Run` /
|
|
537
|
+
* `ProposeReviewReport` / `ScenarioResult` types already in the
|
|
538
|
+
* package. Those are runtime structures with full provenance. A
|
|
539
|
+
* `RunRecord` is the analysis-time projection — the JSON-friendly
|
|
540
|
+
* row you'd put in a parquet file or paste into a notebook.
|
|
541
|
+
*
|
|
542
|
+
* Validate at the boundary:
|
|
543
|
+
*
|
|
544
|
+
* const rec = validateRunRecord(rawJson) // throws on missing
|
|
545
|
+
* const ok = isRunRecord(rawJson) // boolean check
|
|
546
|
+
* const rec = parseRunRecordSafe(rawJson) // { ok, value | error }
|
|
547
|
+
*
|
|
548
|
+
* The validator runs in pure TS — zod is intentionally NOT a
|
|
549
|
+
* dependency. Round-trip tested in `tests/run-record.test.ts`.
|
|
550
|
+
*/
|
|
551
|
+
|
|
552
|
+
/** Search/dev/holdout split tag. 'search' is the paper-grade alias for the
|
|
553
|
+
* combined train+test pool that the optimizer is allowed to read. */
|
|
554
|
+
type RunSplitTag = 'search' | 'dev' | 'holdout';
|
|
555
|
+
interface RunTokenUsage {
|
|
556
|
+
input: number;
|
|
557
|
+
/** All generated tokens charged as output, including reasoning tokens. */
|
|
558
|
+
output: number;
|
|
559
|
+
/** Reasoning-token subset of `output`, when the provider reports it. */
|
|
560
|
+
reasoning?: number;
|
|
561
|
+
/** Prompt tokens served from a provider cache. */
|
|
562
|
+
cached?: number;
|
|
563
|
+
/** Prompt tokens written into a provider cache. */
|
|
564
|
+
cacheWrite?: number;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* How a run's USD amount was obtained.
|
|
568
|
+
*
|
|
569
|
+
* `costUsd` remains mandatory for wire compatibility. New producers should
|
|
570
|
+
* always populate this discriminated union so a missing bill is never
|
|
571
|
+
* mistaken for an observed zero-dollar run. For `uncaptured`, `costUsd` uses
|
|
572
|
+
* the legacy `0` sentinel while this field carries the truthful null.
|
|
573
|
+
*/
|
|
574
|
+
type RunCostProvenance = {
|
|
575
|
+
kind: 'observed';
|
|
576
|
+
usd: number;
|
|
577
|
+
} | {
|
|
578
|
+
kind: 'estimated';
|
|
579
|
+
usd: number;
|
|
580
|
+
} | {
|
|
581
|
+
kind: 'uncaptured';
|
|
582
|
+
usd: null;
|
|
583
|
+
};
|
|
584
|
+
interface RunJudgeMetadata {
|
|
585
|
+
model: string;
|
|
586
|
+
promptVersion: string;
|
|
587
|
+
/** [0,1] confidence the judge declared. Constant judge confidence
|
|
588
|
+
* across many runs is a fallback signal (see `canary.ts`). */
|
|
589
|
+
confidence: number;
|
|
590
|
+
/** True if the judge degraded to a fallback path (rules-only,
|
|
591
|
+
* prior-call cache, etc.). The canary uses this to alert. */
|
|
592
|
+
fallback: boolean;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Per-judge / per-dimension breakdown for runs scored by an ensemble of
|
|
596
|
+
* judges over a multi-dimensional rubric.
|
|
597
|
+
*
|
|
598
|
+
* The collapsed `outcome.searchScore` / `holdoutScore` carries the
|
|
599
|
+
* composite the gate uses. The full breakdown belongs here so consumers
|
|
600
|
+
* can answer "which judge disagreed?", "which dimension dragged the
|
|
601
|
+
* composite down?", and "did half the panel fail?" without re-running.
|
|
602
|
+
*
|
|
603
|
+
* `perJudge[judgeId][dim]` is the canonical source; `perDimMean` and
|
|
604
|
+
* `composite` are convenience projections — derivable but precomputed so
|
|
605
|
+
* downstream IRR primitives (`interRaterReliability`,
|
|
606
|
+
* `corpusInterRaterAgreement`) and reporters don't pay the same
|
|
607
|
+
* aggregation twice.
|
|
608
|
+
*
|
|
609
|
+
* Fail-loud discipline: judges that errored out land in `failedJudges`
|
|
610
|
+
* by id. A missing key in `perJudge` is ambiguous (silent zero vs not
|
|
611
|
+
* run); the explicit list makes a partial-failure recorded as such.
|
|
612
|
+
*/
|
|
613
|
+
interface JudgeScoresRecord {
|
|
614
|
+
/** Per-judge per-dimension scores. `{ "kimi-k2.6": { helpfulness: 0.8, clarity: 0.7 }, ... }`. */
|
|
615
|
+
perJudge: Record<string, Record<string, number>>;
|
|
616
|
+
/** Per-dim mean across judges. Convenience — derivable from `perJudge`. */
|
|
617
|
+
perDimMean: Record<string, number>;
|
|
618
|
+
/** Composite mean across all dims and judges. Mirrors the score
|
|
619
|
+
* the gate sees on `outcome.searchScore` / `holdoutScore`. */
|
|
620
|
+
composite: number;
|
|
621
|
+
/** Judges that errored or returned an unparseable verdict. Recorded
|
|
622
|
+
* by id (e.g. `['glm-5.1']`) so a partial-failure case is explicit,
|
|
623
|
+
* not inferred from missing keys in `perJudge`. */
|
|
624
|
+
failedJudges?: string[];
|
|
625
|
+
/** Free-form notes the judges emitted (joined across judges or
|
|
626
|
+
* first-judge only — consumer's choice). */
|
|
627
|
+
notes?: string;
|
|
628
|
+
}
|
|
629
|
+
interface RunOutcome {
|
|
630
|
+
/** Score on the search/optimization split. Optional because a
|
|
631
|
+
* holdout-only evaluation only fills `holdoutScore`. */
|
|
632
|
+
searchScore?: number;
|
|
633
|
+
/** Score on the held-out split. Optional because a search-only run
|
|
634
|
+
* only fills `searchScore`. At least one must be present. */
|
|
635
|
+
holdoutScore?: number;
|
|
636
|
+
/** Bag of any other metric the run produced — judge dimensions,
|
|
637
|
+
* pass/fail counters, latency stats, etc. Numeric only — keeps
|
|
638
|
+
* reporters honest. */
|
|
639
|
+
raw: Record<string, number>;
|
|
640
|
+
/** Per-judge / per-dim breakdown. Consumers writing ensemble
|
|
641
|
+
* judgements populate this; substrate primitives like
|
|
642
|
+
* `interRaterReliability` and `corpusInterRaterAgreement` accept
|
|
643
|
+
* these records as input. Optional — single-judge or scalar-only
|
|
644
|
+
* runs leave it unset. */
|
|
645
|
+
judgeScores?: JudgeScoresRecord;
|
|
646
|
+
/** Authenticity / realness verdict — did the run build the REAL thing on the
|
|
647
|
+
* intended infra, or fake it (see `./authenticity`)? Optional: only domains
|
|
648
|
+
* with an authenticity config populate it. Carried in the corpus so the
|
|
649
|
+
* flywheel / off-policy learning can optimize for real completion, not gamed
|
|
650
|
+
* pass-rate. `score` is 0-1; `gated` is the anti-Goodhart flag — a gated run
|
|
651
|
+
* must not count as a real success regardless of `score`. */
|
|
652
|
+
realness?: {
|
|
653
|
+
score: number;
|
|
654
|
+
gated: boolean;
|
|
655
|
+
reason?: string;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Mandatory paper-grade fields for a single evaluation run. Optional
|
|
660
|
+
* fields are extension points; mandatory fields throw if missing.
|
|
661
|
+
*
|
|
662
|
+
* Hash discipline:
|
|
663
|
+
* - `promptHash` is the sha256 of the EFFECTIVE prompt sent to the
|
|
664
|
+
* model (after any steering bundle merge).
|
|
665
|
+
* - `configHash` is the sha256 of the effective run config (model,
|
|
666
|
+
* temperature, tools, judges, splits). The pair (promptHash,
|
|
667
|
+
* configHash) uniquely identifies an experiment cell.
|
|
668
|
+
*
|
|
669
|
+
* Model snapshot discipline:
|
|
670
|
+
* - `model` MUST encode a snapshot version. Bare aliases like
|
|
671
|
+
* `claude-sonnet-4` or `gpt-4o` are banned — they remap silently.
|
|
672
|
+
* Use `claude-sonnet-4-6@2025-04-15` or `gpt-4o-2024-11-20`.
|
|
673
|
+
*/
|
|
674
|
+
interface RunRecord {
|
|
675
|
+
/** UUID for the run. */
|
|
676
|
+
runId: string;
|
|
677
|
+
/** Logical experiment grouping (a treatment vs a baseline within
|
|
678
|
+
* the same sweep should share `experimentId`). */
|
|
679
|
+
experimentId: string;
|
|
680
|
+
/** Stable identifier for the candidate (variant) being run. The
|
|
681
|
+
* promotion gate compares two `candidateId`s on matched items. */
|
|
682
|
+
candidateId: string;
|
|
683
|
+
/** RNG seed for the run. Always recorded — silent re-seeding is
|
|
684
|
+
* the most common cause of non-reproducible numbers. */
|
|
685
|
+
seed: number;
|
|
686
|
+
/** Model identifier WITH snapshot version. */
|
|
687
|
+
model: string;
|
|
688
|
+
/** sha256 of the effective prompt (post-steering). */
|
|
689
|
+
promptHash: string;
|
|
690
|
+
/** sha256 of the effective config. */
|
|
691
|
+
configHash: string;
|
|
692
|
+
/** Git SHA the harness was run from. */
|
|
693
|
+
commitSha: string;
|
|
694
|
+
/** End-to-end wall-clock duration in milliseconds. */
|
|
695
|
+
wallMs: number;
|
|
696
|
+
/** Time spent queued before execution started, if known. */
|
|
697
|
+
queueMs?: number;
|
|
698
|
+
/** Total USD cost. Mandatory — runs without a cost number are
|
|
699
|
+
* unbounded by definition and must not be admitted into the gate.
|
|
700
|
+
* `0` is retained as the compatibility sentinel for an uncaptured amount;
|
|
701
|
+
* inspect `costProvenance` before treating it as observed. */
|
|
702
|
+
costUsd: number;
|
|
703
|
+
/** Observed, model-priced estimate, or genuinely uncaptured USD amount.
|
|
704
|
+
* Optional only so existing serialized RunRecords remain valid. */
|
|
705
|
+
costProvenance?: RunCostProvenance;
|
|
706
|
+
/** Token usage breakdown. */
|
|
707
|
+
tokenUsage: RunTokenUsage;
|
|
708
|
+
/** Judge-side metadata, if a judge was used. */
|
|
709
|
+
judgeMetadata?: RunJudgeMetadata;
|
|
710
|
+
/** Per-split scores + raw bag. */
|
|
711
|
+
outcome: RunOutcome;
|
|
712
|
+
/** Canonical, cross-agent failure class drawn from the shared
|
|
713
|
+
* `FAILURE_CLASSES` taxonomy. This is the aggregation key that makes
|
|
714
|
+
* "which failure dominates across the whole fleet" answerable in ONE
|
|
715
|
+
* vocabulary — every agent classifies against the same enum. Producers
|
|
716
|
+
* set it via the substrate classifier; leave unset only when the failure
|
|
717
|
+
* genuinely can't be classified. */
|
|
718
|
+
failureClass?: FailureClass;
|
|
719
|
+
/** Free-form domain-specific failure detail, scoped UNDER `failureClass`
|
|
720
|
+
* (e.g. failureClass='tool_recovery_failure', failureMode='forge_build_unsatisfied').
|
|
721
|
+
* The within-agent drill-down; `failureClass` is the cross-agent key. */
|
|
722
|
+
failureMode?: string;
|
|
723
|
+
/** Which split this run was drawn from. */
|
|
724
|
+
splitTag: RunSplitTag;
|
|
725
|
+
/**
|
|
726
|
+
* Stable scenario identifier the run was scored against. Optional for
|
|
727
|
+
* backwards compatibility, but **strongly recommended**: every primitive
|
|
728
|
+
* that pairs runs by scenario (preferences, paired stats, BT tournament)
|
|
729
|
+
* keys on this. The campaign artifact populates it canonically; legacy
|
|
730
|
+
* runs without it fall back to inference from `outcome.raw.scenario_id`
|
|
731
|
+
* or `experimentId`.
|
|
732
|
+
*/
|
|
733
|
+
scenarioId?: string;
|
|
734
|
+
/**
|
|
735
|
+
* Canonical identity for the agent profile cell that produced this row:
|
|
736
|
+
* profile artifact hash plus optional harness/model/prompt/reporting
|
|
737
|
+
* dimensions. Use `agentProfile.cellId` to group persona sweeps and
|
|
738
|
+
* longitudinal reports by the complete source profile, not by a loose
|
|
739
|
+
* candidate label or opaque config hash.
|
|
740
|
+
*/
|
|
741
|
+
agentProfile?: AgentProfileCell;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
interface RunFilter {
|
|
745
|
+
scenarioId?: string;
|
|
746
|
+
variantId?: string;
|
|
747
|
+
status?: RunStatus;
|
|
748
|
+
since?: number;
|
|
749
|
+
until?: number;
|
|
750
|
+
tag?: {
|
|
751
|
+
key: string;
|
|
752
|
+
value: string;
|
|
753
|
+
};
|
|
754
|
+
parentRunId?: string;
|
|
755
|
+
projectId?: string;
|
|
756
|
+
chatId?: string;
|
|
757
|
+
layer?: RunLayer;
|
|
758
|
+
}
|
|
759
|
+
interface SpanFilter {
|
|
760
|
+
runId?: string;
|
|
761
|
+
parentSpanId?: string;
|
|
762
|
+
kind?: SpanKind;
|
|
763
|
+
name?: string;
|
|
764
|
+
toolName?: string;
|
|
765
|
+
judgeId?: string;
|
|
766
|
+
since?: number;
|
|
767
|
+
until?: number;
|
|
768
|
+
}
|
|
769
|
+
interface EventFilter {
|
|
770
|
+
runId?: string;
|
|
771
|
+
spanId?: string;
|
|
772
|
+
kind?: EventKind;
|
|
773
|
+
since?: number;
|
|
774
|
+
until?: number;
|
|
775
|
+
}
|
|
776
|
+
interface TraceStore {
|
|
777
|
+
appendRun(run: Run): Promise<void>;
|
|
778
|
+
updateRun(runId: string, patch: Partial<Run>): Promise<void>;
|
|
779
|
+
appendSpan(span: Span): Promise<void>;
|
|
780
|
+
updateSpan(spanId: string, patch: Partial<Span>): Promise<void>;
|
|
781
|
+
appendEvent(event: TraceEvent): Promise<void>;
|
|
782
|
+
appendArtifact(artifact: Artifact): Promise<void>;
|
|
783
|
+
appendBudgetEntry(entry: BudgetLedgerEntry): Promise<void>;
|
|
784
|
+
getRun(runId: string): Promise<Run | undefined>;
|
|
785
|
+
listRuns(filter?: RunFilter): Promise<Run[]>;
|
|
786
|
+
spans(filter?: SpanFilter): Promise<Span[]>;
|
|
787
|
+
events(filter?: EventFilter): Promise<TraceEvent[]>;
|
|
788
|
+
budget(runId: string): Promise<BudgetLedgerEntry[]>;
|
|
789
|
+
artifacts(runId: string): Promise<Artifact[]>;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Rollout minting — `tangle.rollout.v1` lines joined from the records the
|
|
794
|
+
* substrate ALREADY keeps. There is no separate rollout store: a rollout
|
|
795
|
+
* is the JOIN of a RunRecord (identity, provenance, cost, outcome) with
|
|
796
|
+
* its trace (spans share `runId`), projected into the canonical line.
|
|
797
|
+
*
|
|
798
|
+
* Composition, not duplication:
|
|
799
|
+
* - identity/provenance → `RunRecord` (candidateId, splitTag, agentProfile, hashes)
|
|
800
|
+
* - step structure → `buildTrajectory` over the shared TraceStore
|
|
801
|
+
* - preference-pair export → `feedbackTrajectoryToOptimizerRow` (feedback-trajectory.ts)
|
|
802
|
+
* - PRM / reward-model → `reward-model-export.ts`
|
|
803
|
+
*
|
|
804
|
+
* Anti-Goodhart invariant: a run whose `outcome.realness.gated` is true
|
|
805
|
+
* is never exported with a positive reward — the gate travels into the
|
|
806
|
+
* training data (`reward` forced to 0, `realness_gated: true`), so a
|
|
807
|
+
* fine-tune cannot learn from gamed successes.
|
|
808
|
+
*
|
|
809
|
+
* Records without spans become labeled GAP LINES (messages: [],
|
|
810
|
+
* provenance.gap) — present in the output AND surfaced in
|
|
811
|
+
* `missingTraces`; a capture gap is a finding, never a silent omission.
|
|
812
|
+
*/
|
|
813
|
+
|
|
814
|
+
/** Redactor applied to every exported string (secrets, PII). Identity by default. */
|
|
815
|
+
type RolloutScrubber = (text: string) => string;
|
|
816
|
+
interface MintRolloutOptions {
|
|
817
|
+
scrub?: RolloutScrubber;
|
|
818
|
+
/** Cap steps per line (longest runs first drop middle steps). Default: no cap. */
|
|
819
|
+
maxSteps?: number;
|
|
820
|
+
/** Role recorded on every minted line. Default 'agent' (a solo eval run). */
|
|
821
|
+
role?: RolloutRole;
|
|
822
|
+
/** Task suite label. Default: the record's `experimentId`. */
|
|
823
|
+
suite?: string;
|
|
824
|
+
/** Injected clock for deterministic output. */
|
|
825
|
+
now?: () => Date;
|
|
826
|
+
}
|
|
827
|
+
interface MintRolloutResult {
|
|
828
|
+
rows: RolloutLine[];
|
|
829
|
+
/** runIds that had a RunRecord but no spans — emitted as gap lines AND listed here. */
|
|
830
|
+
missingTraces: string[];
|
|
831
|
+
}
|
|
832
|
+
declare function rolloutReward(record: RunRecord): {
|
|
833
|
+
reward: number;
|
|
834
|
+
gated: boolean;
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* Join RunRecords with their traces into canonical rollout lines. Records
|
|
838
|
+
* without spans are emitted as labeled gap lines and reported in
|
|
839
|
+
* `missingTraces` — a capture gap is a finding, not a silent omission.
|
|
840
|
+
*/
|
|
841
|
+
declare function mintRolloutRows(records: RunRecord[], store: TraceStore, options?: MintRolloutOptions): Promise<MintRolloutResult>;
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Backfill reader over Claude Code project transcripts
|
|
845
|
+
* (~/.claude/projects/<cwd-slug>/<sessionId>.jsonl) → canonical
|
|
846
|
+
* chat-with-tools messages plus per-session token usage.
|
|
847
|
+
*
|
|
848
|
+
* Transcript lines consumed: type:"user" (string content or content blocks —
|
|
849
|
+
* text + tool_result) and type:"assistant" (content blocks — thinking, text,
|
|
850
|
+
* tool_use; message.usage carries tokens). Sidechain lines (isSidechain=true,
|
|
851
|
+
* subagent threads) are separate invocations and are excluded from the main
|
|
852
|
+
* transcript. Everything else (queue-operation, attachment, last-prompt…) is
|
|
853
|
+
* transport metadata, not conversation.
|
|
854
|
+
*/
|
|
855
|
+
|
|
856
|
+
declare const DEFAULT_CLAUDE_PROJECTS_DIR: string;
|
|
857
|
+
/** Claude Code's project-directory slug for a working directory. */
|
|
858
|
+
declare function claudeProjectSlug(cwd: string): string;
|
|
859
|
+
interface ClaudeTranscriptRef {
|
|
860
|
+
sessionId: string;
|
|
861
|
+
path: string;
|
|
862
|
+
}
|
|
863
|
+
/** Transcript files recorded for sessions launched from `cwd`. */
|
|
864
|
+
declare function findClaudeTranscripts(cwd: string, projectsDir?: string): Promise<ClaudeTranscriptRef[]>;
|
|
865
|
+
interface ClaudeUsageTotals {
|
|
866
|
+
tokensIn: number;
|
|
867
|
+
tokensOut: number;
|
|
868
|
+
cacheRead: number;
|
|
869
|
+
cacheWrite: number;
|
|
870
|
+
}
|
|
871
|
+
interface ClaudeTranscript {
|
|
872
|
+
messages: ChatMessage[];
|
|
873
|
+
usage: ClaudeUsageTotals;
|
|
874
|
+
/** Timestamp of the first conversation line; null = empty transcript. */
|
|
875
|
+
startedAt: string | null;
|
|
876
|
+
endedAt: string | null;
|
|
877
|
+
model: string | null;
|
|
878
|
+
}
|
|
879
|
+
/** Parse one transcript jsonl into canonical messages + usage totals. */
|
|
880
|
+
declare function readClaudeTranscript(path: string): Promise<ClaudeTranscript>;
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Read-only backfill reader over the opencode sqlite store
|
|
884
|
+
* (~/.local/share/opencode/opencode.db) → canonical chat-with-tools messages.
|
|
885
|
+
*
|
|
886
|
+
* Schema consumed (observed, 2026-07): `session` rows carry directory /
|
|
887
|
+
* parent_id / agent / model / cost / tokens_*; `message` rows carry a JSON
|
|
888
|
+
* `data` blob ({role, modelID, providerID, tokens, cost, finish}); `part`
|
|
889
|
+
* rows carry the actual content ({type: text|reasoning|tool|step-start|
|
|
890
|
+
* step-finish|snapshot…}). Tool parts hold {callID, state:{input, output,
|
|
891
|
+
* status}} — both the call and its result, which we split into an assistant
|
|
892
|
+
* tool_call plus a role:"tool" result message.
|
|
893
|
+
*
|
|
894
|
+
* The store is mutable and can be corrupt (a `.corrupt-bak` sibling ships
|
|
895
|
+
* next to it in the wild), so `openOpencodeDb` returns null instead of
|
|
896
|
+
* throwing — callers record a gap line, never crash the backfill.
|
|
897
|
+
*/
|
|
898
|
+
|
|
899
|
+
declare const DEFAULT_OPENCODE_DB: string;
|
|
900
|
+
interface OpencodeSessionRow {
|
|
901
|
+
id: string;
|
|
902
|
+
parentId: string | null;
|
|
903
|
+
directory: string;
|
|
904
|
+
agent: string | null;
|
|
905
|
+
/** Raw session.model JSON: {id, providerID, variant} where present. */
|
|
906
|
+
model: {
|
|
907
|
+
id?: string;
|
|
908
|
+
providerID?: string;
|
|
909
|
+
} | null;
|
|
910
|
+
costUsd: number;
|
|
911
|
+
tokensInput: number;
|
|
912
|
+
tokensOutput: number;
|
|
913
|
+
tokensReasoning: number;
|
|
914
|
+
tokensCacheRead: number;
|
|
915
|
+
tokensCacheWrite: number;
|
|
916
|
+
timeCreated: number;
|
|
917
|
+
timeUpdated: number;
|
|
918
|
+
}
|
|
919
|
+
/** Open the store read-only; null = unavailable/corrupt (caller records a gap). */
|
|
920
|
+
declare function openOpencodeDb(path?: string): Promise<DatabaseSync | null>;
|
|
921
|
+
/** Sessions whose cwd is `directory` (the worker-clone join key). */
|
|
922
|
+
declare function findOpencodeSessionsByDirectory(db: DatabaseSync, directory: string): OpencodeSessionRow[];
|
|
923
|
+
declare function findOpencodeSessionById(db: DatabaseSync, sessionId: string): OpencodeSessionRow | null;
|
|
924
|
+
/**
|
|
925
|
+
* Convert one session's message+part rows into canonical messages.
|
|
926
|
+
* An opencode assistant message row spans several model steps; each step's
|
|
927
|
+
* parts (reasoning → text → tool …) become one assistant message followed by
|
|
928
|
+
* the role:"tool" results of its calls, preserving order.
|
|
929
|
+
*/
|
|
930
|
+
declare function readOpencodeSessionMessages(db: DatabaseSync, sessionId: string): ChatMessage[];
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Deterministic scrubbing pass over rollout-ledger lines before public release.
|
|
934
|
+
*
|
|
935
|
+
* Every rule is a pure regex rewrite applied to every string value in a line
|
|
936
|
+
* (messages, artifacts, run ids, tool arguments — everywhere), so the scrubbed
|
|
937
|
+
* line is still a valid `tangle.rollout.v1` line. Rules are idempotent:
|
|
938
|
+
* scrub(scrub(x)) === scrub(x), and a second pass counts zero hits — that is
|
|
939
|
+
* the property the release pipeline relies on to prove nothing half-scrubbed
|
|
940
|
+
* ships. Rule order matters: whole `KEY=value` env pairs are redacted before
|
|
941
|
+
* the bare-key rule so one secret is never counted twice.
|
|
942
|
+
*/
|
|
943
|
+
|
|
944
|
+
interface ScrubRule {
|
|
945
|
+
name: string;
|
|
946
|
+
pattern: RegExp;
|
|
947
|
+
/** Rewrite for one match; `g1` is the first capture group when present. */
|
|
948
|
+
rewrite: (match: string, g1?: string) => string;
|
|
949
|
+
}
|
|
950
|
+
declare const SCRUB_RULES: readonly ScrubRule[];
|
|
951
|
+
/** Rule name → number of matches rewritten. Always carries every rule (0 is data). */
|
|
952
|
+
type ScrubCounts = Record<string, number>;
|
|
953
|
+
declare function emptyScrubCounts(): ScrubCounts;
|
|
954
|
+
declare function addScrubCounts(into: ScrubCounts, from: ScrubCounts): ScrubCounts;
|
|
955
|
+
declare function scrubText(text: string, counts: ScrubCounts): string;
|
|
956
|
+
/** Scrub every string value in a line; structure and key order are preserved. */
|
|
957
|
+
declare function scrubRolloutLine(line: RolloutLine, counts: ScrubCounts): RolloutLine;
|
|
958
|
+
declare function scrubLines(lines: RolloutLine[]): {
|
|
959
|
+
lines: RolloutLine[];
|
|
960
|
+
counts: ScrubCounts;
|
|
961
|
+
};
|
|
962
|
+
/**
|
|
963
|
+
* A `RolloutScrubber` (text → text) applying the full rule set — the
|
|
964
|
+
* default hook to pass to `mintRolloutRows({ scrub })` so lines are
|
|
965
|
+
* scrubbed at mint time, before they ever reach a ledger file. Release
|
|
966
|
+
* builds re-run `scrubLines` regardless (idempotent), so double-scrubbing
|
|
967
|
+
* is safe and counted as zero.
|
|
968
|
+
*/
|
|
969
|
+
declare function defaultRolloutScrubber(text: string): string;
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* HuggingFace dataset-card (README.md) generation for a rollout-ledger release.
|
|
973
|
+
*
|
|
974
|
+
* The card is a pure function of the SCRUBBED lines plus the release options —
|
|
975
|
+
* no timestamps, no environment reads — so rebuilding from the same ledger
|
|
976
|
+
* yields byte-identical output. It documents the schema, provenance (run ids,
|
|
977
|
+
* generations, the official judge), per-role reward semantics including the
|
|
978
|
+
* inherited/contribution caveat, and a role × reward counts table.
|
|
979
|
+
*/
|
|
980
|
+
|
|
981
|
+
declare const RELEASE_FORMATS: readonly ["sft", "verifiers", "rft", "raw"];
|
|
982
|
+
type ReleaseFormat = (typeof RELEASE_FORMATS)[number];
|
|
983
|
+
/** Format → data file path inside the dataset dir (train split only). */
|
|
984
|
+
declare const FORMAT_FILES: Record<ReleaseFormat, string>;
|
|
985
|
+
interface DatasetCardInputs {
|
|
986
|
+
/** Scrubbed, release-filtered lines (what actually ships). */
|
|
987
|
+
lines: RolloutLine[];
|
|
988
|
+
formats: ReleaseFormat[];
|
|
989
|
+
includeProposers: boolean;
|
|
990
|
+
/** Source ledger basenames, for provenance. */
|
|
991
|
+
sourceFiles: string[];
|
|
992
|
+
scrubTotals: ScrubCounts;
|
|
993
|
+
excluded: {
|
|
994
|
+
proposers: number;
|
|
995
|
+
nonTrain: number;
|
|
996
|
+
};
|
|
997
|
+
formatCounts: Partial<Record<ReleaseFormat, number>>;
|
|
998
|
+
}
|
|
999
|
+
declare function buildDatasetCard(inputs: DatasetCardInputs): string;
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* One-command HuggingFace dataset release from rollout ledgers:
|
|
1003
|
+
*
|
|
1004
|
+
* agent-eval rollout-release <ledger.jsonl...> --out <dir> \
|
|
1005
|
+
* [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]
|
|
1006
|
+
*
|
|
1007
|
+
* Pipeline per input ledger: read + validate → fail-closed filters
|
|
1008
|
+
* (trainable split only; proposer sessions dropped unless
|
|
1009
|
+
* --include-proposers, they contain improvement-loop harness source) →
|
|
1010
|
+
* deterministic scrub → export the requested formats + scrub-report.json +
|
|
1011
|
+
* auto-generated README.md card. Deterministic: same inputs and flags →
|
|
1012
|
+
* byte-identical output dir.
|
|
1013
|
+
*
|
|
1014
|
+
* --push uploads the built dir with `huggingface-cli upload` only when the
|
|
1015
|
+
* CLI exists on PATH and HF_TOKEN is present in the env; the token is
|
|
1016
|
+
* never printed. Everything else runs fully offline.
|
|
1017
|
+
*/
|
|
1018
|
+
|
|
1019
|
+
interface BuildOptions {
|
|
1020
|
+
out: string;
|
|
1021
|
+
formats: ReleaseFormat[];
|
|
1022
|
+
includeProposers: boolean;
|
|
1023
|
+
}
|
|
1024
|
+
interface ScrubReport {
|
|
1025
|
+
/** Input ledger path → rule → rewrite count (only shipped lines are scrubbed). */
|
|
1026
|
+
files: Record<string, ScrubCounts>;
|
|
1027
|
+
totals: ScrubCounts;
|
|
1028
|
+
excluded: {
|
|
1029
|
+
proposers: number;
|
|
1030
|
+
nonTrain: number;
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
interface BuildSummary {
|
|
1034
|
+
inputs: string[];
|
|
1035
|
+
read: number;
|
|
1036
|
+
kept: number;
|
|
1037
|
+
scrub: ScrubReport;
|
|
1038
|
+
formatCounts: Partial<Record<ReleaseFormat, number>>;
|
|
1039
|
+
files: string[];
|
|
1040
|
+
}
|
|
1041
|
+
declare function buildHfDataset(inputs: string[], options: BuildOptions): Promise<BuildSummary>;
|
|
1042
|
+
declare function planPushCommand(repo: string, outDir: string): string[];
|
|
1043
|
+
declare function pushDataset(repo: string, outDir: string): void;
|
|
1044
|
+
interface RolloutReleaseCliArgs extends BuildOptions {
|
|
1045
|
+
inputs: string[];
|
|
1046
|
+
push: string | null;
|
|
1047
|
+
}
|
|
1048
|
+
declare const ROLLOUT_RELEASE_USAGE = "usage: agent-eval rollout-release <ledger.jsonl...> --out <dir> [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]";
|
|
1049
|
+
declare function parseRolloutReleaseArgs(argv: string[]): RolloutReleaseCliArgs;
|
|
1050
|
+
/** CLI driver for `agent-eval rollout-release`. Returns the process exit code. */
|
|
1051
|
+
declare function runRolloutReleaseCli(argv: string[]): Promise<number>;
|
|
1052
|
+
|
|
1053
|
+
export { type BuildOptions, type BuildSummary, CHAT_ROLES, type ChatMessage, type ChatRole, type ChatToolCall, type ClaudeTranscript, type ClaudeTranscriptRef, type ClaudeUsageTotals, DEFAULT_CLAUDE_PROJECTS_DIR, DEFAULT_OPENCODE_DB, type DatasetCardInputs, FORMAT_FILES, type MintRolloutOptions, type MintRolloutResult, type OpencodeSessionRow, RELEASE_FORMATS, ROLLOUT_CAPTURES, ROLLOUT_FORMAT, ROLLOUT_RELEASE_USAGE, ROLLOUT_ROLES, ROLLOUT_SCHEMA, ROLLOUT_SPLITS, type ReleaseFormat, type RewardRow, type RftItem, type RolloutArtifacts, type RolloutCapture, type RolloutCostBlock, type RolloutLine, type RolloutOutcome, type RolloutPolicy, type RolloutProvenance, type RolloutReleaseCliArgs, type RolloutRole, type RolloutScrubber, type RolloutSplit, type RolloutStep, type RolloutTask, SCRUB_RULES, type ScrubCounts, type ScrubReport, type ScrubRule, type SftExportOptions, type SftRow, TRAINABLE_SPLITS, type ToolDef, type VerifiersRolloutOutput, type VerifiersTokenUsage, addScrubCounts, appendRolloutLines, assertRolloutLine, buildDatasetCard, buildHfDataset, claudeProjectSlug, defaultRolloutScrubber, emptyScrubCounts, findClaudeTranscripts, findOpencodeSessionById, findOpencodeSessionsByDirectory, isRolloutLine, isTrainableSplit, mintRolloutRows, openOpencodeDb, parseRolloutReleaseArgs, planPushCommand, pushDataset, readClaudeTranscript, readOpencodeSessionMessages, readRolloutLedger, rolloutReward, runRolloutReleaseCli, scrubLines, scrubRolloutLine, scrubText, toJsonl, toRewardRows, toRftItem, toRftItems, toSftRows, toVerifiersRolloutOutput, toVerifiersRolloutOutputs, validateRolloutLine, writeRolloutLedger };
|