@tangle-network/agent-eval 0.119.1 → 0.120.1

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 (33) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +0 -26
  3. package/dist/benchmarks/index.d.ts +11 -5
  4. package/dist/benchmarks/index.js +4 -4
  5. package/dist/campaign/index.d.ts +93 -37
  6. package/dist/campaign/index.js +23 -7
  7. package/dist/{chunk-PSXJ32SR.js → chunk-32BZXMSO.js} +229 -118
  8. package/dist/chunk-32BZXMSO.js.map +1 -0
  9. package/dist/{chunk-XJ7JVCHB.js → chunk-3A246TSA.js} +2 -2
  10. package/dist/{chunk-XMBOU5W7.js → chunk-JN2FCO5W.js} +4 -5
  11. package/dist/chunk-JN2FCO5W.js.map +1 -0
  12. package/dist/{chunk-D7AEXSM5.js → chunk-PICTDURQ.js} +2 -2
  13. package/dist/{chunk-PAHNGS65.js → chunk-XDIRG3TO.js} +175 -11
  14. package/dist/chunk-XDIRG3TO.js.map +1 -0
  15. package/dist/contract/index.d.ts +55 -28
  16. package/dist/contract/index.js +351 -62
  17. package/dist/contract/index.js.map +1 -1
  18. package/dist/index.d.ts +22 -8
  19. package/dist/index.js +9 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/openapi.json +1 -1
  22. package/dist/rl.d.ts +11 -5
  23. package/dist/{run-campaign-OBXSN5WK.js → run-campaign-HNFPJET4.js} +2 -2
  24. package/package.json +1 -6
  25. package/dist/chunk-PAHNGS65.js.map +0 -1
  26. package/dist/chunk-PSXJ32SR.js.map +0 -1
  27. package/dist/chunk-XMBOU5W7.js.map +0 -1
  28. package/dist/primeintellect/index.d.ts +0 -311
  29. package/dist/primeintellect/index.js +0 -348
  30. package/dist/primeintellect/index.js.map +0 -1
  31. /package/dist/{chunk-XJ7JVCHB.js.map → chunk-3A246TSA.js.map} +0 -0
  32. /package/dist/{chunk-D7AEXSM5.js.map → chunk-PICTDURQ.js.map} +0 -0
  33. /package/dist/{run-campaign-OBXSN5WK.js.map → run-campaign-HNFPJET4.js.map} +0 -0
@@ -13,12 +13,15 @@ import {
13
13
  import {
14
14
  analyzeRuns,
15
15
  summarizeExecution
16
- } from "../chunk-XJ7JVCHB.js";
16
+ } from "../chunk-3A246TSA.js";
17
17
  import {
18
18
  REFERENCE_EQUIVALENCE_INPUT_LIMITS,
19
19
  REFERENCE_EQUIVALENCE_JUDGE_VERSION,
20
20
  buildEvidenceVector,
21
+ buildLoopProvenanceRecord,
22
+ campaignBreakdown,
21
23
  campaignMeanComposite,
24
+ canonicalDigest,
22
25
  composeGate,
23
26
  createReferenceEquivalenceJudge,
24
27
  defaultProductionGate,
@@ -26,24 +29,30 @@ import {
26
29
  evolutionaryProposer,
27
30
  gepaProposer,
28
31
  heldOutGate,
32
+ loopProvenanceArgsFromResult,
29
33
  pairArms,
30
34
  paretoPolicy,
31
35
  paretoSignificanceGate,
32
36
  powerPreflight,
37
+ renderSurfaceDiff,
33
38
  runEval,
34
39
  runImprovementLoop,
35
40
  runReferenceEquivalenceJudge,
36
41
  surfaceContentHash,
37
- surfaceHash
38
- } from "../chunk-PSXJ32SR.js";
42
+ surfaceHash,
43
+ verifyLoopProvenanceRecord
44
+ } from "../chunk-32BZXMSO.js";
39
45
  import {
40
- canonicalJson,
46
+ assertCampaignSplitIdentity,
47
+ assertRealAgentReceipts,
48
+ campaignCoverage,
49
+ campaignSplitDigest,
41
50
  createRunCostLedger,
42
51
  fsCampaignStorage,
43
52
  inMemoryCampaignStorage,
44
53
  resolveRunDir,
45
54
  runCampaign
46
- } from "../chunk-PAHNGS65.js";
55
+ } from "../chunk-XDIRG3TO.js";
47
56
  import {
48
57
  buildDefaultAnalystRegistry,
49
58
  createChatClient
@@ -60,7 +69,9 @@ import "../chunk-DPZAEKA6.js";
60
69
  import {
61
70
  pairedBootstrap
62
71
  } from "../chunk-PJQFMIOX.js";
63
- import "../chunk-JHOJHHU7.js";
72
+ import {
73
+ CostLedger
74
+ } from "../chunk-JHOJHHU7.js";
64
75
  import "../chunk-VI2UW6B6.js";
65
76
  import {
66
77
  recordAggregateMeasurements,
@@ -198,6 +209,7 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
198
209
  runDir,
199
210
  maxConcurrency,
200
211
  cellPlacement: opts.cellPlacement,
212
+ dispatchTimeoutMs: opts.dispatchTimeoutMs,
201
213
  costLedger,
202
214
  expectUsage,
203
215
  labeledStore: opts.labeledStore,
@@ -253,29 +265,17 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
253
265
  candidateCandidateId: "winner"
254
266
  });
255
267
  const durationMs = Date.now() - startedAt;
256
- const workerRecords = opts.collectWorkerRecords?.() ?? cellsToRunRecords(result.winnerOnHoldout.cells, "winner", runDir, result.winnerSurface);
257
268
  const { record: provenance } = await emitLoopProvenance({
258
- runId: `${runDir}#${startedAt}`,
259
- runDir,
260
- timestamp: new Date(startedAt).toISOString(),
261
- baselineSurface: opts.baselineSurface,
262
- winnerSurface: result.winnerSurface,
263
- winnerLabel: result.winnerLabel,
264
- winnerRationale: result.winnerRationale,
265
- diff: result.promotedDiff,
266
- baselineSearchComposite: campaignMeanComposite(result.baselineCampaign),
267
- generations: result.generations.map((g) => ({
268
- generationIndex: g.record.generationIndex,
269
- candidates: g.record.candidates,
270
- promoted: g.record.promoted,
271
- surfaces: g.surfaces.map((s) => ({ surfaceHash: s.surfaceHash, surface: s.surface }))
272
- })),
273
- gate: result.gateResult,
274
- baselineOnHoldout: result.baselineOnHoldout,
275
- winnerOnHoldout: result.winnerOnHoldout,
276
- workerRecords,
277
- totalCostUsd: totalCost,
278
- totalDurationMs: durationMs,
269
+ ...loopProvenanceArgsFromResult({
270
+ runId: `${runDir}#${startedAt}`,
271
+ runDir,
272
+ timestamp: new Date(startedAt).toISOString(),
273
+ baselineSurface: opts.baselineSurface,
274
+ result,
275
+ costReceipts: costLedger.list(),
276
+ totalCostUsd: totalCost,
277
+ totalDurationMs: durationMs
278
+ }),
279
279
  storage,
280
280
  hostedClient: opts.hostedTenant ? createHostedClient(opts.hostedTenant) : void 0
281
281
  });
@@ -423,7 +423,13 @@ function cellsToRunRecords(cells, candidateId, runId, surface) {
423
423
  commitSha: "cell",
424
424
  wallMs: cell.durationMs,
425
425
  costUsd: cell.costUsd,
426
- tokenUsage: { input: 0, output: 0 },
426
+ tokenUsage: {
427
+ input: cell.tokenUsage.input,
428
+ output: cell.tokenUsage.output,
429
+ ...cell.tokenUsage.reasoning === void 0 ? {} : { reasoning: cell.tokenUsage.reasoning },
430
+ ...cell.tokenUsage.cached === void 0 ? {} : { cached: cell.tokenUsage.cached },
431
+ ...cell.tokenUsage.cacheWrite === void 0 ? {} : { cacheWrite: cell.tokenUsage.cacheWrite }
432
+ },
427
433
  outcome: {
428
434
  holdoutScore: composite,
429
435
  raw: {},
@@ -541,17 +547,35 @@ function requirePositiveInteger(value, field) {
541
547
  }
542
548
 
543
549
  // src/contract/measured-comparison.ts
544
- import { createHash as createHash2 } from "crypto";
545
550
  import {
546
551
  agentImprovementMeasuredComparisonSchema
547
552
  } from "@tangle-network/agent-interface";
548
553
  function measuredComparisonFromSelfImproveResult(options) {
549
554
  const { result } = options;
555
+ verifyLoopProvenanceRecord(result.provenance);
550
556
  const power = result.power;
551
557
  if (!power) throw new Error("agent improvement comparison requires heldout power analysis");
552
558
  if (result.provenance.gate.reasons.length === 0) {
553
559
  throw new Error("agent improvement comparison requires measured decision reasons");
554
560
  }
561
+ const receiptLedger = new CostLedger({ receipts: result.receipts });
562
+ const receiptCost = receiptLedger.summary();
563
+ const receipts = receiptLedger.list();
564
+ assertRealAgentReceipts(receipts, { allowMixed: false });
565
+ const receiptsById = new Map(receipts.map((receipt) => [receipt.callId, receipt]));
566
+ const receiptIdsByCell = indexReceiptIdsByCell(receipts);
567
+ assertCompleteMeasuredCampaign(
568
+ result.raw.baselineOnHoldout,
569
+ "heldout baseline",
570
+ receiptsById,
571
+ receiptIdsByCell
572
+ );
573
+ assertCompleteMeasuredCampaign(
574
+ result.raw.winnerOnHoldout,
575
+ "heldout candidate",
576
+ receiptsById,
577
+ receiptIdsByCell
578
+ );
555
579
  const pairs = pairMeasuredCells(
556
580
  result.raw.baselineOnHoldout.cells,
557
581
  result.raw.winnerOnHoldout.cells
@@ -569,10 +593,103 @@ function measuredComparisonFromSelfImproveResult(options) {
569
593
  assertMeasuredNumber(result.lift, composite.delta, "heldout lift");
570
594
  assertMeasuredNumber(result.baseline.compositeMean, composite.baseline, "heldout baseline");
571
595
  assertMeasuredNumber(result.winner.compositeMean, composite.candidate, "heldout candidate");
572
- assertMeasuredNumber(result.provenance.heldOutLift, composite.delta, "provenance heldout lift");
573
- if (result.gateDecision !== result.provenance.gate.decision || power.n !== composite.n || power.confidence !== 0.95) {
574
- throw new Error("agent improvement measurement sources do not agree");
596
+ const baselineContentHash = surfaceContentHash(options.baselineSurface);
597
+ const candidateContentHash = surfaceContentHash(result.winner.surface);
598
+ const canonicalDiff = baselineContentHash === candidateContentHash ? "" : renderSurfaceDiff(result.raw.winnerSurface, options.baselineSurface);
599
+ assertMeasuredIdentity(result.gateDecision, result.raw.gateResult.decision, "raw decision");
600
+ assertMeasuredIdentity(
601
+ candidateContentHash,
602
+ surfaceContentHash(result.raw.winnerSurface),
603
+ "raw winner surface"
604
+ );
605
+ assertMeasuredIdentity(result.diff, canonicalDiff, "surface diff");
606
+ assertMeasuredIdentity(result.raw.promotedDiff, canonicalDiff, "raw surface diff");
607
+ if (result.gateDecision === "ship" && (baselineContentHash === candidateContentHash || result.diff.trim().length === 0)) {
608
+ throw new Error("a shipped improvement requires a changed surface and non-empty diff");
609
+ }
610
+ assertMeasuredIdentity(
611
+ canonicalDigest(power),
612
+ canonicalDigest(
613
+ powerPreflight({
614
+ baselineComposites: pairs.map(([cell]) => measuredComposite(cell)),
615
+ sharedScorerChannel: true
616
+ })
617
+ ),
618
+ "power analysis"
619
+ );
620
+ assertMeasuredNumber(power.n, composite.n, "power sample size");
621
+ assertMeasuredNumber(power.confidence, 0.95, "power confidence");
622
+ assertCompleteMeasuredCampaign(
623
+ result.raw.baselineCampaign,
624
+ "search baseline",
625
+ receiptsById,
626
+ receiptIdsByCell
627
+ );
628
+ assertGenerationMeasurements(result.raw.generations, receiptsById, receiptIdsByCell);
629
+ if (result.raw.neutralizedOnHoldout === void 0 !== (result.raw.neutralizedSurface === void 0)) {
630
+ throw new Error("neutralized surface and campaign must be supplied together");
631
+ }
632
+ if (result.raw.neutralizedOnHoldout) {
633
+ assertCompleteMeasuredCampaign(
634
+ result.raw.neutralizedOnHoldout,
635
+ "heldout neutralized",
636
+ receiptsById,
637
+ receiptIdsByCell
638
+ );
639
+ pairMeasuredCells(result.raw.baselineOnHoldout.cells, result.raw.neutralizedOnHoldout.cells);
640
+ }
641
+ const rebuiltProvenance = buildLoopProvenanceRecord(
642
+ loopProvenanceArgsFromResult({
643
+ runId: result.provenance.runId,
644
+ runDir: result.provenance.runDir,
645
+ timestamp: result.provenance.timestamp,
646
+ baselineSurface: options.baselineSurface,
647
+ result: result.raw,
648
+ costReceipts: receipts,
649
+ totalCostUsd: result.totalCostUsd,
650
+ totalDurationMs: result.durationMs
651
+ })
652
+ );
653
+ assertMeasuredIdentity(
654
+ result.provenance.recordDigest,
655
+ rebuiltProvenance.recordDigest,
656
+ "provenance record"
657
+ );
658
+ assertMeasuredIdentity(
659
+ options.benchmark.splitDigest,
660
+ rebuiltProvenance.evidence.holdout.splitDigest,
661
+ "benchmark heldout split"
662
+ );
663
+ const generationsExplored = result.raw.generations.length;
664
+ assertMeasuredNumber(result.generationsExplored, generationsExplored, "generation count");
665
+ assertMeasuredNumber(result.totalCostUsd, result.cost.totalCostUsd, "cost summary");
666
+ assertMeasuredIdentity(
667
+ canonicalDigest(result.cost),
668
+ canonicalDigest(result.raw.cost),
669
+ "raw cost summary"
670
+ );
671
+ if (!result.cost.accountingComplete) {
672
+ throw new Error(
673
+ `cost accounting is incomplete: ${result.cost.incompleteReasons.join("; ") || "unknown reason"}`
674
+ );
675
+ }
676
+ if (!receiptCost.accountingComplete) {
677
+ throw new Error(
678
+ `cost accounting is incomplete: ${receiptCost.incompleteReasons.join("; ") || "unknown reason"}`
679
+ );
575
680
  }
681
+ assertMeasuredIdentity(
682
+ canonicalDigest(result.cost),
683
+ canonicalDigest(receiptCost),
684
+ "cost receipt summary"
685
+ );
686
+ assertMeasuredNumber(result.totalCostUsd, receiptCost.totalCostUsd, "cost receipts");
687
+ assertMeasuredOptional(result.winner.label, result.raw.winnerLabel, "raw winner label");
688
+ assertMeasuredOptional(
689
+ result.winner.rationale,
690
+ result.raw.winnerRationale,
691
+ "raw winner rationale"
692
+ );
576
693
  return agentImprovementMeasuredComparisonSchema.parse({
577
694
  schemaVersion: 1,
578
695
  kind: "agent-improvement-measured-comparison",
@@ -598,8 +715,8 @@ function measuredComparisonFromSelfImproveResult(options) {
598
715
  } : {},
599
716
  decision: {
600
717
  outcome: result.gateDecision,
601
- reasons: result.provenance.gate.reasons,
602
- contributingChecks: result.provenance.gate.contributingGates.map((check) => ({
718
+ reasons: rebuiltProvenance.gate.reasons,
719
+ contributingChecks: rebuiltProvenance.gate.contributingGates.map((check) => ({
603
720
  name: check.name,
604
721
  passed: check.passed
605
722
  }))
@@ -615,20 +732,98 @@ function measuredComparisonFromSelfImproveResult(options) {
615
732
  },
616
733
  provenance: {
617
734
  kind: "agent-eval-loop",
618
- schema: result.provenance.schema,
619
- runId: result.provenance.runId,
620
- recordDigest: digest(result.provenance),
621
- baselineContentHash: result.provenance.baselineContentHash,
622
- candidateContentHash: result.provenance.winnerContentHash
735
+ schema: rebuiltProvenance.schema,
736
+ runId: rebuiltProvenance.runId,
737
+ recordDigest: rebuiltProvenance.recordDigest,
738
+ baselineContentHash,
739
+ candidateContentHash
623
740
  },
624
- diff: result.diff,
741
+ diff: canonicalDiff,
625
742
  evaluation: {
626
- generationsExplored: result.generationsExplored,
743
+ generationsExplored,
627
744
  durationMs: result.durationMs,
628
745
  totalCostUsd: result.totalCostUsd
629
746
  }
630
747
  });
631
748
  }
749
+ function assertGenerationMeasurements(generations, receiptsById, receiptIdsByCell) {
750
+ for (const generation of generations) {
751
+ for (const measured of generation.surfaces) {
752
+ const candidate = generation.record.candidates.find(
753
+ (entry) => entry.surfaceHash === measured.surfaceHash
754
+ );
755
+ if (!candidate) {
756
+ throw new Error(
757
+ `generation ${generation.record.generationIndex} is missing candidate ${measured.surfaceHash}`
758
+ );
759
+ }
760
+ const composite = campaignMeanComposite(measured.campaign);
761
+ const breakdown = campaignBreakdown(measured.campaign);
762
+ const coverage = campaignCoverage(
763
+ measured.campaign.cells,
764
+ measured.campaign.scenarios,
765
+ measured.campaign.reps,
766
+ true
767
+ );
768
+ assertScorableMeasuredCells(
769
+ measured.campaign,
770
+ coverage.scorableCellIds,
771
+ receiptsById,
772
+ receiptIdsByCell
773
+ );
774
+ assertMeasuredNumber(
775
+ candidate.composite,
776
+ composite,
777
+ `candidate ${measured.surfaceHash} composite`
778
+ );
779
+ if (!candidate.coverage) {
780
+ throw new Error(`candidate ${measured.surfaceHash} does not report campaign coverage`);
781
+ }
782
+ assertMeasuredIdentity(
783
+ canonicalDigest(candidate.coverage),
784
+ canonicalDigest({
785
+ expectedCells: coverage.expectedCellIds.length,
786
+ scorableCells: coverage.scorableCellIds.length,
787
+ unscorableCells: coverage.unscorableCells
788
+ }),
789
+ `candidate ${measured.surfaceHash} coverage`
790
+ );
791
+ assertMeasuredIdentity(
792
+ String(candidate.eligibleForPromotion),
793
+ String(coverage.complete),
794
+ `candidate ${measured.surfaceHash} eligibility`
795
+ );
796
+ assertMeasuredIdentity(
797
+ canonicalDigest(candidate.dimensions),
798
+ canonicalDigest(breakdown.dimensions),
799
+ `candidate ${measured.surfaceHash} dimensions`
800
+ );
801
+ assertMeasuredIdentity(
802
+ canonicalDigest(candidate.scenarios),
803
+ canonicalDigest(breakdown.scenarios),
804
+ `candidate ${measured.surfaceHash} scenarios`
805
+ );
806
+ }
807
+ }
808
+ }
809
+ function assertCompleteMeasuredCampaign(campaign, name, receiptsById, receiptIdsByCell) {
810
+ assertCampaignSplitIdentity(campaign.scenarios, campaign.reps, campaign.splitDigest);
811
+ const coverage = campaignCoverage(campaign.cells, campaign.scenarios, campaign.reps, true);
812
+ if (!coverage.complete) {
813
+ throw new Error(
814
+ `${name} is incomplete (${coverage.scorableCellIds.length}/${coverage.expectedCellIds.length} designed cells scorable)`
815
+ );
816
+ }
817
+ assertScorableMeasuredCells(campaign, coverage.scorableCellIds, receiptsById, receiptIdsByCell);
818
+ }
819
+ function assertScorableMeasuredCells(campaign, scorableCellIds, receiptsById, receiptIdsByCell) {
820
+ const cellsById = new Map(campaign.cells.map((cell) => [cell.cellId, cell]));
821
+ for (const cellId of scorableCellIds) {
822
+ const cell = cellsById.get(cellId);
823
+ if (!cell) throw new Error(`measured campaign is missing scorable cell ${cellId}`);
824
+ assertMeasuredCell(cell, receiptsById, receiptIdsByCell, campaign.runDir);
825
+ }
826
+ }
632
827
  function measuredObjectives(pairs) {
633
828
  const qualityColumns = /* @__PURE__ */ new Map();
634
829
  for (const [baseline, candidate] of pairs) {
@@ -673,19 +868,6 @@ function measuredObjectives(pairs) {
673
868
  function measuredCostObjective(pairs) {
674
869
  const cells = pairs.flat();
675
870
  for (const cell of cells) finiteMeasuredValue(cell.costUsd, "cost:cost");
676
- const reported = cells.some(
677
- (cell) => cell.costUsd !== 0 || cell.tokenUsage !== void 0 && (cell.tokenUsage.input > 0 || cell.tokenUsage.output > 0)
678
- );
679
- if (!reported) {
680
- return {
681
- kind: "cost",
682
- name: "cost",
683
- availability: "unavailable",
684
- reason: "heldout cells did not report model usage or cost",
685
- direction: "lower-is-better",
686
- unit: "usd"
687
- };
688
- }
689
871
  return measuredObjective(
690
872
  {
691
873
  kind: "cost",
@@ -705,7 +887,8 @@ function measuredComposite(cell) {
705
887
  return measuredMean(values);
706
888
  }
707
889
  function pairMeasuredCells(baselineCells, candidateCells) {
708
- const errors = [...baselineCells, ...candidateCells].filter((cell) => cell.error);
890
+ const cells = [...baselineCells, ...candidateCells];
891
+ const errors = cells.filter((cell) => cell.error);
709
892
  if (errors.length > 0) {
710
893
  throw new Error(
711
894
  `measured objectives cannot publish ${errors.length} errored heldout cells: ${errors.map((cell) => measuredCellKey(cell)).join(", ")}`
@@ -729,9 +912,88 @@ function pairMeasuredCells(baselineCells, candidateCells) {
729
912
  if (paired.pairs.length === 0 || paired.unpairedBaseline.length > 0 || paired.unpairedTreatment.length > 0) {
730
913
  throw new Error("measured objectives require the same non-empty paired heldout cells");
731
914
  }
732
- return paired.pairs.map(
733
- (pair) => [pair.baseline.cell, pair.treatment.cell]
915
+ return paired.pairs.map((pair) => {
916
+ const baseline = pair.baseline.cell;
917
+ const candidate = pair.treatment.cell;
918
+ if (baseline.seed !== candidate.seed) {
919
+ throw new Error(`heldout cell ${baseline.cellId} does not share one paired seed`);
920
+ }
921
+ return [baseline, candidate];
922
+ });
923
+ }
924
+ function assertMeasuredCell(cell, receiptsById, receiptIdsByCell, runDir) {
925
+ if (!cell.scenarioId.trim() || !Number.isSafeInteger(cell.rep) || cell.rep < 0 || !Number.isSafeInteger(cell.seed) || cell.cellId !== measuredCellKey(cell)) {
926
+ throw new Error(`heldout cell '${cell.cellId}' has an invalid scenario/rep identity`);
927
+ }
928
+ nonnegativeMeasuredValue(cell.costUsd, `heldout cell ${cell.cellId} cost`);
929
+ nonnegativeMeasuredValue(cell.durationMs, `heldout cell ${cell.cellId} latency`);
930
+ if (!cell.tokenUsage) {
931
+ throw new Error(`heldout cell ${cell.cellId} does not report token usage`);
932
+ }
933
+ nonnegativeMeasuredInteger(cell.tokenUsage.input, `heldout cell ${cell.cellId} input tokens`);
934
+ nonnegativeMeasuredInteger(cell.tokenUsage.output, `heldout cell ${cell.cellId} output tokens`);
935
+ if (cell.tokenUsage.cached !== void 0) {
936
+ nonnegativeMeasuredInteger(cell.tokenUsage.cached, `heldout cell ${cell.cellId} cached tokens`);
937
+ }
938
+ if (!Array.isArray(cell.costCallIds)) {
939
+ throw new Error(`heldout cell ${cell.cellId} does not identify its cost receipts`);
940
+ }
941
+ if (new Set(cell.costCallIds).size !== cell.costCallIds.length) {
942
+ throw new Error(`heldout cell ${cell.cellId} repeats a cost receipt`);
943
+ }
944
+ const linkedReceipts = cell.costCallIds.map((callId) => {
945
+ const receipt = receiptsById.get(callId);
946
+ if (!receipt)
947
+ throw new Error(`heldout cell ${cell.cellId} references missing cost receipt ${callId}`);
948
+ if (receipt.tags?.runDir !== runDir || receipt.tags.cellId !== cell.cellId || receipt.tags.scenarioId !== cell.scenarioId || receipt.tags.rep !== String(cell.rep)) {
949
+ throw new Error(`heldout cell ${cell.cellId} references a cost receipt from another cell`);
950
+ }
951
+ return receipt;
952
+ });
953
+ assertMeasuredIdentity(
954
+ canonicalDigest([...cell.costCallIds].sort()),
955
+ canonicalDigest(
956
+ [
957
+ ...receiptIdsByCell.get(receiptCellKey(runDir, cell.cellId, cell.scenarioId, cell.rep)) ?? []
958
+ ].sort()
959
+ ),
960
+ `heldout cell ${cell.cellId} cost receipt IDs`
734
961
  );
962
+ const agentReceipts = linkedReceipts.filter((receipt) => receipt.channel === "agent");
963
+ if (agentReceipts.some((receipt) => receipt.error)) {
964
+ throw new Error(`measured cell ${cell.cellId} links a failed agent receipt`);
965
+ }
966
+ assertRealAgentReceipts(agentReceipts, { allowMixed: false });
967
+ assertMeasuredNumber(
968
+ cell.costUsd,
969
+ agentReceipts.reduce((total, receipt) => total + receipt.costUsd, 0),
970
+ `heldout cell ${cell.cellId} cost receipts`
971
+ );
972
+ assertMeasuredNumber(
973
+ cell.tokenUsage.input,
974
+ agentReceipts.reduce((total, receipt) => total + receipt.inputTokens, 0),
975
+ `heldout cell ${cell.cellId} input receipts`
976
+ );
977
+ assertMeasuredNumber(
978
+ cell.tokenUsage.output,
979
+ agentReceipts.reduce((total, receipt) => total + receipt.outputTokens, 0),
980
+ `heldout cell ${cell.cellId} output receipts`
981
+ );
982
+ assertMeasuredNumber(
983
+ cell.tokenUsage.cached ?? 0,
984
+ agentReceipts.reduce((total, receipt) => total + (receipt.cachedTokens ?? 0), 0),
985
+ `heldout cell ${cell.cellId} cached receipts`
986
+ );
987
+ for (const [judge, score] of Object.entries(cell.judgeScores)) {
988
+ if (score.failed) {
989
+ throw new Error(`heldout cell ${cell.cellId} contains failed judge '${judge}'`);
990
+ }
991
+ finiteMeasuredValue(score.composite, `objective:${judge}`);
992
+ for (const [dimension, value] of Object.entries(score.dimensions)) {
993
+ finiteMeasuredValue(value, `dimension:${judge}:${dimension}`);
994
+ }
995
+ }
996
+ measuredComposite(cell);
735
997
  }
736
998
  function measuredObjective(identity, pairs, value) {
737
999
  const key = measuredObjectiveKey(identity);
@@ -788,6 +1050,16 @@ function finiteMeasuredValue(value, name) {
788
1050
  if (!Number.isFinite(value)) throw new Error(`measured objective '${name}' is not finite`);
789
1051
  return value;
790
1052
  }
1053
+ function nonnegativeMeasuredValue(value, name) {
1054
+ if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be non-negative`);
1055
+ return value;
1056
+ }
1057
+ function nonnegativeMeasuredInteger(value, name) {
1058
+ if (!Number.isSafeInteger(value) || value < 0) {
1059
+ throw new Error(`${name} must be a non-negative safe integer`);
1060
+ }
1061
+ return value;
1062
+ }
791
1063
  function measuredMean(values) {
792
1064
  if (values.length === 0) throw new Error("measured objective has no paired values");
793
1065
  return values.reduce((sum, value) => sum + value, 0) / values.length;
@@ -805,10 +1077,26 @@ function assertMeasuredNumber(actual, expected, name) {
805
1077
  throw new Error(`${name} does not agree across the measured comparison`);
806
1078
  }
807
1079
  }
808
- function digest(value) {
809
- const json = JSON.stringify(value);
810
- if (json === void 0) throw new Error("agent improvement provenance is not serializable");
811
- return `sha256:${createHash2("sha256").update(canonicalJson(JSON.parse(json))).digest("hex")}`;
1080
+ function assertMeasuredIdentity(actual, expected, name) {
1081
+ if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
1082
+ }
1083
+ function assertMeasuredOptional(actual, expected, name) {
1084
+ if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
1085
+ }
1086
+ function indexReceiptIdsByCell(receipts) {
1087
+ const idsByCell = /* @__PURE__ */ new Map();
1088
+ for (const receipt of receipts) {
1089
+ const tags = receipt.tags;
1090
+ if (!tags?.runDir || !tags.cellId || !tags.scenarioId || tags.rep === void 0) continue;
1091
+ const key = receiptCellKey(tags.runDir, tags.cellId, tags.scenarioId, tags.rep);
1092
+ const ids = idsByCell.get(key) ?? [];
1093
+ ids.push(receipt.callId);
1094
+ idsByCell.set(key, ids);
1095
+ }
1096
+ return idsByCell;
1097
+ }
1098
+ function receiptCellKey(runDir, cellId, scenarioId, rep) {
1099
+ return JSON.stringify([runDir, cellId, scenarioId, String(rep)]);
812
1100
  }
813
1101
 
814
1102
  // src/contract/eval-reporting-suite.ts
@@ -1326,6 +1614,7 @@ export {
1326
1614
  analyzeRuns,
1327
1615
  buildDefaultAnalystRegistry,
1328
1616
  buildEvidenceVector,
1617
+ campaignSplitDigest,
1329
1618
  composeGate,
1330
1619
  createChatClient,
1331
1620
  createReferenceEquivalenceJudge,