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