@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +23 -4
  2. package/dist/analyst/index.d.ts +7 -0
  3. package/dist/analyst/index.js +4 -4
  4. package/dist/benchmarks/index.d.ts +2 -0
  5. package/dist/benchmarks/index.js +4 -4
  6. package/dist/campaign/index.d.ts +13 -0
  7. package/dist/campaign/index.js +4 -4
  8. package/dist/chunk-4Y7AAATF.js +1151 -0
  9. package/dist/chunk-4Y7AAATF.js.map +1 -0
  10. package/dist/{chunk-VPDOSN3L.js → chunk-5PVZVCZB.js} +7 -5
  11. package/dist/chunk-5PVZVCZB.js.map +1 -0
  12. package/dist/{chunk-WP3GEXKI.js → chunk-A6GT67HT.js} +3 -3
  13. package/dist/{chunk-MHPEGJHC.js → chunk-DT7OXY3C.js} +2 -2
  14. package/dist/{chunk-MK3Q6GCL.js → chunk-EQUK3RFS.js} +14 -5
  15. package/dist/chunk-EQUK3RFS.js.map +1 -0
  16. package/dist/{chunk-N7ZS6PEF.js → chunk-GID26AN4.js} +2 -2
  17. package/dist/{chunk-4KO6D2BO.js → chunk-HM6V7F3M.js} +2 -2
  18. package/dist/chunk-IPYXE555.js +594 -0
  19. package/dist/chunk-IPYXE555.js.map +1 -0
  20. package/dist/chunk-MAX3TN3C.js +249 -0
  21. package/dist/chunk-MAX3TN3C.js.map +1 -0
  22. package/dist/chunk-MGGFVCJ7.js +288 -0
  23. package/dist/chunk-MGGFVCJ7.js.map +1 -0
  24. package/dist/{chunk-KKPPFIDS.js → chunk-PMITBABE.js} +7 -5
  25. package/dist/{chunk-KKPPFIDS.js.map → chunk-PMITBABE.js.map} +1 -1
  26. package/dist/{chunk-J3LHTAAB.js → chunk-QOTFXW5L.js} +1 -47
  27. package/dist/chunk-QOTFXW5L.js.map +1 -0
  28. package/dist/chunk-R7ZRE2KV.js +138 -0
  29. package/dist/chunk-R7ZRE2KV.js.map +1 -0
  30. package/dist/chunk-RZTMDUO7.js +49 -0
  31. package/dist/chunk-RZTMDUO7.js.map +1 -0
  32. package/dist/{chunk-QNVBALEX.js → chunk-W5B3ZGP3.js} +3 -3
  33. package/dist/cli.js +13 -2
  34. package/dist/cli.js.map +1 -1
  35. package/dist/contract/index.d.ts +9 -0
  36. package/dist/contract/index.js +4 -4
  37. package/dist/index.d.ts +701 -82
  38. package/dist/index.js +57 -121
  39. package/dist/index.js.map +1 -1
  40. package/dist/multishot/index.d.ts +2 -0
  41. package/dist/openapi.json +1 -1
  42. package/dist/pipelines/index.js +4 -2
  43. package/dist/pipelines/index.js.map +1 -1
  44. package/dist/rl.d.ts +5 -0
  45. package/dist/rl.js +2 -2
  46. package/dist/rollout/index.d.ts +1053 -0
  47. package/dist/rollout/index.js +109 -0
  48. package/dist/rollout/index.js.map +1 -0
  49. package/dist/supervisor-run/index.d.ts +705 -0
  50. package/dist/supervisor-run/index.js +52 -0
  51. package/dist/supervisor-run/index.js.map +1 -0
  52. package/dist/wire/index.d.ts +3 -0
  53. package/dist/wire/index.js +2 -2
  54. package/docs/rollout.md +48 -0
  55. package/package.json +11 -1
  56. package/dist/chunk-J3LHTAAB.js.map +0 -1
  57. package/dist/chunk-MK3Q6GCL.js.map +0 -1
  58. package/dist/chunk-VPDOSN3L.js.map +0 -1
  59. /package/dist/{chunk-WP3GEXKI.js.map → chunk-A6GT67HT.js.map} +0 -0
  60. /package/dist/{chunk-MHPEGJHC.js.map → chunk-DT7OXY3C.js.map} +0 -0
  61. /package/dist/{chunk-N7ZS6PEF.js.map → chunk-GID26AN4.js.map} +0 -0
  62. /package/dist/{chunk-4KO6D2BO.js.map → chunk-HM6V7F3M.js.map} +0 -0
  63. /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 };