@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,705 @@
1
+ /**
2
+ * `tangle.rollout.v1` — THE canonical rollout serialization, owned by
3
+ * agent-eval. One JSONL line per agent invocation (a solo eval run, a
4
+ * supervisor episode, a worker session, a proposer shot, a judge call, an
5
+ * analyst pass), labeled with its task/split coordinates and a single
6
+ * scalar reward, carrying the FULL message transcript inline.
7
+ *
8
+ * This schema is the reconciliation of two prior producers:
9
+ * - agent-eval's RunRecord-joined rollout rows (PR #410): identity,
10
+ * provenance hashes, the realness gate travelling into the reward,
11
+ * trace-derived steps.
12
+ * - the bench rollout-ledger (agent-runtime PR #591): the wire shape —
13
+ * role, task.split/rep, parent_rollout_id, policy provenance, capture
14
+ * provenance, inline canonical chat-with-tools messages.
15
+ * Where the two conflicted, RunRecord-derived semantics won; the wire
16
+ * field names follow the ledger (snake_case). See `docs/rollout.md` for
17
+ * the field-by-field decision table.
18
+ *
19
+ * Messages are inlined — never referenced — because every harness store a
20
+ * rollout can be recovered from is mutable or garbage-collected. A line
21
+ * must stay a complete training/eval example on its own.
22
+ *
23
+ * `outcome.reward` is THE single scalar (null = no verdict exists — a
24
+ * labeled gap, never 0). `outcome.realness_gated` is the anti-Goodhart
25
+ * flag: a gated line must never export as a positive training example.
26
+ */
27
+ declare const ROLLOUT_SCHEMA = "tangle.rollout.v1";
28
+ /** `agent` = a solo evaluation run (no multi-agent topology). */
29
+ type RolloutRole = 'agent' | 'supervisor' | 'worker' | 'proposer' | 'judge' | 'analyst';
30
+ /**
31
+ * Split vocabulary follows `RunRecord.splitTag` ('search' is the pool the
32
+ * optimizer may read — the trainable split), extended with the ledger's
33
+ * 'canary'. 'train' is a legacy alias for 'search' emitted by
34
+ * pre-unification ledgers; it validates and counts as trainable, but new
35
+ * producers must emit 'search'.
36
+ */
37
+ type RolloutSplit = 'search' | 'dev' | 'holdout' | 'canary' | 'train';
38
+ /** 'mint' = joined live from RunRecord + trace by `mintRolloutRows`. */
39
+ type RolloutCapture = 'mint' | 'settle-time' | 'backfill';
40
+ type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
41
+ interface ChatToolCall {
42
+ id: string;
43
+ type: 'function';
44
+ function: {
45
+ name: string;
46
+ /** JSON-encoded argument object, exactly as the model emitted it. */
47
+ arguments: string;
48
+ };
49
+ }
50
+ interface ChatMessage {
51
+ role: ChatRole;
52
+ content: string | null;
53
+ /** Reasoning/thinking channel where the harness captured it (full fidelity). */
54
+ reasoning_content?: string;
55
+ tool_calls?: ChatToolCall[];
56
+ /** Required on role:"tool" — the ChatToolCall this result answers. */
57
+ tool_call_id?: string;
58
+ name?: string;
59
+ }
60
+ interface ToolDef {
61
+ type: 'function';
62
+ function: {
63
+ name: string;
64
+ description?: string;
65
+ parameters?: Record<string, unknown>;
66
+ };
67
+ }
68
+ /**
69
+ * Compact trace-span projection (llm/tool step) carried alongside the
70
+ * conversation when the line was minted from a trace. Optional: lines
71
+ * recovered from harness stores have no span structure.
72
+ */
73
+ interface RolloutStep {
74
+ kind: string;
75
+ name: string;
76
+ /** llm: last-message summary · tool: stringified args. Scrubbed. */
77
+ input?: string;
78
+ /** llm: output text · tool: stringified result. Scrubbed. */
79
+ output?: string;
80
+ status?: 'ok' | 'error';
81
+ durationMs?: number;
82
+ }
83
+ interface RolloutTask {
84
+ /** Benchmark/suite id (e.g. "swe-bench-verified") or the experiment id. */
85
+ suite: string;
86
+ instance_id: string;
87
+ split: RolloutSplit;
88
+ /** Sampling seed the campaign pinned; null = not recorded. */
89
+ seed: number | null;
90
+ /** Replicate index (0-based). */
91
+ rep: number;
92
+ }
93
+ interface RolloutPolicy {
94
+ /** Harness that drove the invocation (e.g. "opencode", "claude", "pi-loops"). */
95
+ harness: string | null;
96
+ harness_version: string | null;
97
+ model: string | null;
98
+ provider: string | null;
99
+ /** Commit of the agent profile / candidate under evaluation. */
100
+ profile_commit: string | null;
101
+ /** sha256 of the effective prompt (post-steering), when recorded. */
102
+ prompt_hash?: string | null;
103
+ /** sha256 of the effective run config, when recorded. */
104
+ config_hash?: string | null;
105
+ /** Canonical agent-profile cell identity, when the run carries one. */
106
+ agent_profile_cell_id?: string | null;
107
+ /** Sampling params (temperature, top_p, max_tokens…); null = not recorded. */
108
+ sampling: Record<string, unknown> | null;
109
+ }
110
+ interface RolloutOutcome {
111
+ /**
112
+ * THE single scalar training signal — the official verdict.
113
+ * null = no verdict exists for this invocation (a labeled gap, never 0).
114
+ */
115
+ reward: number | null;
116
+ /** Where the reward came from (judge id; "/inherited" = parent episode's). */
117
+ reward_source: string | null;
118
+ /** Raw judge verdict record, verbatim. */
119
+ verdict: unknown;
120
+ /** Everything that is NOT the scalar reward. */
121
+ metrics: Record<string, unknown>;
122
+ is_completed: boolean;
123
+ is_truncated: boolean;
124
+ error: string | null;
125
+ /**
126
+ * Anti-Goodhart flag from `RunRecord.outcome.realness.gated`: the run
127
+ * faked its success signal. Reward is forced to 0 at mint time and the
128
+ * line never qualifies for SFT. Optional on the wire (absent = false)
129
+ * so pre-unification ledgers stay readable.
130
+ */
131
+ realness_gated?: boolean;
132
+ }
133
+ interface RolloutCostBlock {
134
+ usd: number | null;
135
+ tokens_in: number | null;
136
+ tokens_out: number | null;
137
+ tokens_reasoning: number | null;
138
+ cache_read: number | null;
139
+ cache_write: number | null;
140
+ wall_s: number | null;
141
+ }
142
+ interface RolloutArtifacts {
143
+ patch_path: string | null;
144
+ run_dir: string | null;
145
+ /** Source-of-truth transcript pointer (session id / jsonl path) for audit. */
146
+ transcript_ref: string | null;
147
+ }
148
+ interface RolloutProvenance {
149
+ captured_at: string;
150
+ capture: RolloutCapture;
151
+ /** Present on gap lines: why `messages` could not be recovered. */
152
+ gap?: string;
153
+ }
154
+ interface RolloutLine {
155
+ schema: typeof ROLLOUT_SCHEMA;
156
+ rollout_id: string;
157
+ /** Spawning invocation within the same episode (worker → supervisor). */
158
+ parent_rollout_id: string | null;
159
+ run_id: string;
160
+ /** Logical experiment grouping from `RunRecord.experimentId`. Optional on
161
+ * the wire (pre-unification ledgers lack it); null = not recorded. */
162
+ experiment_id?: string | null;
163
+ /** Stable candidate identity from `RunRecord.candidateId`; null = not recorded. */
164
+ candidate_id?: string | null;
165
+ /** Improvement-loop generation (-1 = baseline); null = not an improvement loop. */
166
+ generation: number | null;
167
+ /** Improvement-loop candidate index (-1 = baseline); null = not an improvement loop. */
168
+ candidate_index: number | null;
169
+ role: RolloutRole;
170
+ task: RolloutTask;
171
+ policy: RolloutPolicy;
172
+ /** Full transcript, inline. [] = gap line (see provenance.gap). */
173
+ messages: ChatMessage[];
174
+ tool_defs: ToolDef[];
175
+ /** Trace-span projections, when minted from a trace. */
176
+ steps?: RolloutStep[];
177
+ outcome: RolloutOutcome;
178
+ cost: RolloutCostBlock;
179
+ artifacts: RolloutArtifacts;
180
+ provenance: RolloutProvenance;
181
+ }
182
+
183
+ /**
184
+ * Supervisor-run analysis — the multi-agent analogue of single-rollout trace
185
+ * analysis. A solo rollout is one invocation with a transcript; a supervisor
186
+ * run is a TREE of invocations (a brain that spawns, steers, and settles
187
+ * workers) plus the event timeline that connects them. `src/trace-analyst`
188
+ * answers "what happened inside one session"; this module answers "what did
189
+ * the tree do" — did the brain steer anyone mid-task, how many spawn waves,
190
+ * how concurrent, how idle, what did each role cost, what came back.
191
+ *
192
+ * The nodes of that tree are NOT a new shape: they are `tangle.rollout.v1`
193
+ * rows (`src/rollout`), keyed by `parent_rollout_id`, with `role` already
194
+ * spanning `supervisor` / `worker`. `supervisorRunRolloutLines` mints them.
195
+ * What rollout rows deliberately do NOT carry is the inter-invocation event
196
+ * timeline (spawn/settle/steer instants), which is what every structural
197
+ * metric here is computed from — so the reader consumes the journal event
198
+ * stream and emits rollout rows, rather than maintaining a parallel node type.
199
+ *
200
+ * ## UNAVAILABLE ≠ ZERO
201
+ *
202
+ * Every metric whose backing artifact can be missing is typed
203
+ * `Measured<T> = T | { unavailable: reason }`. A supervisor that steered
204
+ * nobody reports `steers: 0`; a supervisor whose worker logs were never
205
+ * written reports `steers: unavailable — <reason>`. The two have driven
206
+ * opposite conclusions about the same architecture, so they never collapse.
207
+ */
208
+
209
+ /** A metric that could not be computed, with the reason its artifact was missing. */
210
+ interface Unavailable {
211
+ readonly unavailable: string;
212
+ }
213
+ /** A metric value, or the reason it is unknown. NEVER collapse `unavailable` to 0. */
214
+ type Measured<T> = T | Unavailable;
215
+ declare function unavailable(reason: string): Unavailable;
216
+ declare function isUnavailable(v: unknown): v is Unavailable;
217
+ /** Render a measured scalar for the markdown/headline: `0` and `unavailable` stay distinct. */
218
+ declare function showMeasured(v: Measured<number | string | boolean | null>): string;
219
+ /** One worker's logs, as read. `null` = the artifact did not exist. */
220
+ interface WorkerLogSource {
221
+ readonly label: string;
222
+ /** Worker event stream — started / progress / finished / message events (JSONL). */
223
+ readonly events: string | null;
224
+ /** The durable steer queue — one line per steer request (JSONL). */
225
+ readonly inbox: string | null;
226
+ /** Worker patch byte length, or null when absent. */
227
+ readonly patchBytes: number | null;
228
+ }
229
+ /**
230
+ * Everything the pure analyzer reads — already-read bytes, never paths. Each
231
+ * field is `null` when its artifact was absent, which is what turns the
232
+ * dependent metrics into `unavailable` rather than 0.
233
+ *
234
+ * This is the whole input contract. Any store that can produce these strings
235
+ * (an on-disk loops run, an object-store archive, a database, a test fixture)
236
+ * is a valid source; `loopsSupervisorRunReader` is ONE implementation.
237
+ */
238
+ interface SupervisorRunSources {
239
+ /** Stable identity of the run being analyzed (a directory, a run id, a URL). */
240
+ readonly runRef: string;
241
+ readonly instanceId: string | null;
242
+ /** Which arm/variant of a comparison this run is, when the run belongs to one. */
243
+ readonly arm: string | null;
244
+ /** Identity of the supervision-tree store this was read from; null = none found. */
245
+ readonly supRunDir: string | null;
246
+ /** Supervision journal — spawned / settled / cancelled / metered events (JSONL). */
247
+ readonly journal: string | null;
248
+ /** Per-brain-call tap (JSONL): finish_reason, completion tokens, requested max tokens. */
249
+ readonly brainLog: string | null;
250
+ /** Supervisor state document (JSON). */
251
+ readonly state: string | null;
252
+ /** Supervisor progress stream (JSONL). */
253
+ readonly progress: string | null;
254
+ /** Per-worker logs; `null` = the worker log store itself was missing. */
255
+ readonly workers: readonly WorkerLogSource[] | null;
256
+ /** Why `workers` is null (only set when it is). */
257
+ readonly workersMissingReason: string | null;
258
+ /** Run result document (JSON). */
259
+ readonly result: string | null;
260
+ /**
261
+ * Judge verdict document (JSON), or the matching ledger row re-encoded as
262
+ * one. Runners that write the verdict straight to a ledger leave no judge
263
+ * document, so the ledger row is the same fact from the same run — not a
264
+ * substitute measurement.
265
+ */
266
+ readonly judge: string | null;
267
+ /** Where `judge` came from, for the report's provenance line. */
268
+ readonly judgeSource: string | null;
269
+ /** Delivered unified-diff patch text. */
270
+ readonly patch: string | null;
271
+ /** Outer-driver log (used for the driver's steer verbs + deadline evidence). */
272
+ readonly driverLog: string | null;
273
+ /**
274
+ * Worker tokens recovered from a harness session store; null = store unavailable.
275
+ * `store` names the store in the report's provenance line (e.g. `opencode`).
276
+ */
277
+ readonly harnessWorkerTokens: {
278
+ store: string;
279
+ sessions: number;
280
+ input: number;
281
+ output: number;
282
+ } | null;
283
+ readonly harnessMissingReason: string | null;
284
+ }
285
+ /**
286
+ * A source of supervisor-run bytes. Implementations own their storage layout;
287
+ * the analyzer only ever sees `SupervisorRunSources`.
288
+ */
289
+ interface SupervisorRunReader {
290
+ /** Stable identity of what this reader points at (for logs and report labels). */
291
+ readonly runRef: string;
292
+ read(): Promise<SupervisorRunSources>;
293
+ }
294
+ declare const SUPERVISOR_RUN_SCHEMA = "tangle.supervisor-run@1";
295
+ declare const SUPERVISOR_RUN_ROLLUP_SCHEMA = "tangle.supervisor-run-rollup@1";
296
+ interface SteerBreakdown {
297
+ readonly worker: string;
298
+ /** Steer requests durably queued to this worker's inbox. */
299
+ readonly queued: number;
300
+ /** Steers the worker's executor actually accepted (control event `delivered:true`). */
301
+ readonly delivered: number;
302
+ }
303
+ interface OrchestrationMetrics {
304
+ readonly workersSpawned: Measured<number>;
305
+ readonly workersSettled: Measured<number>;
306
+ readonly workersCancelled: Measured<number>;
307
+ /** THE HEADLINE: mid-task steers the brain sent to live workers. 0 ≠ unavailable. */
308
+ readonly steers: Measured<number>;
309
+ readonly steersDelivered: Measured<number>;
310
+ readonly steersByWorker: Measured<readonly SteerBreakdown[]>;
311
+ /** Outer-driver `supervisor_steer` tool calls seen in the driver log (a second steer path). */
312
+ readonly driverSteerCalls: Measured<number>;
313
+ /**
314
+ * Spawn waves. A wave is a maximal run of worker spawns with no settle/cancel between
315
+ * them: wave N+1 begins at the first spawn issued after at least one worker from an
316
+ * earlier wave has settled. Structural, not a time threshold — no tunable constant.
317
+ */
318
+ readonly waves: Measured<number>;
319
+ readonly waveSizes: Measured<readonly number[]>;
320
+ readonly maxConcurrency: Measured<number>;
321
+ /** Worker spawns issued after the first settlement — the retry/respawn tail. */
322
+ readonly respawns: Measured<number>;
323
+ /** Labels spawned more than once (a literal retry of the same subtask). */
324
+ readonly repeatedLabels: Measured<readonly string[]>;
325
+ /** Longest parent chain below the root, in worker hops. */
326
+ readonly delegationDepth: Measured<number>;
327
+ readonly timeToFirstSpawnMs: Measured<number>;
328
+ readonly supervisorWallMs: Measured<number>;
329
+ /** Wall time inside the supervisor run with ZERO live workers. */
330
+ readonly idleMs: Measured<number>;
331
+ readonly idlePct: Measured<number>;
332
+ /** sum(worker wall) / supervisor wall. >1 means real parallelism. */
333
+ readonly workerUtilization: Measured<number>;
334
+ }
335
+ interface DecisionMetrics {
336
+ readonly settledByStatus: Measured<Record<string, number>>;
337
+ readonly settledVerdicts: Measured<Record<string, number>>;
338
+ /** Worker verified its own work green AND produced a patch. */
339
+ readonly accepted: Measured<number>;
340
+ /** Worker settled with a failing verify. */
341
+ readonly rejected: Measured<number>;
342
+ /** Worker verified green but delivered no patch bytes — output with nothing to accept. */
343
+ readonly emptyPass: Measured<number>;
344
+ /** Settlements the brain observed before issuing its next spawn (evidence→respawn). */
345
+ readonly observeThenRespawn: Measured<number>;
346
+ /** Respawns with no settled evidence in front of them. */
347
+ readonly respawnWithoutEvidence: Measured<number>;
348
+ /** Steer + question traffic on the live down/up legs — the only "review while running" signal. */
349
+ readonly reviewActions: Measured<number>;
350
+ readonly workerEvidenceBytes: Measured<number>;
351
+ }
352
+ interface RoleSpend {
353
+ readonly tokensIn: Measured<number>;
354
+ readonly tokensOut: Measured<number>;
355
+ readonly usd: Measured<number>;
356
+ readonly source: string;
357
+ }
358
+ interface PerWorkerRow {
359
+ readonly worker: string;
360
+ readonly wallMs: number | null;
361
+ readonly tokensIn: number;
362
+ readonly tokensOut: number;
363
+ readonly usd: number;
364
+ readonly patchBytes: number | null;
365
+ readonly passed: boolean | null;
366
+ }
367
+ interface WallDistribution {
368
+ readonly n: number;
369
+ readonly min: number;
370
+ readonly p50: number;
371
+ readonly p90: number;
372
+ readonly max: number;
373
+ readonly sum: number;
374
+ }
375
+ interface EconomicsMetrics {
376
+ /** Driver/brain inference — journal `metered` events. */
377
+ readonly brain: RoleSpend;
378
+ /**
379
+ * Brain completions that came back `finish_reason: "length"` — output TRUNCATED. Any value
380
+ * above 0 means the supervisor planned into a wall and then acted on the half-written plan,
381
+ * which is a defect and not a cost figure. The journal's `metered` rows carry token counts
382
+ * but no finish reason, so this reads the per-call brain tap; a run whose supervisor
383
+ * predates that tap reports `unavailable`, never 0.
384
+ */
385
+ readonly brainTruncations: Measured<number>;
386
+ /** Worker inference — journal `settled` spend plus the harness session join. */
387
+ readonly workers: RoleSpend;
388
+ readonly totalUsd: Measured<number>;
389
+ /**
390
+ * Where `totalUsd` came from. CLI-backend workers never price their own inference into
391
+ * the journal, so on those arms the total is BRAIN-ONLY and the worker row's token
392
+ * counts (recovered from the harness store) are the honest worker-side figure.
393
+ */
394
+ readonly totalUsdSource: string;
395
+ readonly costPerAcceptedPatchUsd: Measured<number>;
396
+ readonly workerWallMsDistribution: Measured<WallDistribution>;
397
+ readonly perWorker: Measured<readonly PerWorkerRow[]>;
398
+ }
399
+ interface PatchStats {
400
+ readonly files: number;
401
+ readonly linesAdded: number;
402
+ readonly linesRemoved: number;
403
+ readonly testFilesTouched: readonly string[];
404
+ }
405
+ interface OutcomeMetrics {
406
+ readonly supStatus: Measured<string>;
407
+ readonly supVerdict: Measured<string>;
408
+ readonly delivered: Measured<boolean>;
409
+ readonly judgeResolved: Measured<boolean | null>;
410
+ readonly judgeScore: Measured<number | null>;
411
+ readonly judgePassed: Measured<number | null>;
412
+ readonly judgeTotal: Measured<number | null>;
413
+ readonly verifyPass: Measured<boolean>;
414
+ readonly verifyRc: Measured<number>;
415
+ readonly patch: Measured<PatchStats>;
416
+ /** Which document the judge fields came from (a judge file, a ledger row, or nothing). */
417
+ readonly judgeSource: string | null;
418
+ }
419
+ interface SupervisorRunReport {
420
+ readonly schema: typeof SUPERVISOR_RUN_SCHEMA;
421
+ /** The `runRef` of the sources this report was computed from. */
422
+ readonly runRef: string;
423
+ readonly instanceId: string | null;
424
+ readonly arm: string | null;
425
+ readonly supervisorId: Measured<string>;
426
+ readonly generatedAt: string;
427
+ readonly orchestration: OrchestrationMetrics;
428
+ readonly decision: DecisionMetrics;
429
+ readonly economics: EconomicsMetrics;
430
+ readonly outcome: OutcomeMetrics;
431
+ /** Artifacts that were missing, in read order — the provenance of every `unavailable`. */
432
+ readonly gaps: readonly string[];
433
+ /** The `traces` CLI command that covers the harness-session layer for this run. */
434
+ readonly traceCommand: string;
435
+ }
436
+ interface RollupCellRow {
437
+ readonly instanceId: string | null;
438
+ readonly arm: string | null;
439
+ readonly steers: Measured<number>;
440
+ readonly waves: Measured<number>;
441
+ readonly utilization: Measured<number>;
442
+ readonly idlePct: Measured<number>;
443
+ readonly resolved: Measured<boolean | null>;
444
+ readonly usd: Measured<number>;
445
+ }
446
+ interface SupervisorRunRollup {
447
+ readonly schema: typeof SUPERVISOR_RUN_ROLLUP_SCHEMA;
448
+ readonly cells: number;
449
+ readonly steersTotal: Measured<number>;
450
+ readonly cellsWithSteers: Measured<number>;
451
+ readonly cellsWithUnavailableSteers: number;
452
+ readonly wavesMean: Measured<number>;
453
+ readonly maxConcurrencyMax: Measured<number>;
454
+ readonly utilizationMean: Measured<number>;
455
+ readonly idlePctMean: Measured<number>;
456
+ readonly workersSpawnedTotal: Measured<number>;
457
+ readonly acceptedTotal: Measured<number>;
458
+ readonly usdTotal: Measured<number>;
459
+ readonly resolvedCount: Measured<number>;
460
+ readonly perCell: readonly RollupCellRow[];
461
+ }
462
+ /**
463
+ * A supervision tree expressed in the canonical rollout row type: one
464
+ * `RolloutLine` per invocation, joined by `parent_rollout_id`. The root row
465
+ * carries `role: 'supervisor'`; every spawned worker carries `role: 'worker'`
466
+ * with the root as its parent.
467
+ */
468
+ interface SupervisorRunTree {
469
+ readonly rootId: string | null;
470
+ readonly nodes: readonly RolloutLine[];
471
+ /** Why a node could not be recovered, in read order. */
472
+ readonly gaps: readonly string[];
473
+ }
474
+
475
+ /**
476
+ * The pure analyzer. Takes already-read bytes (`SupervisorRunSources`) and
477
+ * returns the report — every metric derivable from a synthetic journal string
478
+ * with no filesystem, no process, and no network. All I/O lives in a reader
479
+ * (`loops-reader.ts` is one).
480
+ */
481
+
482
+ interface Tokens {
483
+ input: number;
484
+ output: number;
485
+ }
486
+ interface SpendLike {
487
+ tokens: Tokens;
488
+ usd: number;
489
+ }
490
+ interface SpawnRow {
491
+ id: string;
492
+ parent: string | null;
493
+ label: string;
494
+ at: number | null;
495
+ }
496
+ interface CloseRow {
497
+ id: string;
498
+ kind: 'settled' | 'cancelled';
499
+ status: string | null;
500
+ verdict: string | null;
501
+ at: number | null;
502
+ spend: SpendLike;
503
+ }
504
+ interface WorkerLogFacts {
505
+ started: number | null;
506
+ /** True once a `finished` event was seen — independent of whether its `at` parsed. */
507
+ finished: boolean;
508
+ finishedAt: number | null;
509
+ passed: boolean | null;
510
+ /** `patchBytes` as reported by the finished event (not the patch file's size). */
511
+ finishedPatchBytes: number | null;
512
+ evidenceBytes: number;
513
+ steersQueued: number;
514
+ steersDelivered: number;
515
+ questions: number;
516
+ }
517
+ /**
518
+ * The tree + timeline the report is computed from, exposed because the rollout-row
519
+ * minter needs exactly the same parse (one parser, two consumers).
520
+ */
521
+ interface SupervisorTreeFacts {
522
+ readonly rootId: string | null;
523
+ readonly spawns: readonly SpawnRow[];
524
+ readonly closes: readonly CloseRow[];
525
+ readonly workerSpawns: readonly SpawnRow[];
526
+ readonly workerCloses: readonly CloseRow[];
527
+ readonly brain: {
528
+ tokensIn: number;
529
+ tokensOut: number;
530
+ usd: number;
531
+ meteredCount: number;
532
+ };
533
+ readonly workerLogs: ReadonlyMap<string, WorkerLogFacts>;
534
+ readonly startedAt: number | null;
535
+ readonly completedAt: number | null;
536
+ }
537
+ declare function parseSupervisorTree(src: SupervisorRunSources): SupervisorTreeFacts;
538
+ /**
539
+ * Analyze already-read supervisor-run bytes. Pure and synchronous: same bytes
540
+ * in, same report out (modulo `generatedAt`, which `now` pins in tests).
541
+ */
542
+ declare function analyzeSupervisorRunSources(src: SupervisorRunSources, now?: () => number): SupervisorRunReport;
543
+ /** Unified-diff stats. Counts `+++ b/<path>` targets, body +/- lines, and test-file touches. */
544
+ declare function parsePatch(text: string): PatchStats;
545
+ /**
546
+ * Aggregate many supervisor-run reports. A metric no run could measure stays
547
+ * `unavailable` rather than becoming a 0-valued mean, and cells whose steer
548
+ * count was unavailable are counted separately from cells that measured zero.
549
+ */
550
+ declare function rollupSupervisorRuns(reports: readonly SupervisorRunReport[]): SupervisorRunRollup;
551
+
552
+ /**
553
+ * ONE implementation of `SupervisorRunReader`: the on-disk layout the loops
554
+ * supervisor writes — `<runDir>/ws/.loops/supervisor/<id>/{journal.jsonl,
555
+ * state.json, progress.ndjson, workers/*.ndjson}` alongside the run's
556
+ * `result.json` / `judge.json` / `driver.log` / delivered patch.
557
+ *
558
+ * Nothing in `analyze.ts` knows this layout exists. A different store (an
559
+ * archive, an object bucket, a database) implements the same interface and
560
+ * gets the same report.
561
+ *
562
+ * Worker token recovery reuses the rollout module's opencode reader rather
563
+ * than opening a second sqlite path — one store client, one corruption policy.
564
+ */
565
+
566
+ /** Locate the (single) supervisor run dir under `<ws>/.loops/supervisor`. */
567
+ declare function findSupervisorRunDirIn(ws: string): Promise<string | null>;
568
+ interface LoopsReaderOptions {
569
+ /** Override the workspace dir (default `<runDir>/ws`). */
570
+ readonly ws?: string;
571
+ /** Delivered patch path (default: `patchPath` from result.json). */
572
+ readonly patchPath?: string;
573
+ /** opencode sqlite store; set to `null` to skip the worker-token join entirely. */
574
+ readonly opencodeDb?: string | null;
575
+ /** Ledger to fall back to when the run has no `judge.json` (matched on iid + arm + runDir). */
576
+ readonly ledgerPath?: string;
577
+ }
578
+ /**
579
+ * Read a loops supervisor run directory into source bytes. Never throws on a
580
+ * missing artifact — an absent file becomes a `null` field, which is what makes
581
+ * the dependent metric `unavailable` instead of 0.
582
+ */
583
+ declare function readLoopsSupervisorRun(runDir: string, opts?: LoopsReaderOptions): Promise<SupervisorRunSources>;
584
+ /** The loops on-disk layout, as a `SupervisorRunReader`. */
585
+ declare function loopsSupervisorRunReader(runDir: string, opts?: LoopsReaderOptions): SupervisorRunReader;
586
+ /**
587
+ * Analyze a supervisor run. Accepts a run directory (read through the loops
588
+ * reader), any `SupervisorRunReader`, or already-read source bytes — so a
589
+ * caller with its own store never has to touch the filesystem layout.
590
+ */
591
+ declare function analyzeSupervisorRun(input: string | SupervisorRunReader | SupervisorRunSources, opts?: LoopsReaderOptions): Promise<SupervisorRunReport>;
592
+ interface WriteSupervisorRunOptions extends LoopsReaderOptions {
593
+ /** Append the headline block here (the experiment's run log). */
594
+ readonly appendHeadlineTo?: string;
595
+ /** Also console.log the headline (default true). */
596
+ readonly echo?: boolean;
597
+ /**
598
+ * Write `run-report.{json,md}` here instead of into the run dir. Set when
599
+ * reporting over a run directory that must stay READ-ONLY (a live run, an
600
+ * archived generation).
601
+ */
602
+ readonly reportDir?: string;
603
+ }
604
+ /**
605
+ * Read a completed run, write `run-report.json` + `run-report.md` beside its
606
+ * artifacts, and append the headline block to the run log. Never throws on a
607
+ * missing artifact — a run that produced nothing still yields a report whose
608
+ * every metric says why.
609
+ */
610
+ declare function writeSupervisorRunReport(runDir: string, opts?: WriteSupervisorRunOptions): Promise<SupervisorRunReport>;
611
+ /**
612
+ * File stem for out-of-tree reports. Built from the run path's identifying
613
+ * segments — candidate tag (the segment under `arm-runs/`), rep, instance, arm
614
+ * — so two runs of the same instance from different candidates/reps never
615
+ * overwrite each other.
616
+ */
617
+ declare function supervisorReportStem(runDir: string): string;
618
+ /**
619
+ * Best-effort wrapper for a hot path: a reporting failure must never kill a run
620
+ * that already produced real work. Returns null and logs the reason instead.
621
+ */
622
+ declare function writeSupervisorRunReportSafe(runDir: string, opts?: WriteSupervisorRunOptions): Promise<SupervisorRunReport | null>;
623
+ /**
624
+ * Report every run under an experiment `outDir` (any depth of
625
+ * `runs/<iid>/<arm>`), write each run's report, and write the rollup at
626
+ * `<outDir>/run-report-round.{json,md}`.
627
+ */
628
+ declare function reportSupervisorRound(outDir: string, opts?: WriteSupervisorRunOptions & {
629
+ title?: string;
630
+ }): Promise<SupervisorRunRollup>;
631
+ /** Every `<...>/runs/<iid>/<arm>` directory under `root`. */
632
+ declare function findSupervisorRunDirs(root: string): Promise<string[]>;
633
+
634
+ /**
635
+ * Human-readable renderings of a supervisor-run report. Zero and unavailable
636
+ * render differently on purpose (`0` vs `unavailable — <reason>`), because the
637
+ * two have driven opposite conclusions about the same architecture.
638
+ */
639
+
640
+ /**
641
+ * The block appended to a run log after every run — the answers an operator asks
642
+ * for, in the log tail, with no extra command.
643
+ */
644
+ declare function renderSupervisorRunHeadline(r: SupervisorRunReport): string;
645
+ declare function renderSupervisorRunMarkdown(r: SupervisorRunReport): string;
646
+ declare function renderSupervisorRollupMarkdown(rollup: SupervisorRunRollup, title?: string): string;
647
+
648
+ /**
649
+ * The supervision tree as `tangle.rollout.v1` rows.
650
+ *
651
+ * A supervisor run IS a tree of rollouts, so its nodes are not a new shape:
652
+ * the root becomes one `RolloutLine` with `role: 'supervisor'`, every spawned
653
+ * worker becomes a `RolloutLine` with `role: 'worker'` and
654
+ * `parent_rollout_id` pointing at its spawner. The rows append to the same
655
+ * ledger as solo-agent rollouts and join to them with the same keys.
656
+ *
657
+ * What the journal CANNOT supply is the transcript: a worker's messages live
658
+ * in its harness store (opencode sqlite, Claude Code jsonl), which the
659
+ * `src/rollout/readers/*` intake readers own. Rows minted here are therefore
660
+ * GAP lines (`messages: []`, `provenance.gap` set) carrying identity,
661
+ * structure, outcome and cost; hydrating them with messages is the readers'
662
+ * job, keyed on `artifacts.transcript_ref`.
663
+ *
664
+ * Timing lives in `outcome.metrics` (`spawned_at` / `settled_at` / `wall_ms`)
665
+ * rather than a schema field: `tangle.rollout.v1` describes ONE invocation,
666
+ * and the inter-invocation event timeline — which is what waves, concurrency,
667
+ * idle and utilization are computed from — is a property of the journal, not
668
+ * of any single row. The analyzer reads that timeline; these rows carry the
669
+ * per-node facts.
670
+ */
671
+
672
+ interface SupervisorRolloutOptions {
673
+ /** Benchmark/suite id for `task.suite`. Defaults to `'supervisor-run'`. */
674
+ readonly suite?: string;
675
+ /** `task.split`. Defaults to `'search'` (the trainable pool). */
676
+ readonly split?: RolloutSplit;
677
+ /** Replicate index. Defaults to 0. */
678
+ readonly rep?: number;
679
+ /** Sampling seed the campaign pinned. Defaults to null (not recorded). */
680
+ readonly seed?: number | null;
681
+ /** `run_id` for every node. Defaults to the supervisor root id, else `runRef`. */
682
+ readonly runId?: string;
683
+ /** Harness that drove the supervisor. */
684
+ readonly supervisorHarness?: string | null;
685
+ /** Harness that drove the workers. */
686
+ readonly workerHarness?: string | null;
687
+ /** Model the supervisor ran on. */
688
+ readonly supervisorModel?: string | null;
689
+ /** Model the workers ran on. */
690
+ readonly workerModel?: string | null;
691
+ readonly experimentId?: string | null;
692
+ readonly candidateId?: string | null;
693
+ readonly generation?: number | null;
694
+ readonly candidateIndex?: number | null;
695
+ /** Pins `provenance.captured_at`; defaults to now. */
696
+ readonly capturedAt?: string;
697
+ }
698
+ /**
699
+ * Mint the supervision tree as rollout rows. Returns the rows plus the gaps
700
+ * that made any of them incomplete — same unavailable-vs-zero discipline as
701
+ * the report: a row with no transcript says WHY, it never pretends to be empty.
702
+ */
703
+ declare function supervisorRunRolloutLines(src: SupervisorRunSources, opts?: SupervisorRolloutOptions): SupervisorRunTree;
704
+
705
+ export { type CloseRow, type DecisionMetrics, type EconomicsMetrics, type LoopsReaderOptions, type Measured, type OrchestrationMetrics, type OutcomeMetrics, type PatchStats, type PerWorkerRow, type RoleSpend, type RollupCellRow, SUPERVISOR_RUN_ROLLUP_SCHEMA, SUPERVISOR_RUN_SCHEMA, type SpawnRow, type SteerBreakdown, type SupervisorRolloutOptions, type SupervisorRunReader, type SupervisorRunReport, type SupervisorRunRollup, type SupervisorRunSources, type SupervisorRunTree, type SupervisorTreeFacts, type Unavailable, type WallDistribution, type WorkerLogFacts, type WorkerLogSource, type WriteSupervisorRunOptions, analyzeSupervisorRun, analyzeSupervisorRunSources, findSupervisorRunDirIn, findSupervisorRunDirs, isUnavailable, loopsSupervisorRunReader, parsePatch, parseSupervisorTree, readLoopsSupervisorRun, renderSupervisorRollupMarkdown, renderSupervisorRunHeadline, renderSupervisorRunMarkdown, reportSupervisorRound, rollupSupervisorRuns, showMeasured, supervisorReportStem, supervisorRunRolloutLines, unavailable, writeSupervisorRunReport, writeSupervisorRunReportSafe };