@tangle-network/agent-eval 0.126.7 → 0.127.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 (129) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/analyst/index.d.ts +51 -39
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/belief-state/index.d.ts +34 -36
  6. package/dist/belief-state/index.js +2 -2
  7. package/dist/benchmarks/index.d.ts +30 -12
  8. package/dist/benchmarks/index.js +12 -10
  9. package/dist/builder-eval/index.js +1 -1
  10. package/dist/campaign/index.d.ts +432 -146
  11. package/dist/campaign/index.js +11 -9
  12. package/dist/{chunk-ARU2PZFM.js → chunk-424XHVKV.js} +43 -23
  13. package/dist/chunk-424XHVKV.js.map +1 -0
  14. package/dist/{chunk-NUKSVU3W.js → chunk-5DTSBUL2.js} +2 -2
  15. package/dist/{chunk-BTACVO2W.js → chunk-65QETEKG.js} +542 -258
  16. package/dist/chunk-65QETEKG.js.map +1 -0
  17. package/dist/{chunk-CGG5SLH3.js → chunk-ARCFIY7U.js} +2 -2
  18. package/dist/{chunk-P22LJ3Y2.js → chunk-CLELFRQG.js} +6 -4
  19. package/dist/chunk-CLELFRQG.js.map +1 -0
  20. package/dist/{chunk-LUNF2SEL.js → chunk-DJKY2TSY.js} +3 -3
  21. package/dist/chunk-DPUHNQLN.js +232 -0
  22. package/dist/chunk-DPUHNQLN.js.map +1 -0
  23. package/dist/{chunk-IPYXE555.js → chunk-EJGRPCO3.js} +36 -13
  24. package/dist/chunk-EJGRPCO3.js.map +1 -0
  25. package/dist/{chunk-K6IAZZ6L.js → chunk-FA3MFYNA.js} +132 -110
  26. package/dist/chunk-FA3MFYNA.js.map +1 -0
  27. package/dist/chunk-IG35YDN3.js +614 -0
  28. package/dist/chunk-IG35YDN3.js.map +1 -0
  29. package/dist/{chunk-DPZAEKA6.js → chunk-IMGJZBFG.js} +84 -56
  30. package/dist/chunk-IMGJZBFG.js.map +1 -0
  31. package/dist/{chunk-H5UD2323.js → chunk-IN455XKS.js} +53 -1
  32. package/dist/chunk-IN455XKS.js.map +1 -0
  33. package/dist/{chunk-PJQFMIOX.js → chunk-MHELPNRP.js} +31 -1
  34. package/dist/chunk-MHELPNRP.js.map +1 -0
  35. package/dist/{chunk-IILEIWGW.js → chunk-NL33OXEP.js} +41 -36
  36. package/dist/chunk-NL33OXEP.js.map +1 -0
  37. package/dist/{chunk-474LBSOX.js → chunk-OD5NQ2CS.js} +41 -16
  38. package/dist/chunk-OD5NQ2CS.js.map +1 -0
  39. package/dist/{chunk-QOTFXW5L.js → chunk-P5W7RQKK.js} +2 -2
  40. package/dist/{chunk-IR3KBHOY.js → chunk-P6FYH6K4.js} +39 -3
  41. package/dist/chunk-P6FYH6K4.js.map +1 -0
  42. package/dist/{chunk-J5SQWP6Y.js → chunk-PBE2LOSS.js} +18 -2
  43. package/dist/chunk-PBE2LOSS.js.map +1 -0
  44. package/dist/{chunk-E3IADPLG.js → chunk-PRB53E3J.js} +50 -224
  45. package/dist/chunk-PRB53E3J.js.map +1 -0
  46. package/dist/{chunk-X4UCIOTZ.js → chunk-S5YLIBFX.js} +2 -2
  47. package/dist/{chunk-7VYEGHWF.js → chunk-SZLVEKMJ.js} +41 -20
  48. package/dist/chunk-SZLVEKMJ.js.map +1 -0
  49. package/dist/{chunk-LKKT3IVV.js → chunk-TSN7JT6D.js} +6 -4
  50. package/dist/{chunk-LKKT3IVV.js.map → chunk-TSN7JT6D.js.map} +1 -1
  51. package/dist/{chunk-M7AH34KV.js → chunk-UTA2TGC4.js} +32 -16
  52. package/dist/chunk-UTA2TGC4.js.map +1 -0
  53. package/dist/{chunk-MAX3TN3C.js → chunk-UWZZKKU7.js} +7 -19
  54. package/dist/chunk-UWZZKKU7.js.map +1 -0
  55. package/dist/{chunk-ZVCHKKOP.js → chunk-WED5KLZQ.js} +39 -15
  56. package/dist/chunk-WED5KLZQ.js.map +1 -0
  57. package/dist/{chunk-OCFJACJU.js → chunk-X6VOPMLD.js} +74 -14
  58. package/dist/chunk-X6VOPMLD.js.map +1 -0
  59. package/dist/{chunk-MOXWMGPC.js → chunk-Y5CYOL42.js} +159 -70
  60. package/dist/chunk-Y5CYOL42.js.map +1 -0
  61. package/dist/{chunk-FO7HEH76.js → chunk-YJBNWCAA.js} +2 -2
  62. package/dist/chunk-ZPUEEM5U.js +179 -0
  63. package/dist/chunk-ZPUEEM5U.js.map +1 -0
  64. package/dist/{chunk-KE2VWPZX.js → chunk-ZUUWPZCV.js} +3 -3
  65. package/dist/cli.js +4 -4
  66. package/dist/contract/index.d.ts +421 -124
  67. package/dist/contract/index.js +242 -115
  68. package/dist/contract/index.js.map +1 -1
  69. package/dist/control.d.ts +34 -32
  70. package/dist/control.js +2 -2
  71. package/dist/hosted/index.d.ts +142 -41
  72. package/dist/hosted/index.js +23 -1
  73. package/dist/index.d.ts +364 -255
  74. package/dist/index.js +315 -226
  75. package/dist/index.js.map +1 -1
  76. package/dist/meta-eval/index.d.ts +32 -32
  77. package/dist/meta-eval/index.js +2 -2
  78. package/dist/multishot/index.d.ts +1 -0
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.js +2 -2
  81. package/dist/reporting.d.ts +97 -74
  82. package/dist/reporting.js +5 -4
  83. package/dist/rl.d.ts +177 -131
  84. package/dist/rl.js +232 -126
  85. package/dist/rl.js.map +1 -1
  86. package/dist/rollout/index.d.ts +53 -63
  87. package/dist/rollout/index.js +7 -5
  88. package/dist/{run-campaign-FFRM3RH5.js → run-campaign-3A2EBYBE.js} +4 -3
  89. package/dist/supervisor-run/index.d.ts +7 -15
  90. package/dist/supervisor-run/index.js +2 -2
  91. package/dist/traces.d.ts +66 -41
  92. package/dist/traces.js +9 -5
  93. package/dist/wire/index.d.ts +1 -0
  94. package/dist/wire/index.js +2 -2
  95. package/docs/concepts.md +18 -1
  96. package/docs/hosted-ingest-spec.md +37 -40
  97. package/docs/insight-report.md +57 -7
  98. package/docs/research-report-methodology.md +20 -21
  99. package/docs/rollout.md +13 -8
  100. package/package.json +4 -3
  101. package/dist/chunk-474LBSOX.js.map +0 -1
  102. package/dist/chunk-7VYEGHWF.js.map +0 -1
  103. package/dist/chunk-ARU2PZFM.js.map +0 -1
  104. package/dist/chunk-BTACVO2W.js.map +0 -1
  105. package/dist/chunk-DPZAEKA6.js.map +0 -1
  106. package/dist/chunk-E3IADPLG.js.map +0 -1
  107. package/dist/chunk-H5UD2323.js.map +0 -1
  108. package/dist/chunk-IILEIWGW.js.map +0 -1
  109. package/dist/chunk-IPYXE555.js.map +0 -1
  110. package/dist/chunk-IR3KBHOY.js.map +0 -1
  111. package/dist/chunk-J5SQWP6Y.js.map +0 -1
  112. package/dist/chunk-K6IAZZ6L.js.map +0 -1
  113. package/dist/chunk-M7AH34KV.js.map +0 -1
  114. package/dist/chunk-MAX3TN3C.js.map +0 -1
  115. package/dist/chunk-MOXWMGPC.js.map +0 -1
  116. package/dist/chunk-OCFJACJU.js.map +0 -1
  117. package/dist/chunk-P22LJ3Y2.js.map +0 -1
  118. package/dist/chunk-PJQFMIOX.js.map +0 -1
  119. package/dist/chunk-ZVCHKKOP.js.map +0 -1
  120. package/dist/chunk-ZZUXHH3R.js +0 -99
  121. package/dist/chunk-ZZUXHH3R.js.map +0 -1
  122. /package/dist/{chunk-NUKSVU3W.js.map → chunk-5DTSBUL2.js.map} +0 -0
  123. /package/dist/{chunk-CGG5SLH3.js.map → chunk-ARCFIY7U.js.map} +0 -0
  124. /package/dist/{chunk-LUNF2SEL.js.map → chunk-DJKY2TSY.js.map} +0 -0
  125. /package/dist/{chunk-QOTFXW5L.js.map → chunk-P5W7RQKK.js.map} +0 -0
  126. /package/dist/{chunk-X4UCIOTZ.js.map → chunk-S5YLIBFX.js.map} +0 -0
  127. /package/dist/{chunk-FO7HEH76.js.map → chunk-YJBNWCAA.js.map} +0 -0
  128. /package/dist/{chunk-KE2VWPZX.js.map → chunk-ZUUWPZCV.js.map} +0 -0
  129. /package/dist/{run-campaign-FFRM3RH5.js.map → run-campaign-3A2EBYBE.js.map} +0 -0
package/dist/rl.js CHANGED
@@ -10,25 +10,29 @@ import {
10
10
  } from "./chunk-3RF76KTD.js";
11
11
  import {
12
12
  runEvalCampaign
13
- } from "./chunk-P22LJ3Y2.js";
13
+ } from "./chunk-CLELFRQG.js";
14
14
  import {
15
15
  detectRewardHacking,
16
16
  extractVerifiableReward,
17
17
  extractVerifiableRewardsFromRecords,
18
18
  filterDeterministicallyRewarded
19
- } from "./chunk-ARU2PZFM.js";
20
- import "./chunk-J5SQWP6Y.js";
19
+ } from "./chunk-424XHVKV.js";
20
+ import {
21
+ campaignCellToRunRecord
22
+ } from "./chunk-ZPUEEM5U.js";
23
+ import "./chunk-PBE2LOSS.js";
21
24
  import {
22
25
  rubricPredictiveValidity
23
- } from "./chunk-X4UCIOTZ.js";
26
+ } from "./chunk-S5YLIBFX.js";
24
27
  import {
25
28
  evaluateInterimReleaseConfidence
26
29
  } from "./chunk-MAZ26DC7.js";
27
- import "./chunk-DPZAEKA6.js";
30
+ import "./chunk-IMGJZBFG.js";
31
+ import "./chunk-DPUHNQLN.js";
28
32
  import {
29
33
  benjaminiHochberg,
30
34
  wilcoxonSignedRank
31
- } from "./chunk-PJQFMIOX.js";
35
+ } from "./chunk-MHELPNRP.js";
32
36
  import {
33
37
  observationsFromRunRecords,
34
38
  thompsonCurriculum,
@@ -39,7 +43,9 @@ import "./chunk-VI2UW6B6.js";
39
43
  import "./chunk-TT4KNT67.js";
40
44
  import "./chunk-PC4UYEBM.js";
41
45
  import "./chunk-VQMK5FMP.js";
42
- import "./chunk-IILEIWGW.js";
46
+ import {
47
+ runTaskScore
48
+ } from "./chunk-NL33OXEP.js";
43
49
  import "./chunk-MA6HLL3S.js";
44
50
  import "./chunk-VSMTAMNK.js";
45
51
  import {
@@ -366,13 +372,19 @@ import { dirname } from "path";
366
372
  async function toDpoRows(triples, lookups) {
367
373
  const out = [];
368
374
  for (const t of triples) {
369
- const [prompt, chosen, rejected] = await Promise.all([
375
+ const [chosenPrompt, rejectedPrompt, chosen, rejected] = await Promise.all([
370
376
  Promise.resolve(lookups.promptOf(t.chosenRunId)),
377
+ Promise.resolve(lookups.promptOf(t.rejectedRunId)),
371
378
  Promise.resolve(lookups.completionOf(t.chosenRunId)),
372
379
  Promise.resolve(lookups.completionOf(t.rejectedRunId))
373
380
  ]);
381
+ if (chosenPrompt !== rejectedPrompt) {
382
+ throw new Error(
383
+ `toDpoRows: preference "${t.chosenRunId}"/"${t.rejectedRunId}" resolves to different prompts`
384
+ );
385
+ }
374
386
  out.push({
375
- prompt,
387
+ prompt: chosenPrompt,
376
388
  chosen,
377
389
  rejected,
378
390
  margin: t.marginScore,
@@ -394,29 +406,44 @@ function toDpoJsonl(rows) {
394
406
  }
395
407
  async function toGrpoRows(runs, lookups) {
396
408
  const rewardOf2 = lookups.rewardOf ?? defaultReward;
409
+ const promptHashByScenario = /* @__PURE__ */ new Map();
397
410
  const grouped = /* @__PURE__ */ new Map();
398
411
  for (const r of runs) {
399
- const sid = r.scenarioId ?? r.experimentId;
400
- const arr = grouped.get(sid) ?? [];
401
- arr.push(r);
402
- grouped.set(sid, arr);
412
+ const reward2 = rewardOf2(r);
413
+ if (!isTrainingRunEligible(r, reward2, lookups)) continue;
414
+ const existingPromptHash = promptHashByScenario.get(r.scenarioId);
415
+ if (existingPromptHash !== void 0 && existingPromptHash !== r.promptHash) {
416
+ throw new Error(
417
+ `toGrpoRows: scenario "${r.scenarioId}" contains mixed prompt identities "${existingPromptHash}" and "${r.promptHash}"`
418
+ );
419
+ }
420
+ promptHashByScenario.set(r.scenarioId, r.promptHash);
421
+ const key = `${r.scenarioId}\0${r.promptHash}`;
422
+ const group = grouped.get(key) ?? {
423
+ scenarioId: r.scenarioId,
424
+ promptHash: r.promptHash,
425
+ scored: []
426
+ };
427
+ group.scored.push({ run: r, reward: reward2 });
428
+ grouped.set(key, group);
403
429
  }
404
430
  const rows = [];
405
- for (const [scenarioId, group] of grouped.entries()) {
406
- if (group.length === 0) continue;
407
- const prompt = await Promise.resolve(lookups.promptOf(group[0].runId));
408
- const completions = [];
409
- const rewards = [];
410
- const runIds = [];
411
- for (const r of group) {
412
- const reward2 = rewardOf2(r);
413
- if (reward2 === null) continue;
414
- const completion = await Promise.resolve(lookups.completionOf(r.runId));
415
- completions.push(completion);
416
- rewards.push(reward2);
417
- runIds.push(r.runId);
431
+ for (const { scenarioId, promptHash, scored } of grouped.values()) {
432
+ if (scored.length < 2) continue;
433
+ const prompts = await Promise.all(
434
+ scored.map(({ run }) => Promise.resolve(lookups.promptOf(run.runId)))
435
+ );
436
+ const prompt = prompts[0];
437
+ if (prompts.some((value) => value !== prompt)) {
438
+ throw new Error(
439
+ `toGrpoRows: prompt identity "${promptHash}" resolves to different text within scenario "${scenarioId}"`
440
+ );
418
441
  }
419
- if (completions.length === 0) continue;
442
+ const completions = await Promise.all(
443
+ scored.map(({ run }) => Promise.resolve(lookups.completionOf(run.runId)))
444
+ );
445
+ const rewards = scored.map(({ reward: reward2 }) => reward2);
446
+ const runIds = scored.map(({ run }) => run.runId);
420
447
  rows.push({
421
448
  prompt,
422
449
  completions,
@@ -424,6 +451,7 @@ async function toGrpoRows(runs, lookups) {
424
451
  runIds,
425
452
  meta: {
426
453
  scenarioId,
454
+ promptHash,
427
455
  n: completions.length,
428
456
  meanReward: rewards.reduce((s, x) => s + x, 0) / rewards.length
429
457
  }
@@ -438,6 +466,8 @@ async function toSftRows(runs, lookups) {
438
466
  const include = lookups.include ?? (() => true);
439
467
  const rows = [];
440
468
  for (const r of runs) {
469
+ const score = runTaskScore(r);
470
+ if (!isTrainingRunEligible(r, score, lookups)) continue;
441
471
  if (!include(r)) continue;
442
472
  const system = lookups.systemOf?.(r);
443
473
  const [prompt, completion] = await Promise.all([
@@ -454,7 +484,7 @@ async function toSftRows(runs, lookups) {
454
484
  runId: r.runId,
455
485
  candidateId: r.candidateId,
456
486
  scenarioId: r.scenarioId,
457
- score: r.outcome.holdoutScore ?? r.outcome.searchScore,
487
+ score,
458
488
  model: r.model
459
489
  }
460
490
  });
@@ -510,20 +540,65 @@ function stepRewardsToJsonl(stepRewards) {
510
540
  return rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length > 0 ? "\n" : "");
511
541
  }
512
542
  function defaultReward(run) {
513
- const v = run.outcome.holdoutScore ?? run.outcome.searchScore;
514
- return typeof v === "number" && Number.isFinite(v) ? v : null;
543
+ return runTaskScore(run) ?? null;
544
+ }
545
+ function isTrainingRunEligible(run, quality, options = {}) {
546
+ const minimumQualityExclusive = options.minimumQualityExclusive ?? 0;
547
+ if (!Number.isFinite(minimumQualityExclusive)) {
548
+ throw new Error("minimumQualityExclusive must be finite");
549
+ }
550
+ if (quality === null || quality === void 0) return false;
551
+ if (!Number.isFinite(quality)) {
552
+ throw new Error(`training quality for run "${run.runId}" must be finite`);
553
+ }
554
+ if (quality <= minimumQualityExclusive) return false;
555
+ if (run.terminalOutcome !== "succeeded") return false;
556
+ if (run.failureClass !== void 0 || run.terminalFailureReason !== void 0) return false;
557
+ if (run.outcome.realness?.gated === true) return false;
558
+ if (run.splitTag === "search") return true;
559
+ return run.splitTag === "holdout" && options.allowHeldOutTrainingData === true;
515
560
  }
516
561
 
517
562
  // src/rl/dataset.ts
518
- function reward(r) {
519
- const v = r.outcome.holdoutScore ?? r.outcome.searchScore;
520
- return typeof v === "number" && Number.isFinite(v) ? v : null;
563
+ var DATASET_FORMATS = ["grpo", "sft", "dpo"];
564
+ var DATASET_FORMAT_SET = new Set(DATASET_FORMATS);
565
+ function validateDatasetFormats(value) {
566
+ if (!Array.isArray(value) || value.length === 0) {
567
+ throw new Error("buildRlDataset: formats must contain at least one of: grpo, sft, dpo");
568
+ }
569
+ const formats = [];
570
+ const seen = /* @__PURE__ */ new Set();
571
+ for (const format of value) {
572
+ if (!DATASET_FORMAT_SET.has(format)) {
573
+ throw new Error(
574
+ `buildRlDataset: unsupported format ${JSON.stringify(format)}; expected exactly one of: grpo, sft, dpo`
575
+ );
576
+ }
577
+ const datasetFormat = format;
578
+ if (seen.has(datasetFormat)) {
579
+ throw new Error(
580
+ `buildRlDataset: duplicate format ${JSON.stringify(datasetFormat)}; each format may be requested once`
581
+ );
582
+ }
583
+ seen.add(datasetFormat);
584
+ formats.push(datasetFormat);
585
+ }
586
+ return formats;
587
+ }
588
+ function reward(r, rewardOf2) {
589
+ const value = rewardOf2 ? rewardOf2(r) : runTaskScore(r) ?? null;
590
+ if (value !== null && !Number.isFinite(value)) {
591
+ throw new Error(`buildRlDataset: reward for run "${r.runId}" must be finite`);
592
+ }
593
+ return value;
521
594
  }
522
595
  function distinct(xs) {
523
596
  return [...new Set(xs)].sort();
524
597
  }
525
598
  function computeRewardStats(values) {
526
- if (values.length === 0) return { n: 0, mean: 0, median: 0, min: 0, max: 0, std: 0 };
599
+ if (values.length === 0) {
600
+ return { n: 0, mean: null, median: null, min: null, max: null, std: null };
601
+ }
527
602
  const sorted = [...values].sort((a, b) => a - b);
528
603
  const n = sorted.length;
529
604
  const mean = sorted.reduce((s, x) => s + x, 0) / n;
@@ -532,7 +607,7 @@ function computeRewardStats(values) {
532
607
  const variance = sorted.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
533
608
  return { n, mean, median, min: sorted[0], max: sorted[n - 1], std: Math.sqrt(variance) };
534
609
  }
535
- function computeStats(records) {
610
+ function computeStats(records, rewardOf2) {
536
611
  const splits = { search: 0, dev: 0, holdout: 0 };
537
612
  let inTok = 0;
538
613
  let outTok = 0;
@@ -542,12 +617,13 @@ function computeStats(records) {
542
617
  splits[r.splitTag] = (splits[r.splitTag] ?? 0) + 1;
543
618
  inTok += r.tokenUsage.input;
544
619
  outTok += r.tokenUsage.output;
545
- cost += r.costUsd;
546
- const rw = reward(r);
620
+ cost += r.costUsd ?? 0;
621
+ const rw = reward(r, rewardOf2);
547
622
  if (rw !== null) rewards.push(rw);
548
623
  }
549
624
  return {
550
625
  records: records.length,
626
+ scoredRecords: rewards.length,
551
627
  splits,
552
628
  reward: computeRewardStats(rewards),
553
629
  models: distinct(records.map((r) => r.model)),
@@ -561,16 +637,18 @@ async function buildRlDataset(records, lookups, config, preferences) {
561
637
  if (records.length === 0) {
562
638
  throw new Error("buildRlDataset: no records \u2014 refusing to package an empty dataset");
563
639
  }
564
- const formats = config.formats ?? ["grpo", "sft"];
640
+ const formats = validateDatasetFormats(config.formats === void 0 ? ["sft"] : config.formats);
565
641
  const files = {};
566
642
  const rowCounts = {};
567
643
  if (formats.includes("grpo")) {
568
644
  const rows = await toGrpoRows(records, lookups);
645
+ requireRows("grpo", rows.length);
569
646
  files["train.grpo.jsonl"] = toGrpoJsonl(rows);
570
647
  rowCounts.grpo = rows.length;
571
648
  }
572
649
  if (formats.includes("sft")) {
573
650
  const rows = await toSftRows(records, lookups);
651
+ requireRows("sft", rows.length);
574
652
  files["train.sft.jsonl"] = toSftJsonl(rows);
575
653
  rowCounts.sft = rows.length;
576
654
  }
@@ -579,23 +657,35 @@ async function buildRlDataset(records, lookups, config, preferences) {
579
657
  throw new Error("buildRlDataset: format 'dpo' requires `preferences` (triples + lookups)");
580
658
  }
581
659
  const rows = await toDpoRows(preferences.triples, preferences.lookups);
660
+ requireRows("dpo", rows.length);
582
661
  files["train.dpo.jsonl"] = toDpoJsonl(rows);
583
662
  rowCounts.dpo = rows.length;
584
663
  }
664
+ if (!Object.keys(files).some((name) => name.startsWith("train.") && name.endsWith(".jsonl"))) {
665
+ throw new Error("buildRlDataset: no trainer file was emitted");
666
+ }
585
667
  const manifest = {
586
668
  ...config,
587
669
  formats,
588
670
  rowCounts,
589
- stats: computeStats(records)
671
+ stats: computeStats(records, lookups.rewardOf)
590
672
  };
591
673
  files["manifest.json"] = `${JSON.stringify(manifest, null, 2)}
592
674
  `;
593
675
  files["DATASHEET.md"] = datasheetToMarkdown(manifest);
594
676
  return { manifest, files };
595
677
  }
678
+ function requireRows(format, rows) {
679
+ if (rows === 0) {
680
+ throw new Error(`buildRlDataset: requested '${format}' format produced no trainable rows`);
681
+ }
682
+ }
596
683
  function pct(x) {
597
684
  return `${(x * 100).toFixed(1)}%`;
598
685
  }
686
+ function stat(value) {
687
+ return value === null ? "n/a" : value.toFixed(3);
688
+ }
599
689
  function datasheetToMarkdown(m) {
600
690
  const s = m.stats;
601
691
  const total = s.records || 1;
@@ -604,31 +694,32 @@ function datasheetToMarkdown(m) {
604
694
  return [
605
695
  `# Dataset: ${m.name} \`v${m.version}\``,
606
696
  "",
607
- `**Domain:** ${m.domain} \xB7 **Created:** ${m.createdAtIso} \xB7 **License:** ${m.license}`,
697
+ `**Domain:** ${m.domain} | **Created:** ${m.createdAtIso} | **License:** ${m.license}`,
608
698
  "",
609
699
  "## Reward provenance",
610
- `- **Kind:** ${m.reward.kind}${deterministic ? " \u2705 (decidable \u2014 not judge-noise)" : ""}`,
700
+ `- **Kind:** ${m.reward.kind}${deterministic ? " (decidable, not judge noise)" : ""}`,
611
701
  `- **Source:** ${m.reward.source}`,
612
702
  `- **Description:** ${m.reward.description}`,
613
703
  "",
614
704
  "## Composition",
615
705
  `- **Records (trajectories):** ${s.records}`,
706
+ `- **Scored records:** ${s.scoredRecords}`,
616
707
  `- **Formats:** ${m.formats.map((f) => `${f} (${m.rowCounts[f] ?? 0} rows)`).join(", ")}`,
617
708
  "- **Splits:**",
618
709
  splitLines,
619
710
  "",
620
711
  "## Reward distribution",
621
- `- n=${s.reward.n} \xB7 mean=${s.reward.mean.toFixed(3)} \xB7 median=${s.reward.median.toFixed(3)} \xB7 min=${s.reward.min.toFixed(3)} \xB7 max=${s.reward.max.toFixed(3)} \xB7 std=${s.reward.std.toFixed(3)}`,
712
+ `- n=${s.reward.n} | mean=${stat(s.reward.mean)} | median=${stat(s.reward.median)} | min=${stat(s.reward.min)} | max=${stat(s.reward.max)} | std=${stat(s.reward.std)}`,
622
713
  "",
623
714
  "## Provenance",
624
715
  `- **Models:** ${s.models.join(", ")}`,
625
716
  `- **Prompt/agent versions (sha256):** ${s.promptHashes.length} distinct`,
626
717
  `- **Commits:** ${s.commitShas.join(", ")}`,
627
- `- **Tokens:** ${s.totalTokens.input} in / ${s.totalTokens.output} out \xB7 **Cost:** $${s.totalCostUsd.toFixed(2)}`,
718
+ `- **Tokens:** ${s.totalTokens.input} in / ${s.totalTokens.output} out | **Cost:** $${s.totalCostUsd.toFixed(2)}`,
628
719
  "",
629
720
  "## Quality gates",
630
721
  `- Contamination probe: ${m.qualityGates?.contaminationProbe ?? "not-run"}`,
631
- `- Dedup: ${m.qualityGates?.dedup ? "yes" : "no"} \xB7 Verifiable-reward filter: ${m.qualityGates?.verifiableRewardFilter ? "yes" : "no"}`,
722
+ `- Dedup: ${m.qualityGates?.dedup ? "yes" : "no"} | Verifiable-reward filter: ${m.qualityGates?.verifiableRewardFilter ? "yes" : "no"}`,
632
723
  "",
633
724
  "## Recommended uses",
634
725
  m.intendedUse,
@@ -640,7 +731,7 @@ function datasheetToMarkdown(m) {
640
731
  m.limitations,
641
732
  "",
642
733
  "## Token rendering",
643
- "For RL/SFT training, tokenize with the per-model renderer (DeepSeek-V3 / Kimi-K2 / Qwen3) to preserve token identity and per-token loss masks across tool-call turns \u2014 see `renderers` (PrimeIntellect). The `messages` / `completions` here are the renderer input.",
734
+ "For RL/SFT training, tokenize with the per-model renderer (DeepSeek-V3 / Kimi-K2 / Qwen3) to preserve token identity and per-token loss masks across tool-call turns. See `renderers` (PrimeIntellect). The `messages` / `completions` here are the renderer input.",
644
735
  ""
645
736
  ].join("\n");
646
737
  }
@@ -675,21 +766,27 @@ function readCorpus(corpusPath) {
675
766
  return out;
676
767
  }
677
768
  function rewardOf(r) {
678
- const v = r.outcome.holdoutScore ?? r.outcome.searchScore;
679
- return typeof v === "number" && Number.isFinite(v) ? v : 0;
769
+ return runTaskScore(r) ?? null;
680
770
  }
681
771
  async function buildDatasetFromCorpus(corpusPath, config, opts = {}) {
682
772
  let records = readCorpus(corpusPath).filter(
683
773
  (r) => typeof r.prompt === "string" && typeof r.completion === "string"
684
774
  );
685
775
  if (opts.splits) records = records.filter((r) => opts.splits.includes(r.splitTag));
686
- if (opts.minScore != null) records = records.filter((r) => rewardOf(r) >= opts.minScore);
776
+ records = records.filter((r) => rewardOf(r) !== null);
777
+ if (opts.minScore != null) {
778
+ records = records.filter((r) => {
779
+ const reward2 = rewardOf(r);
780
+ return reward2 !== null && reward2 >= opts.minScore;
781
+ });
782
+ }
687
783
  const text = new Map(
688
784
  records.map((r) => [r.runId, { prompt: r.prompt, completion: r.completion }])
689
785
  );
690
786
  const lookups = {
691
787
  promptOf: (id) => text.get(id)?.prompt ?? "",
692
- completionOf: (id) => text.get(id)?.completion ?? ""
788
+ completionOf: (id) => text.get(id)?.completion ?? "",
789
+ allowHeldOutTrainingData: opts.allowHeldOutTrainingData
693
790
  };
694
791
  return buildRlDataset(records, lookups, config);
695
792
  }
@@ -705,7 +802,7 @@ var PredictiveValidityResearcher = class {
705
802
  const threshold = this.opts.failureThreshold ?? 0.5;
706
803
  const failures = [];
707
804
  const failingRuns = runs.filter((r) => {
708
- const score = r.outcome.holdoutScore ?? r.outcome.searchScore;
805
+ const score = runTaskScore(r);
709
806
  return typeof score === "number" && score < threshold;
710
807
  });
711
808
  if (failingRuns.length === 0) return failures;
@@ -717,8 +814,11 @@ var PredictiveValidityResearcher = class {
717
814
  }
718
815
  for (const [candidateId, group] of grouped.entries()) {
719
816
  const meanScore = group.reduce((s, r) => {
720
- const x = r.outcome.holdoutScore ?? r.outcome.searchScore ?? 0;
721
- return s + x;
817
+ const score = runTaskScore(r);
818
+ if (score === void 0) {
819
+ throw new Error(`failing run ${r.runId} unexpectedly has no task score`);
820
+ }
821
+ return s + score;
722
822
  }, 0) / group.length;
723
823
  failures.push({
724
824
  code: `low-score-${candidateId}`,
@@ -788,15 +888,17 @@ var PredictiveValidityResearcher = class {
788
888
  baselineId: plan.baselineCandidateId,
789
889
  evidence: {
790
890
  productiveRuns: 0,
791
- medianPairedDelta: 0,
792
- pairedCI: { low: 0, high: 0 },
793
- pairedPValue: 1,
794
- searchScore: 0,
795
- holdoutScore: 0,
796
- overfitGap: 0,
797
- baselineOverfitGap: 0,
798
- medianCandidateCost: Number.NaN,
799
- medianBaselineCost: Number.NaN
891
+ unpairedCandidateRuns: 0,
892
+ unpairedBaselineRuns: 0,
893
+ medianPairedDelta: null,
894
+ pairedCI: null,
895
+ pairedPValue: null,
896
+ searchScore: null,
897
+ holdoutScore: null,
898
+ overfitGap: null,
899
+ baselineOverfitGap: null,
900
+ medianCandidateCost: null,
901
+ medianBaselineCost: null
800
902
  },
801
903
  reason: "predictive-validity researcher does not execute plans; the caller is expected to run the sweep and call rubricPredictiveValidity directly with the resulting RunRecord[].",
802
904
  rejectionCode: "few_runs"
@@ -837,21 +939,27 @@ var PredictiveValidityResearcher = class {
837
939
  };
838
940
 
839
941
  // src/rl/preferences.ts
840
- var SPLIT_TAG_DEFAULT = "holdout";
841
942
  var DEFAULT_REWARD = (run) => {
842
- const v = run.outcome.holdoutScore ?? run.outcome.searchScore;
843
- return typeof v === "number" && Number.isFinite(v) ? v : null;
943
+ return runTaskScore(run) ?? null;
844
944
  };
845
945
  function extractPreferences(runs, opts = {}) {
846
946
  const strategy = opts.strategy ?? "paired-by-scenario-and-seed";
847
947
  const minMargin = opts.minMargin ?? 0.05;
848
- const splitTag = opts.splitTag ?? SPLIT_TAG_DEFAULT;
948
+ const splitTag = opts.splitTag;
849
949
  const rewardOf2 = opts.rewardOf ?? DEFAULT_REWARD;
850
- const filtered = runs.filter((r) => r.splitTag === splitTag);
950
+ if (splitTag === "holdout" && opts.allowHeldOutTrainingData !== true) {
951
+ throw new Error(
952
+ 'extractPreferences: splitTag "holdout" requires allowHeldOutTrainingData: true'
953
+ );
954
+ }
955
+ if (splitTag === "dev") {
956
+ throw new Error('extractPreferences: splitTag "dev" is evaluation-only; train from "search"');
957
+ }
851
958
  const scoredEntries = [];
852
- for (const run of filtered) {
959
+ for (const run of runs) {
960
+ if (splitTag !== void 0 && run.splitTag !== splitTag) continue;
853
961
  const s = rewardOf2(run);
854
- if (s === null) continue;
962
+ if (!isTrainingRunEligible(run, s, opts)) continue;
855
963
  scoredEntries.push({ run, score: s });
856
964
  }
857
965
  const pairs = [];
@@ -946,14 +1054,6 @@ function extractPreferences(runs, opts = {}) {
946
1054
  }
947
1055
  return { pairs, cellsInspected, pairsBelowMargin, cellsSingleton, strategy };
948
1056
  }
949
- function toTRLFormat(triples, promptOf) {
950
- return triples.map((t) => ({
951
- prompt: promptOf(t.meta.chosenPromptHash),
952
- chosen: t.meta.chosenPromptHash,
953
- // caller substitutes the model output via the runId map
954
- rejected: t.meta.rejectedPromptHash
955
- }));
956
- }
957
1057
  function toAnthropicFormat(triples) {
958
1058
  return triples.map((t) => ({
959
1059
  scenarioId: t.scenarioId,
@@ -989,11 +1089,7 @@ function makePair(a, b, scenarioId, minMargin) {
989
1089
  };
990
1090
  }
991
1091
  function scenarioOf(run) {
992
- if (typeof run.scenarioId === "string" && run.scenarioId.length > 0) return run.scenarioId;
993
- const fromRaw = run.outcome.raw.scenario_id;
994
- if (typeof fromRaw === "number" && Number.isFinite(fromRaw)) return String(fromRaw);
995
- if (typeof fromRaw === "string") return fromRaw;
996
- return run.experimentId;
1092
+ return run.scenarioId;
997
1093
  }
998
1094
 
999
1095
  // src/rl/process-reward.ts
@@ -1123,16 +1219,17 @@ function prmTrainingPairs(stepRewardsByRun, opts = {}) {
1123
1219
 
1124
1220
  // src/rl/rl-campaign.ts
1125
1221
  async function runRLCampaign(opts) {
1126
- const campaign = await runEvalCampaign(opts);
1222
+ const splitTag = opts.splitTag ?? "search";
1223
+ const campaign = await runEvalCampaign({ ...opts, splitTag });
1127
1224
  const rewardSignals = extractVerifiableRewardsFromRecords(
1128
1225
  campaign.runs,
1129
1226
  opts.verifiableReward ?? {}
1130
1227
  );
1131
1228
  const preferences = extractPreferences(campaign.runs, {
1229
+ ...opts.preferences,
1132
1230
  strategy: opts.preferences?.strategy ?? "paired-by-scenario-and-seed",
1133
1231
  minMargin: opts.preferences?.minMargin ?? 0.05,
1134
- splitTag: opts.preferences?.splitTag ?? opts.splitTag ?? "holdout",
1135
- rewardOf: opts.preferences?.rewardOf
1232
+ splitTag: opts.preferences?.splitTag ?? splitTag
1136
1233
  });
1137
1234
  let interimConfidence = null;
1138
1235
  if (opts.report?.comparator) {
@@ -1192,17 +1289,17 @@ function collectPairedDeltaSeries(runs, comparator) {
1192
1289
  const baseline = /* @__PURE__ */ new Map();
1193
1290
  for (const r of runs) {
1194
1291
  if (r.candidateId !== comparator) continue;
1195
- const sid = r.scenarioId ?? r.experimentId;
1196
- const score = r.outcome.holdoutScore ?? r.outcome.searchScore;
1197
- if (typeof score !== "number" || !Number.isFinite(score)) continue;
1292
+ const sid = r.scenarioId;
1293
+ const score = runTaskScore(r);
1294
+ if (score === void 0) continue;
1198
1295
  baseline.set(`${sid}::${r.seed}`, score);
1199
1296
  }
1200
1297
  const byCandidate = /* @__PURE__ */ new Map();
1201
1298
  for (const r of runs) {
1202
1299
  if (r.candidateId === comparator) continue;
1203
- const sid = r.scenarioId ?? r.experimentId;
1204
- const score = r.outcome.holdoutScore ?? r.outcome.searchScore;
1205
- if (typeof score !== "number" || !Number.isFinite(score)) continue;
1300
+ const sid = r.scenarioId;
1301
+ const score = runTaskScore(r);
1302
+ if (score === void 0) continue;
1206
1303
  const baseScore = baseline.get(`${sid}::${r.seed}`);
1207
1304
  if (typeof baseScore !== "number") continue;
1208
1305
  const arr = byCandidate.get(r.candidateId) ?? [];
@@ -1238,62 +1335,68 @@ function buildSummary(args) {
1238
1335
  function campaignToRunRecords(campaign, ctx) {
1239
1336
  const splitTag = ctx.splitTag ?? "search";
1240
1337
  const candidateId = ctx.candidateId ?? campaign.manifestHash;
1241
- return campaign.cells.map((cell) => {
1242
- const composites = Object.values(cell.judgeScores).map((s) => s.composite);
1243
- const score = composites.length > 0 ? composites.reduce((a, b) => a + b, 0) / composites.length : 0;
1244
- const raw = { rep: cell.rep, duration_ms: cell.durationMs };
1245
- for (const judge of Object.values(cell.judgeScores)) {
1246
- for (const [dim, value] of Object.entries(judge.dimensions)) {
1247
- if (Number.isFinite(value)) raw[`dim.${dim}`] = value;
1248
- }
1249
- }
1250
- if (typeof cell.generation === "number") raw.generation = cell.generation;
1251
- const outcome = { raw };
1252
- if (splitTag === "holdout") outcome.holdoutScore = score;
1253
- else outcome.searchScore = score;
1254
- return {
1338
+ return campaign.cells.map(
1339
+ (cell) => campaignCellToRunRecord(cell, {
1255
1340
  runId: cell.cellId,
1256
1341
  experimentId: ctx.experimentId,
1257
1342
  candidateId,
1258
- seed: cell.seed,
1259
1343
  model: ctx.model,
1260
1344
  promptHash: ctx.promptHash,
1261
1345
  configHash: ctx.configHash,
1262
1346
  commitSha: ctx.commitSha,
1263
- wallMs: cell.durationMs,
1264
- costUsd: Number.isFinite(cell.costUsd) ? cell.costUsd : ctx.defaultCostUsd ?? 0,
1265
- tokenUsage: { input: 0, output: 0 },
1266
- outcome,
1267
- failureMode: cell.error ? "cell_error" : void 0,
1268
1347
  splitTag,
1269
- scenarioId: cell.scenarioId
1270
- };
1271
- });
1348
+ defaultCostUsd: ctx.defaultCostUsd
1349
+ })
1350
+ );
1272
1351
  }
1273
1352
  function verificationReportToRunRecord(report, ctx, opts = {}) {
1274
1353
  const splitTag = ctx.splitTag ?? "search";
1275
1354
  const runId = opts.runId ?? `run-${ctx.candidateId}-${ctx.experimentId}-${report.startedAt}`;
1355
+ const hasValidLayerMeasurement = report.layers.some(hasValidTaskMeasurement);
1356
+ const taskScore = hasValidLayerMeasurement && isValidScore(report.taskScore) ? report.taskScore : void 0;
1357
+ let executionErrorCount = 0;
1358
+ let judgeErrorCount = 0;
1359
+ let layerErrorCount = 0;
1360
+ let layerTimeoutCount = 0;
1361
+ let unscoredLayerCount = 0;
1276
1362
  const raw = {
1277
1363
  pass_count: report.passCount,
1278
1364
  fail_count: report.failCount,
1279
1365
  error_count: report.errorCount,
1280
1366
  skipped_count: report.skippedCount,
1281
1367
  duration_ms: report.durationMs,
1282
- blended_score: report.blendedScore
1368
+ execution_error_count: 0
1283
1369
  };
1284
1370
  for (const layer of report.layers) {
1285
- if (typeof layer.score === "number") raw[`layer.${layer.layer}`] = layer.score;
1371
+ if (hasValidTaskMeasurement(layer)) raw[`layer.${layer.layer}`] = layer.score;
1372
+ else unscoredLayerCount++;
1286
1373
  raw[`layer_${layer.layer}_pass`] = layer.status === "pass" ? 1 : 0;
1374
+ if (layer.status === "error" || layer.status === "timeout") {
1375
+ if (layer.errorSource === "judge") judgeErrorCount++;
1376
+ else executionErrorCount++;
1377
+ if (layer.status === "error") layerErrorCount++;
1378
+ else layerTimeoutCount++;
1379
+ }
1287
1380
  if (layer.diagnostics) {
1288
1381
  for (const [k, v] of Object.entries(layer.diagnostics)) {
1289
1382
  if (typeof v === "number" && Number.isFinite(v)) raw[`layer.${layer.layer}.${k}`] = v;
1290
1383
  }
1291
1384
  }
1292
1385
  }
1293
- const firstFail = report.layers.find((l) => l.status === "fail" || l.status === "error");
1386
+ raw.execution_error_count = executionErrorCount;
1387
+ if (judgeErrorCount > 0) raw.judge_error_count = judgeErrorCount;
1388
+ if (layerErrorCount > 0) raw.layer_error_count = layerErrorCount;
1389
+ if (layerTimeoutCount > 0) raw.layer_timeout_count = layerTimeoutCount;
1390
+ if (unscoredLayerCount > 0) raw.unscored_layer_count = unscoredLayerCount;
1391
+ if (taskScore !== void 0) raw.blended_score = taskScore;
1392
+ const firstScoredFailure = report.layers.find(
1393
+ (layer) => layer.status === "fail" && hasValidTaskMeasurement(layer)
1394
+ );
1294
1395
  const outcome = { raw };
1295
- if (splitTag === "holdout") outcome.holdoutScore = report.blendedScore;
1296
- else outcome.searchScore = report.blendedScore;
1396
+ if (taskScore !== void 0) {
1397
+ if (splitTag === "holdout") outcome.holdoutScore = taskScore;
1398
+ else outcome.searchScore = taskScore;
1399
+ }
1297
1400
  return {
1298
1401
  runId,
1299
1402
  experimentId: ctx.experimentId,
@@ -1304,19 +1407,21 @@ function verificationReportToRunRecord(report, ctx, opts = {}) {
1304
1407
  configHash: ctx.configHash,
1305
1408
  commitSha: ctx.commitSha,
1306
1409
  wallMs: report.durationMs,
1307
- costUsd: ctx.defaultCostUsd ?? 0,
1410
+ costUsd: ctx.defaultCostUsd ?? null,
1411
+ costProvenance: ctx.defaultCostUsd === void 0 ? { kind: "uncaptured", usd: null } : { kind: "estimated", usd: ctx.defaultCostUsd },
1308
1412
  tokenUsage: { input: 0, output: 0 },
1413
+ terminalOutcome: "succeeded",
1309
1414
  outcome,
1310
- failureMode: firstFail ? failureModeFromLayer(firstFail) : void 0,
1415
+ failureMode: firstScoredFailure ? `layer_${firstScoredFailure.layer}_fail` : void 0,
1311
1416
  splitTag,
1312
1417
  scenarioId: ctx.scenarioId
1313
1418
  };
1314
1419
  }
1315
- function failureModeFromLayer(layer) {
1316
- if (layer.status === "error") return `layer_${layer.layer}_error`;
1317
- if (layer.status === "fail") return `layer_${layer.layer}_fail`;
1318
- if (layer.status === "timeout") return `layer_${layer.layer}_timeout`;
1319
- return `layer_${layer.layer}_${layer.status}`;
1420
+ function hasValidTaskMeasurement(layer) {
1421
+ return (layer.status === "pass" || layer.status === "fail") && isValidScore(layer.score);
1422
+ }
1423
+ function isValidScore(score) {
1424
+ return typeof score === "number" && Number.isFinite(score) && score >= 0 && score <= 1;
1320
1425
  }
1321
1426
 
1322
1427
  // src/rl/sim-fidelity.ts
@@ -1689,6 +1794,7 @@ export {
1689
1794
  fitBradleyTerry,
1690
1795
  injectIrrelevantClause,
1691
1796
  inverseProbabilityWeighting,
1797
+ isTrainingRunEligible,
1692
1798
  jsDivergence,
1693
1799
  observationsFromRunRecords,
1694
1800
  offPolicyEstimateAll,
@@ -1718,7 +1824,7 @@ export {
1718
1824
  toPrmRows,
1719
1825
  toSftJsonl,
1720
1826
  toSftRows,
1721
- toTRLFormat,
1827
+ validateDatasetFormats,
1722
1828
  varianceBasedCurriculum,
1723
1829
  verificationReportToRunRecord
1724
1830
  };