@tangle-network/agent-eval 0.191.0 → 0.192.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 (95) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/adapters/http.d.ts +2 -2
  3. package/dist/analyst/index.d.ts +6 -6
  4. package/dist/analyst/index.js +3 -3
  5. package/dist/{benchmark-command-3tZJnD82.js → benchmark-command-C458yEFK.js} +7 -7
  6. package/dist/{benchmark-command-3tZJnD82.js.map → benchmark-command-C458yEFK.js.map} +1 -1
  7. package/dist/benchmarks/index.d.ts +3 -3
  8. package/dist/benchmarks/index.js +3 -3
  9. package/dist/campaign/index.d.ts +4 -4
  10. package/dist/campaign/index.js +8 -9
  11. package/dist/{campaign-C8iWEF0M.js → campaign-BVQVBEfH.js} +12 -326
  12. package/dist/campaign-BVQVBEfH.js.map +1 -0
  13. package/dist/{campaign-evidence-BqjlTM3P.js → campaign-evidence-DcQiYhJI.js} +10 -45
  14. package/dist/campaign-evidence-DcQiYhJI.js.map +1 -0
  15. package/dist/cli.js +2 -2
  16. package/dist/contract/index.d.ts +6 -6
  17. package/dist/contract/index.js +6 -6
  18. package/dist/{default-registry-DK_9aVJf.d.ts → default-registry-DdqA_-_o.d.ts} +3 -3
  19. package/dist/{default-registry-DK_9aVJf.d.ts.map → default-registry-DdqA_-_o.d.ts.map} +1 -1
  20. package/dist/{define-agent-eval-BE43YTm4.js → define-agent-eval-DQuos3Aw.js} +8 -9
  21. package/dist/define-agent-eval-DQuos3Aw.js.map +1 -0
  22. package/dist/{define-agent-eval-P28y7mI1.d.ts → define-agent-eval-DcludNJS.d.ts} +13 -22
  23. package/dist/{define-agent-eval-P28y7mI1.d.ts.map → define-agent-eval-DcludNJS.d.ts.map} +1 -1
  24. package/dist/{dspy-rlm-engine-MfpwoEaj.js → dspy-rlm-engine-BX34vPVY.js} +2 -2
  25. package/dist/{dspy-rlm-engine-MfpwoEaj.js.map → dspy-rlm-engine-BX34vPVY.js.map} +1 -1
  26. package/dist/{engine-vNiM02Ub.d.ts → engine-C1DIAEpn.d.ts} +2 -2
  27. package/dist/{engine-vNiM02Ub.d.ts.map → engine-C1DIAEpn.d.ts.map} +1 -1
  28. package/dist/evaluation.d.ts +1 -1
  29. package/dist/experiment/index.d.ts +1 -1
  30. package/dist/experiment/index.js +1 -1
  31. package/dist/{external-optimizer-process-xFTliuiK.js → external-optimizer-process-D-Sab4v3.js} +3 -62
  32. package/dist/external-optimizer-process-D-Sab4v3.js.map +1 -0
  33. package/dist/{external-optimizer-subprocess-B234V9V0.js → external-optimizer-subprocess-kbqC-iWf.js} +64 -3
  34. package/dist/external-optimizer-subprocess-kbqC-iWf.js.map +1 -0
  35. package/dist/hosted/index.js +1 -1
  36. package/dist/{index-I4UJGRno.d.ts → index-Bj96W0jZ.d.ts} +28 -2
  37. package/dist/{index-I4UJGRno.d.ts.map → index-Bj96W0jZ.d.ts.map} +1 -1
  38. package/dist/{index-D-TN7ZRM.d.ts → index-DBoXFaz0.d.ts} +3 -3
  39. package/dist/{index-D-TN7ZRM.d.ts.map → index-DBoXFaz0.d.ts.map} +1 -1
  40. package/dist/{index-DH-MWH5f.d.ts → index-DNVzu9Vv.d.ts} +6 -6
  41. package/dist/{index-DH-MWH5f.d.ts.map → index-DNVzu9Vv.d.ts.map} +1 -1
  42. package/dist/{index-CzfnASj_.d.ts → index-DwQxdWve.d.ts} +338 -129
  43. package/dist/index-DwQxdWve.d.ts.map +1 -0
  44. package/dist/index.d.ts +9 -9
  45. package/dist/index.js +6 -6
  46. package/dist/jev.d.ts +1 -1
  47. package/dist/{journal-c3qY0SDP.js → journal-DvKCvjty.js} +94 -2
  48. package/dist/{journal-c3qY0SDP.js.map → journal-DvKCvjty.js.map} +1 -1
  49. package/dist/ledger-core/index.d.ts +2 -2
  50. package/dist/ledger-core/index.js +2 -2
  51. package/dist/{llm-judge-NJOHM0iA.js → llm-judge-Z6MkTkd0.js} +1819 -609
  52. package/dist/llm-judge-Z6MkTkd0.js.map +1 -0
  53. package/dist/{matrix-CgqdFm5N.d.ts → matrix-CQMMrEQ5.d.ts} +2 -2
  54. package/dist/{matrix-CgqdFm5N.d.ts.map → matrix-CQMMrEQ5.d.ts.map} +1 -1
  55. package/dist/multishot/golden/index.d.ts +1 -1
  56. package/dist/multishot/index.d.ts +2 -2
  57. package/dist/openapi.json +1 -1
  58. package/dist/{produced-state-CY-L7CqS.js → produced-state-CVmCPQDJ.js} +3 -3
  59. package/dist/{produced-state-CY-L7CqS.js.map → produced-state-CVmCPQDJ.js.map} +1 -1
  60. package/dist/rl.d.ts +1 -1
  61. package/dist/{search-ledger-CQ8Uy4FK.js → search-ledger-Ck7tok0h.js} +78 -12
  62. package/dist/search-ledger-Ck7tok0h.js.map +1 -0
  63. package/dist/{search-ledger-file-EvdDZRhv.js → search-ledger-file-DY0AXX3W.js} +2 -2
  64. package/dist/{search-ledger-file-EvdDZRhv.js.map → search-ledger-file-DY0AXX3W.js.map} +1 -1
  65. package/dist/{search-shipper-C55V7HE3.js → search-shipper-BG182p-U.js} +3 -3
  66. package/dist/{search-shipper-C55V7HE3.js.map → search-shipper-BG182p-U.js.map} +1 -1
  67. package/dist/{semantic-concept-judge-Bt7bXAAo.js → semantic-concept-judge-Bg-560H0.js} +3 -3
  68. package/dist/{semantic-concept-judge-Bt7bXAAo.js.map → semantic-concept-judge-Bg-560H0.js.map} +1 -1
  69. package/dist/{skillopt-optimization-method-DwO3TOAV.js → skillopt-optimization-method-DoayuM6D.js} +6 -6
  70. package/dist/{skillopt-optimization-method-DwO3TOAV.js.map → skillopt-optimization-method-DoayuM6D.js.map} +1 -1
  71. package/dist/{statistical-heldout-BED3wBtX.d.ts → statistical-heldout-C1Ej5Ce8.d.ts} +2 -2
  72. package/dist/{statistical-heldout-BED3wBtX.d.ts.map → statistical-heldout-C1Ej5Ce8.d.ts.map} +1 -1
  73. package/dist/{store-tool-spans-Cfh9XUCm.d.ts → store-tool-spans-ByLdU26j.d.ts} +3 -3
  74. package/dist/{store-tool-spans-Cfh9XUCm.d.ts.map → store-tool-spans-ByLdU26j.d.ts.map} +1 -1
  75. package/dist/{tool-groups-D-mkeN0N.d.ts → tool-groups-DiBi16ns.d.ts} +2 -2
  76. package/dist/tool-groups-DiBi16ns.d.ts.map +1 -0
  77. package/dist/trace-repair/index.d.ts +1 -1
  78. package/dist/traces.d.ts +2 -2
  79. package/dist/{types-ClK-sHs8.d.ts → types-ByZslIlr.d.ts} +2 -2
  80. package/dist/{types-ClK-sHs8.d.ts.map → types-ByZslIlr.d.ts.map} +1 -1
  81. package/dist/{types-BmQRs661.d.ts → types-C1CAD70_.d.ts} +14 -15
  82. package/dist/types-C1CAD70_.d.ts.map +1 -0
  83. package/docs/campaign-proposers.md +14 -16
  84. package/docs/search-ledger.md +62 -1
  85. package/package.json +1 -1
  86. package/dist/campaign-C8iWEF0M.js.map +0 -1
  87. package/dist/campaign-evidence-BqjlTM3P.js.map +0 -1
  88. package/dist/define-agent-eval-BE43YTm4.js.map +0 -1
  89. package/dist/external-optimizer-process-xFTliuiK.js.map +0 -1
  90. package/dist/external-optimizer-subprocess-B234V9V0.js.map +0 -1
  91. package/dist/index-CzfnASj_.d.ts.map +0 -1
  92. package/dist/llm-judge-NJOHM0iA.js.map +0 -1
  93. package/dist/search-ledger-CQ8Uy4FK.js.map +0 -1
  94. package/dist/tool-groups-D-mkeN0N.d.ts.map +0 -1
  95. package/dist/types-BmQRs661.d.ts.map +0 -1
@@ -1,21 +1,24 @@
1
1
  import { i as JudgeError, s as ValidationError } from "./errors-Dngq5h35.js";
2
2
  import { a as hashCanonical, i as compareCodeUnits, r as canonicalString, t as LEDGER_HASH_PATTERN } from "./canonical-BsZVgBhW.js";
3
+ import { t as mulberry32 } from "./random-Dn5fPWkt.js";
3
4
  import { o as summarizeNumberSeries, s as weightedComposite, t as confidenceInterval } from "./descriptive-1V17A-qa.js";
4
- import { t as decidePairedPromotion } from "./paired-promotion-decision-6cn3bLc3.js";
5
+ import { n as DECISION_PAIRED_DELTA_STATISTIC } from "./paired-tests-C8iCsioC.js";
6
+ import { i as pairedDeltaTest, r as minimumPairsForPairedDeltaTest, t as decidePairedPromotion } from "./paired-promotion-decision-6cn3bLc3.js";
5
7
  import { a as heldoutSignificance, i as dimensionRegressions, o as pairHoldout } from "./statistical-heldout-GcLIPxk4.js";
6
8
  import { i as modelHasSnapshot, l as searchCellRunId } from "./run-record-BRlM99rX.js";
7
9
  import { n as contentHash } from "./verdict-cache-BQEsVy9n.js";
8
10
  import { r as projectCampaignCellQuality, t as campaignCellCostProvenance } from "./run-record-tNnlQIrD.js";
9
11
  import { i as CostLedger, t as CostAccountingIncompleteError } from "./cost-ledger-_IbkejCF.js";
10
- import { A as FinalEvidenceConflictError, B as surfaceHashMatches, C as campaignMeanCompositeOrNull, G as assertCampaignSplitIdentity, I as renderSurfaceDiff, J as campaignScenarioIdentity, K as assertCompleteCampaign, L as surfaceContentHash, R as surfaceDispatchRef, S as campaignMeanComposite, V as BackendIntegrityError, W as assertCampaignDesign, Y as campaignSplitDigest, Z as formatCoverageFailures, b as assertFiniteRankKey, j as FinalEvidenceError, q as campaignCoverage, t as createCampaignEvidenceReceipt, w as compareRankKeys, x as campaignBreakdown, z as surfaceHash } from "./campaign-evidence-BqjlTM3P.js";
11
- import { p as mapConcurrent } from "./journal-c3qY0SDP.js";
12
+ import { F as surfaceContentHash, G as campaignCoverage, H as assertCampaignDesign, I as surfaceDispatchRef, K as campaignScenarioIdentity, L as surfaceHash, O as FinalEvidenceConflictError, P as renderSurfaceDiff, R as surfaceHashMatches, S as campaignMeanCompositeOrNull, U as assertCampaignSplitIdentity, W as assertCompleteCampaign, Y as formatCoverageFailures, b as campaignBreakdown, k as FinalEvidenceError, q as campaignSplitDigest, t as createCampaignEvidenceReceipt, x as campaignMeanComposite, z as BackendIntegrityError } from "./campaign-evidence-DcQiYhJI.js";
13
+ import { m as mapConcurrent } from "./journal-DvKCvjty.js";
12
14
  import { t as deepFreezeCanonicalJson } from "./deep-freeze-CurcOQg3.js";
13
- import { S as fsCampaignStorage, g as isRecord, h as isExternalTextCandidate, x as createRunCostLedger } from "./external-optimizer-subprocess-B234V9V0.js";
15
+ import { a as SearchLedgerError } from "./search-ledger-file-DY0AXX3W.js";
16
+ import { C as fsCampaignStorage, S as createRunCostLedger, g as isRecord, h as isExternalTextCandidate, x as acquireSingleRunLock } from "./external-optimizer-subprocess-kbqC-iWf.js";
14
17
  import { r as summarizeEvaluationUnits, t as defineEvaluationClaim } from "./claim-V9Zqbq0J.js";
15
18
  import { f as redactText, n as REDACTION_VERSION, o as detectCredential, s as detectPersonalData } from "./redact-B7rt9Zot.js";
16
19
  import { t as detectRewardHacking } from "./reward-hacking-D0XwhVWE.js";
17
20
  import { n as assertProposalFindings, t as combineAbortSignals } from "./abort-signal-CtzAM_sJ.js";
18
- import { a as validateSearchLedgerEvent, c as searchCellId, i as replaySearchLedgerText, l as searchNodeId, u as searchTaskSetDigest } from "./search-ledger-CQ8Uy4FK.js";
21
+ import { a as validateSearchLedgerEvent, d as searchTaskSetDigest, f as searchUnitScores, i as replaySearchLedgerText, l as searchCellId, n as openSearchLedger, t as FileSearchLedger, u as searchNodeId } from "./search-ledger-Ck7tok0h.js";
19
22
  import { d as stripFencedJson, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-KYQBh9C8.js";
20
23
  import { createHash } from "node:crypto";
21
24
  import { writeFileSync } from "node:fs";
@@ -1161,8 +1164,8 @@ function detectCalibrationDrift(runs, opts) {
1161
1164
  alpha,
1162
1165
  recentN: recent.length,
1163
1166
  historyN: historical.length,
1164
- recentMean: mean$1(recent),
1165
- historyMean: mean$1(historical)
1167
+ recentMean: mean$2(recent),
1168
+ historyMean: mean$2(historical)
1166
1169
  }
1167
1170
  }],
1168
1171
  evaluation: {
@@ -1399,7 +1402,7 @@ function chiSquareCritical(df, alpha) {
1399
1402
  }
1400
1403
  return TABLE[10][idx];
1401
1404
  }
1402
- function mean$1(xs) {
1405
+ function mean$2(xs) {
1403
1406
  if (xs.length === 0) return 0;
1404
1407
  return xs.reduce((s, x) => s + x, 0) / xs.length;
1405
1408
  }
@@ -2532,20 +2535,994 @@ function paretoFrontierWithCrowding(candidates, objectives) {
2532
2535
  return crowdingDistance(frontier, objectives).sort((a, b) => b.distance - a.distance);
2533
2536
  }
2534
2537
  //#endregion
2535
- //#region src/campaign/types.ts
2536
- /** Type guard: a proposal carrying its rationale vs a bare
2537
- * surface. The loop branches on this to populate `GenerationCandidate`. */
2538
- function isProposedCandidate(value) {
2539
- return typeof value === "object" && value !== null && "surface" in value && "label" in value && "rationale" in value;
2538
+ //#region src/campaign/allocation.ts
2539
+ /**
2540
+ * Every node gets every train and selection task at every repeat: the root as
2541
+ * `root` cells, every other node as `train` cells (the proposer's feedback) and
2542
+ * `screen` cells (the policy's private evidence). This is the fixed plan the
2543
+ * generation loop ran and GEPA runs.
2544
+ */
2545
+ function uniform(options = {}) {
2546
+ const reps = options.reps ?? 1;
2547
+ if (!Number.isSafeInteger(reps) || reps < 1) throw new TypeError(`uniform: reps must be a positive integer, got ${String(reps)}`);
2548
+ const cells = (state, root) => {
2549
+ const header = state.header;
2550
+ if (!header) return [];
2551
+ const plan = [];
2552
+ for (const split of ["train", "selection"]) for (const task of header.splits[split].tasks) for (let rep = 0; rep < reps; rep++) plan.push({
2553
+ taskId: task.taskId,
2554
+ split,
2555
+ rep,
2556
+ stage: root ? "root" : split === "train" ? "train" : "screen"
2557
+ });
2558
+ return plan;
2559
+ };
2560
+ return {
2561
+ name: reps === 1 ? "uniform" : `uniform(reps=${reps})`,
2562
+ plan: (state, nodeId) => cells(state, nodeId === state.rootNodeId),
2563
+ screenSize: (state) => cells(state, false).length
2564
+ };
2540
2565
  }
2541
- const LABEL_TRUST_RANK = {
2542
- unverified: 0,
2543
- "verified-signal": 1,
2544
- "human-rated": 2
2566
+ //#endregion
2567
+ //#region src/campaign/estimate-node.ts
2568
+ /**
2569
+ * Per-node statistics of a search.
2570
+ *
2571
+ * `estimateNode` is the one paired contrast of two nodes: it averages each
2572
+ * node's cells inside their units, pairs the units both nodes scored, and runs
2573
+ * `pairedDeltaTest` on the per-unit deltas. `estimateNodeFromCells` is the same
2574
+ * computation over cells a caller already holds, such as rows a server read
2575
+ * from its database. The bootstrap seed comes from `cellSetDigest`, the digest
2576
+ * of exactly the cells used, so a producer and a verifier that hold the same
2577
+ * cells report the same bits.
2578
+ *
2579
+ * `searchPosterior` gives every node a normal posterior on its improvement over
2580
+ * the root, which expansion policies sample parents from. Its numbers steer
2581
+ * spend and claim nothing.
2582
+ */
2583
+ const CONFIDENCE = .95;
2584
+ const RESAMPLES = 2e3;
2585
+ /** 6 at 95 %: the smallest sample an exact one-sided sign test can resolve. */
2586
+ const DESCRIPTIVE_FROM = minimumPairsForPairedDeltaTest(CONFIDENCE);
2587
+ /** The estimator every `NodeEstimate` names. */
2588
+ const SEARCH_ESTIMATOR = {
2589
+ uri: "npm:@tangle-network/agent-eval#estimateNode",
2590
+ revision: hashCanonical({
2591
+ name: "tangle.estimate-node.2026-09",
2592
+ unit: "mean of the unit’s scored cells, summed in cellId order",
2593
+ pairing: "units both nodes scored, in unitId order",
2594
+ statistic: DECISION_PAIRED_DELTA_STATISTIC,
2595
+ interval: "percentile paired bootstrap",
2596
+ confidence: CONFIDENCE,
2597
+ resamples: RESAMPLES,
2598
+ rng: "mulberry32",
2599
+ seed: "the first 32 bits of cellSetDigest",
2600
+ signTest: "exact, one-sided toward improvement in the objective direction",
2601
+ methods: {
2602
+ none: "below 2 pairs",
2603
+ insufficient: `below ${DESCRIPTIVE_FROM} pairs`,
2604
+ descriptive: `below 20 pairs`,
2605
+ bootstrap: `from 20 pairs`
2606
+ },
2607
+ indeterminate: "every paired delta equal: no interval and no sign p"
2608
+ })
2545
2609
  };
2546
- /** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
2547
- function labelTrustRank(trust) {
2548
- return LABEL_TRUST_RANK[trust ?? "unverified"];
2610
+ /** The method a paired sample of `pairs` units supports. */
2611
+ function searchEstimateMethod(pairs) {
2612
+ if (pairs < 2) return "none";
2613
+ if (pairs < DESCRIPTIVE_FROM) return "insufficient";
2614
+ if (pairs < 20) return "descriptive";
2615
+ return "bootstrap";
2616
+ }
2617
+ /**
2618
+ * Digest of the cells an estimate reads, with the contrast they serve. Cells
2619
+ * are listed in cellId order, so the digest names the set, not the order it
2620
+ * was read in.
2621
+ */
2622
+ function searchCellSetDigest(input) {
2623
+ const list = (cells) => [...cells].sort((left, right) => compareCodeUnits(left.cellId, right.cellId)).map((cell) => [
2624
+ cell.cellId,
2625
+ cell.unitId,
2626
+ cell.attempt,
2627
+ cell.score
2628
+ ]);
2629
+ return hashCanonical({
2630
+ contrast: [input.nodeId, input.against],
2631
+ split: input.split,
2632
+ nodeCells: list(input.nodeCells),
2633
+ againstCells: list(input.againstCells)
2634
+ });
2635
+ }
2636
+ /**
2637
+ * The paired contrast of `nodeId` against `against` on the units both scored.
2638
+ *
2639
+ * `delta` is the mean per-unit difference, node minus `against`, in the
2640
+ * metric's own units; for a `minimize` objective an improvement is negative.
2641
+ * The exact sign test is one-sided toward improvement in the objective's
2642
+ * direction. Unscored cells (errored, cancelled or in flight) are absent from
2643
+ * the inputs, never zero.
2644
+ *
2645
+ * - `none` (0 or 1 pair): no delta.
2646
+ * - `insufficient` (2 to 5): the delta only.
2647
+ * - `descriptive` (6 to 19): the bootstrap interval as spread, and the sign-test p.
2648
+ * - `bootstrap` (20 or more): the decision-grade bootstrap interval.
2649
+ *
2650
+ * When every paired delta is equal the interval would have zero width, which
2651
+ * is an absence of evidence; the estimate is then `indeterminate` and carries
2652
+ * neither an interval nor a p-value.
2653
+ */
2654
+ function estimateNodeFromCells(input) {
2655
+ const { nodeId, against, split, direction } = input;
2656
+ if (nodeId === against) throw new SearchLedgerError(`node ${nodeId} cannot be estimated against itself`);
2657
+ assertDistinctCells([...input.nodeCells, ...input.againstCells], nodeId, against);
2658
+ const cellSetDigest = searchCellSetDigest(input);
2659
+ const nodeUnits = searchUnitScores(input.nodeCells);
2660
+ const { node, other } = pairUnits(nodeUnits, unitMeans(searchUnitScores(input.againstCells)));
2661
+ const pairs = node.length;
2662
+ const method = searchEstimateMethod(pairs);
2663
+ const base = {
2664
+ against,
2665
+ split,
2666
+ units: nodeUnits.length,
2667
+ pairs,
2668
+ method,
2669
+ cellSetDigest,
2670
+ estimator: SEARCH_ESTIMATOR
2671
+ };
2672
+ if (method === "none") return {
2673
+ ...base,
2674
+ delta: null,
2675
+ interval: null,
2676
+ exactSignP: null,
2677
+ indeterminate: false
2678
+ };
2679
+ const maximize = direction === "maximize";
2680
+ const test = pairedDeltaTest(maximize ? other : node, maximize ? node : other, {
2681
+ statistic: DECISION_PAIRED_DELTA_STATISTIC,
2682
+ confidence: CONFIDENCE,
2683
+ resamples: RESAMPLES,
2684
+ seed: seedFromDigest(cellSetDigest)
2685
+ });
2686
+ const { mean, low, high } = test.bootstrap;
2687
+ const indeterminate = test.indeterminate;
2688
+ const spread = (method === "descriptive" || method === "bootstrap") && !indeterminate;
2689
+ return {
2690
+ ...base,
2691
+ delta: plain(maximize ? mean : -mean),
2692
+ interval: spread ? maximize ? [plain(low), plain(high)] : [plain(-high), plain(-low)] : null,
2693
+ exactSignP: method === "descriptive" && !indeterminate ? test.pValue : null,
2694
+ indeterminate
2695
+ };
2696
+ }
2697
+ /**
2698
+ * `estimateNodeFromCells` over the cells a search state holds: `nodeId`
2699
+ * against `against` on one split, in the direction the search's objective
2700
+ * declares.
2701
+ */
2702
+ function estimateNode(state, nodeId, options) {
2703
+ const header = state.header;
2704
+ if (!header) throw new SearchLedgerError(`search ${state.searchId} has not been opened`);
2705
+ for (const id of [nodeId, options.against]) if (!state.hasNode(id)) throw new SearchLedgerError(`node ${id} is not in search ${state.searchId}`);
2706
+ return estimateNodeFromCells({
2707
+ nodeId,
2708
+ against: options.against,
2709
+ split: options.split,
2710
+ direction: header.objective.direction,
2711
+ nodeCells: state.scoredCells(nodeId, options.split),
2712
+ againstCells: state.scoredCells(options.against, options.split)
2713
+ });
2714
+ }
2715
+ /**
2716
+ * The posterior every node's parent-selection draw uses. Each node's mean is
2717
+ * its improvement over the root on shared units; its variance is the search's
2718
+ * pooled between-unit variance divided by its unit count, so a node measured
2719
+ * on one unit has a wide posterior rather than none. The root is the reference
2720
+ * and sits at exactly 0.
2721
+ */
2722
+ function searchPosterior(state, options = {}) {
2723
+ const split = options.split ?? "selection";
2724
+ const header = state.header;
2725
+ if (!header) throw new SearchLedgerError(`search ${state.searchId} has not been opened`);
2726
+ const rootNodeId = state.rootNodeId;
2727
+ if (rootNodeId === null) throw new SearchLedgerError(`search ${state.searchId} has no root node yet`);
2728
+ const sign = header.objective.direction === "maximize" ? 1 : -1;
2729
+ const root = searchUnitScores(state.scoredCells(rootNodeId, split));
2730
+ const rootMeans = unitMeans(root);
2731
+ const nodes = [];
2732
+ let squares = 0;
2733
+ let degreesOfFreedom = 0;
2734
+ for (const nodeId of state.nodeIds()) {
2735
+ if (nodeId === rootNodeId) {
2736
+ const measured = root.length > 0;
2737
+ nodes.push({
2738
+ nodeId,
2739
+ pairs: root.length,
2740
+ mean: measured ? 0 : null,
2741
+ variance: measured ? 0 : null
2742
+ });
2743
+ continue;
2744
+ }
2745
+ const { node, other } = pairUnits(searchUnitScores(state.scoredCells(nodeId, split)), rootMeans);
2746
+ const gains = node.map((value, index) => sign * (value - other[index]));
2747
+ const mean = gains.length > 0 ? sum(gains) / gains.length : null;
2748
+ if (mean !== null && gains.length >= 2) {
2749
+ squares += sum(gains.map((gain) => (gain - mean) ** 2));
2750
+ degreesOfFreedom += gains.length - 1;
2751
+ }
2752
+ nodes.push({
2753
+ nodeId,
2754
+ pairs: gains.length,
2755
+ mean: mean === null ? null : plain(mean),
2756
+ variance: null
2757
+ });
2758
+ }
2759
+ const pooledVariance = degreesOfFreedom > 0 ? squares / degreesOfFreedom : null;
2760
+ if (pooledVariance !== null) {
2761
+ for (const posterior of nodes) if (posterior.nodeId !== rootNodeId && posterior.pairs > 0) posterior.variance = pooledVariance / posterior.pairs;
2762
+ }
2763
+ return {
2764
+ split,
2765
+ rootNodeId,
2766
+ pooledVariance,
2767
+ degreesOfFreedom,
2768
+ nodes
2769
+ };
2770
+ }
2771
+ function unitMeans(units) {
2772
+ return new Map(units.map((unit) => [unit.unitId, unit.mean]));
2773
+ }
2774
+ /** Unit means of two nodes on the units both scored, aligned in the node's
2775
+ * unitId order. */
2776
+ function pairUnits(node, otherMeans) {
2777
+ const paired = {
2778
+ node: [],
2779
+ other: []
2780
+ };
2781
+ for (const unit of node) {
2782
+ const mean = otherMeans.get(unit.unitId);
2783
+ if (mean === void 0) continue;
2784
+ paired.node.push(unit.mean);
2785
+ paired.other.push(mean);
2786
+ }
2787
+ return paired;
2788
+ }
2789
+ function assertDistinctCells(cells, nodeId, against) {
2790
+ const seen = /* @__PURE__ */ new Set();
2791
+ for (const cell of cells) {
2792
+ if (seen.has(cell.cellId)) throw new SearchLedgerError(`cell ${cell.cellId} appears twice in the estimate of ${nodeId} against ${against}`);
2793
+ if (!Number.isFinite(cell.score)) throw new SearchLedgerError(`cell ${cell.cellId} has a non-finite score ${cell.score}`);
2794
+ seen.add(cell.cellId);
2795
+ }
2796
+ }
2797
+ /** The first 32 bits of a `sha256:` digest, as the bootstrap's seed. */
2798
+ function seedFromDigest(digest) {
2799
+ return Number.parseInt(digest.slice(7, 15), 16) | 0;
2800
+ }
2801
+ function sum(values) {
2802
+ let total = 0;
2803
+ for (const value of values) total += value;
2804
+ return total;
2805
+ }
2806
+ /** Canonical JSON has one zero; never hand it a negative one. */
2807
+ function plain(value) {
2808
+ return value === 0 ? 0 : value;
2809
+ }
2810
+ /** The kernel every `runSearch` ledger names as its search implementation. */
2811
+ const SEARCH_KERNEL_SOURCE = {
2812
+ uri: "npm:@tangle-network/agent-eval#runSearch",
2813
+ revision: hashCanonical({
2814
+ name: "tangle.search-kernel.2026-09",
2815
+ dispatch: "claim, then rung and root, then screen, then train; first allocated first within a stage",
2816
+ expansion: "when no cell waits, fewer than twice the lanes capacity run, and the cap admits one proposal and its expected screens",
2817
+ reservation: {
2818
+ hard: "the lane per-cell maximum",
2819
+ estimate: "1.5 times the p99 of the lane settled cells once 20 settled, else the lane prior"
2820
+ },
2821
+ retries: "a retryable errored attempt runs again, up to maxAttempts",
2822
+ resume: "an unrecorded operation is recorded failed at an unknown cost with floor 0; a recorded proposal is finished from its stored output; an unsettled cell is offered to adopt before it runs",
2823
+ writers: "one kernel per ledger file on a host, by a pid lock beside the ledger",
2824
+ close: "the policy leader is selected when it has a scored cell; every other undecided node is rejected"
2825
+ })
2826
+ };
2827
+ const USD_TOLERANCE = 1e-9;
2828
+ /** Settled cells a lane needs before its own cost distribution sets its estimate. */
2829
+ const ESTIMATE_FROM_CELLS = 20;
2830
+ const ESTIMATE_MARGIN = 1.5;
2831
+ const DEFAULT_MAX_ATTEMPTS = 3;
2832
+ /** Dispatch order: claim cells first, then rungs and the root, then screens, then train. */
2833
+ const STAGE_PRIORITY = {
2834
+ claim: 0,
2835
+ rung: 1,
2836
+ root: 1,
2837
+ screen: 2,
2838
+ train: 3,
2839
+ external: 3
2840
+ };
2841
+ /** Run a search to its close, or continue one from its ledger. */
2842
+ async function runSearch(options) {
2843
+ return new SearchKernel(options).run();
2844
+ }
2845
+ /** The expansion index a kernel operation id encodes, or null. */
2846
+ function searchExpansionIndex(operationId) {
2847
+ const match = /^expand-(\d+)$/.exec(operationId);
2848
+ return match ? Number(match[1]) : null;
2849
+ }
2850
+ var SearchKernel = class {
2851
+ options;
2852
+ recorder;
2853
+ maxAttempts;
2854
+ now;
2855
+ state;
2856
+ lanes = /* @__PURE__ */ new Map();
2857
+ inFlight = /* @__PURE__ */ new Map();
2858
+ proposal = null;
2859
+ proposalController = null;
2860
+ completions = [];
2861
+ wake = null;
2862
+ /** Unfinished cells per node. */
2863
+ pending = /* @__PURE__ */ new Map();
2864
+ /** Admitted nodes whose screen finished, in registration order. */
2865
+ screened = [];
2866
+ screenedSet = /* @__PURE__ */ new Set();
2867
+ admitted = /* @__PURE__ */ new Set();
2868
+ expansionOf = /* @__PURE__ */ new Map();
2869
+ artifacts = /* @__PURE__ */ new Map();
2870
+ /** Children a proposal returned, by artifact digest, until they are registered. */
2871
+ proposed = /* @__PURE__ */ new Map();
2872
+ /** Cells the ledger showed unsettled at start: an earlier process may hold their attempt. */
2873
+ adoptable = /* @__PURE__ */ new Set();
2874
+ operationCosts = [];
2875
+ /** Operations started: the next one's id. */
2876
+ expansions = 0;
2877
+ /** Proposals that completed, by operation index. A proposal an interrupted
2878
+ * process lost counts toward none of the stop rules, so a resumed search
2879
+ * makes the proposals the uninterrupted one would have made. */
2880
+ completed = [];
2881
+ stopReason = null;
2882
+ failure = null;
2883
+ constructor(options) {
2884
+ this.options = options;
2885
+ this.recorder = options.recorder;
2886
+ this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
2887
+ this.now = options.now ?? Date.now;
2888
+ if (!Number.isSafeInteger(this.maxAttempts) || this.maxAttempts < 1) throw new TypeError("runSearch: maxAttempts must be a positive integer");
2889
+ const { maxExpansions } = options;
2890
+ if (maxExpansions !== void 0 && (!Number.isSafeInteger(maxExpansions) || maxExpansions < 0)) throw new TypeError("runSearch: maxExpansions must be a non-negative integer");
2891
+ for (const lane of options.executor.lanes()) {
2892
+ if (!Number.isSafeInteger(lane.capacity) || lane.capacity < 1) throw new TypeError(`runSearch: lane ${lane.name} needs a positive integer capacity`);
2893
+ if (!Number.isFinite(lane.cellUsd) || lane.cellUsd < 0) throw new TypeError(`runSearch: lane ${lane.name} needs a finite non-negative cellUsd`);
2894
+ if (this.lanes.has(lane.name)) throw new TypeError(`runSearch: lane ${lane.name} is declared twice`);
2895
+ this.lanes.set(lane.name, {
2896
+ ...lane,
2897
+ inFlight: 0,
2898
+ queues: [
2899
+ [],
2900
+ [],
2901
+ [],
2902
+ []
2903
+ ],
2904
+ settledCosts: []
2905
+ });
2906
+ }
2907
+ if (this.lanes.size === 0) throw new TypeError("runSearch: the executor declares no lane");
2908
+ }
2909
+ async run() {
2910
+ const { ledger } = this.recorder;
2911
+ const lock = ledger instanceof FileSearchLedger ? acquireSingleRunLock({ lockPath: `${ledger.path}.run.lock` }) : null;
2912
+ try {
2913
+ return await this.runLocked();
2914
+ } finally {
2915
+ lock?.release();
2916
+ }
2917
+ }
2918
+ async runLocked() {
2919
+ await this.refresh();
2920
+ const header = this.state.header;
2921
+ if (!header) throw new Error(`runSearch: search ${this.recorder.searchId} has not been opened`);
2922
+ if (header.policy.expansion !== this.options.policy.name || header.policy.allocation !== this.options.allocation.name) throw new Error(`runSearch: search ${this.recorder.searchId} was opened with policy ${header.policy.expansion} and allocation ${header.policy.allocation}, not ${this.options.policy.name} and ${this.options.allocation.name}`);
2923
+ if (this.state.closed) return this.closedResult();
2924
+ await this.restore();
2925
+ const { signal } = this.options;
2926
+ const onAbort = () => {
2927
+ for (const controller of this.inFlight.values()) controller.abort(signal?.reason);
2928
+ this.proposalController?.abort(signal?.reason);
2929
+ this.wakeUp();
2930
+ };
2931
+ signal?.addEventListener("abort", onAbort, { once: true });
2932
+ try {
2933
+ await this.loop();
2934
+ } finally {
2935
+ signal?.removeEventListener("abort", onAbort);
2936
+ }
2937
+ if (this.failure) throw this.failure.error;
2938
+ await this.close();
2939
+ return this.closedResult();
2940
+ }
2941
+ async restore() {
2942
+ const rootInput = this.options.codec.node(this.recorder, this.options.root);
2943
+ const rootId = this.state.rootNodeId;
2944
+ if (rootId === null) {
2945
+ const { nodeId } = await this.recorder.registerNode(rootInput);
2946
+ await this.recorder.recordEdge({
2947
+ childNodeId: nodeId,
2948
+ parents: [],
2949
+ operator: "seed",
2950
+ attribution: "explicit",
2951
+ proposer: null,
2952
+ proposalKey: "root",
2953
+ rationale: { unknown: "the root is the caller-supplied starting artifact" },
2954
+ diffs: [],
2955
+ label: "root"
2956
+ });
2957
+ await this.refresh();
2958
+ } else if (this.state.node(rootId).artifactDigest !== rootInput.artifactDigest) throw new Error(`runSearch: search ${this.recorder.searchId} started from another root artifact; a changed start is a derived search`);
2959
+ const root = this.state.rootNodeId;
2960
+ this.artifacts.set(root, this.options.root);
2961
+ this.expansionOf.set(root, -1);
2962
+ const recorded = [];
2963
+ for (let expansion = 0;; expansion++) {
2964
+ const operationId = `expand-${expansion}`;
2965
+ const operation = this.state.operation(operationId);
2966
+ if (!operation) break;
2967
+ this.expansions = expansion + 1;
2968
+ if (operation.recorded) {
2969
+ this.operationCosts.push(operation.spentUsd);
2970
+ recorded.push(expansion);
2971
+ if (operation.outcome === "completed") this.completed.push(expansion);
2972
+ continue;
2973
+ }
2974
+ await this.recorder.recordOperation({
2975
+ operationId,
2976
+ operationKind: "candidate-generation",
2977
+ execution: this.options.proposer.execution,
2978
+ outcome: {
2979
+ status: "failed",
2980
+ failure: {
2981
+ code: "interrupted",
2982
+ message: "the search stopped while this proposal ran; its output was lost"
2983
+ }
2984
+ },
2985
+ accounting: interruptedAccounting()
2986
+ });
2987
+ await this.refresh();
2988
+ }
2989
+ for (const node of this.state.nodes()) {
2990
+ if (node.edgeIds.length === 0) continue;
2991
+ const edge = this.state.edge(node.edgeIds[0]);
2992
+ const expansion = edge.proposer?.operationId ? searchExpansionIndex(edge.proposer.operationId) : null;
2993
+ this.expansionOf.set(node.nodeId, expansion ?? -1);
2994
+ if (node.status !== "invalid") this.admitted.add(node.nodeId);
2995
+ }
2996
+ for (const cell of this.state.cells()) {
2997
+ const lane = cell.lane === null ? void 0 : this.lanes.get(cell.lane);
2998
+ if (cell.final && lane) lane.settledCosts.push(cell.spentUsd);
2999
+ if (this.cellDone(cell)) continue;
3000
+ if (!lane) throw new Error(`runSearch: cell ${cell.cellId} was placed on lane ${String(cell.lane)}, which the executor does not declare`);
3001
+ this.adoptable.add(cell.cellId);
3002
+ this.enqueue(cell);
3003
+ this.pending.set(cell.nodeId, (this.pending.get(cell.nodeId) ?? 0) + 1);
3004
+ }
3005
+ for (const expansion of recorded) await this.finishProposal(expansion);
3006
+ for (const node of this.state.nodes()) if (this.admitted.has(node.nodeId) && !isTerminal(node)) await this.allocateFor(node.nodeId);
3007
+ for (const nodeId of this.state.nodeIds()) if (this.admitted.has(nodeId) && (this.pending.get(nodeId) ?? 0) === 0) this.markScreened(nodeId);
3008
+ }
3009
+ async loop() {
3010
+ for (;;) {
3011
+ if (this.failure || this.options.signal?.aborted) {
3012
+ await this.drain(this.failure?.error ?? this.options.signal?.reason);
3013
+ this.failure ??= { error: this.options.signal?.reason ?? /* @__PURE__ */ new Error("aborted") };
3014
+ return;
3015
+ }
3016
+ if (this.pastDeadline()) {
3017
+ this.stopReason ??= "deadline";
3018
+ if (this.queuedCount() > 0) await this.cancelQueued("deadline");
3019
+ }
3020
+ this.dispatch();
3021
+ const blocked = this.stopReason === null && this.proposal === null ? await this.tryExpand() : null;
3022
+ if (!(this.inFlight.size > 0 || this.proposal !== null)) {
3023
+ if (this.queuedCount() > 0) continue;
3024
+ this.stopReason ??= blocked ?? "converged";
3025
+ return;
3026
+ }
3027
+ await this.handle(await this.next());
3028
+ }
3029
+ }
3030
+ /** The next completion, or null when an abort woke the loop without one. */
3031
+ async next() {
3032
+ if (this.completions.length === 0) await new Promise((resolve) => {
3033
+ this.wake = resolve;
3034
+ });
3035
+ return this.completions.shift() ?? null;
3036
+ }
3037
+ push(completion) {
3038
+ this.completions.push(completion);
3039
+ this.wakeUp();
3040
+ }
3041
+ wakeUp() {
3042
+ const wake = this.wake;
3043
+ this.wake = null;
3044
+ wake?.();
3045
+ }
3046
+ async handle(completion) {
3047
+ if (completion === null) return;
3048
+ if (completion.kind === "cell") await this.settle(completion);
3049
+ else await this.recordProposal(completion);
3050
+ }
3051
+ /** Abort everything in flight and record what still completes. */
3052
+ async drain(reason) {
3053
+ for (const controller of this.inFlight.values()) controller.abort(reason);
3054
+ this.proposalController?.abort(reason);
3055
+ while (this.inFlight.size > 0 || this.proposal !== null) await this.handle(await this.next());
3056
+ }
3057
+ /** Fill free lane slots with queued cells. A queued cell was admitted when
3058
+ * it was allocated, so starting it needs no further decision. */
3059
+ dispatch() {
3060
+ if (this.failure || this.options.signal?.aborted || this.pastDeadline()) return;
3061
+ const cap = this.state.header.budget.maxConcurrency;
3062
+ for (const lane of this.lanes.values()) while (lane.inFlight < lane.capacity && (cap === null || this.inFlight.size < cap)) {
3063
+ const cellId = lane.queues.find((queue) => queue.length > 0)?.shift();
3064
+ if (cellId === void 0) break;
3065
+ this.start(cellId, lane);
3066
+ }
3067
+ }
3068
+ start(cellId, lane) {
3069
+ const cell = this.state.cell(cellId);
3070
+ const attempt = cell.attempts + 1;
3071
+ const controller = new AbortController();
3072
+ const work = {
3073
+ searchId: this.recorder.searchId,
3074
+ cellId,
3075
+ nodeId: cell.nodeId,
3076
+ taskId: cell.taskId,
3077
+ unitId: cell.unitId,
3078
+ split: cell.split,
3079
+ rep: cell.rep,
3080
+ stage: cell.stage,
3081
+ lane: lane.name,
3082
+ attempt,
3083
+ runId: `${cellId}:${attempt}`,
3084
+ artifact: this.artifact(cell.nodeId),
3085
+ signal: controller.signal
3086
+ };
3087
+ const adopt = this.adoptable.delete(cellId);
3088
+ this.inFlight.set(cellId, controller);
3089
+ lane.inFlight += 1;
3090
+ const { executor } = this.options;
3091
+ (async () => (adopt ? await executor.adopt(work) : null) ?? await executor.run(work))().then((result) => this.push({
3092
+ kind: "cell",
3093
+ cellId,
3094
+ attempt,
3095
+ result
3096
+ }), (error) => this.push({
3097
+ kind: "cell",
3098
+ cellId,
3099
+ attempt,
3100
+ error
3101
+ }));
3102
+ }
3103
+ async settle(completion) {
3104
+ const { cellId, attempt } = completion;
3105
+ this.inFlight.delete(cellId);
3106
+ const before = this.state.cell(cellId);
3107
+ const lane = this.lanes.get(before.lane);
3108
+ lane.inFlight -= 1;
3109
+ if (!completion.result) {
3110
+ this.failure ??= { error: completion.error };
3111
+ return;
3112
+ }
3113
+ this.dispatch();
3114
+ if ((this.failure || this.options.signal?.aborted) && completion.result.outcome.status === "errored") return;
3115
+ await this.recorder.settleCell({
3116
+ ...completion.result,
3117
+ cellId,
3118
+ attempt
3119
+ });
3120
+ await this.refresh();
3121
+ const cell = this.state.cell(cellId);
3122
+ if (cell.final) lane.settledCosts.push(cell.spentUsd);
3123
+ if (!this.cellDone(cell)) {
3124
+ if (this.failure || this.options.signal?.aborted) return;
3125
+ this.enqueue(cell);
3126
+ return;
3127
+ }
3128
+ const left = (this.pending.get(cell.nodeId) ?? 1) - 1;
3129
+ this.pending.set(cell.nodeId, left);
3130
+ if (left === 0) await this.onScreenDone(cell.nodeId);
3131
+ }
3132
+ async onScreenDone(nodeId) {
3133
+ this.markScreened(nodeId);
3134
+ if (this.stopReason === null && !this.failure) await this.allocateFor(nodeId);
3135
+ }
3136
+ markScreened(nodeId) {
3137
+ if (this.screenedSet.has(nodeId)) return;
3138
+ this.screenedSet.add(nodeId);
3139
+ const ordinal = this.state.node(nodeId).ordinal;
3140
+ let index = this.screened.length;
3141
+ while (index > 0 && this.state.node(this.screened[index - 1]).ordinal > ordinal) index--;
3142
+ this.screened = [
3143
+ ...this.screened.slice(0, index),
3144
+ nodeId,
3145
+ ...this.screened.slice(index)
3146
+ ];
3147
+ }
3148
+ /** Allocate the cells the allocator plans for a node and the ledger lacks. */
3149
+ async allocateFor(nodeId) {
3150
+ const plans = this.options.allocation.plan(this.state, nodeId).filter((plan) => !this.state.cell(searchCellId(this.recorder.searchId, nodeId, plan.taskId, plan.split, plan.rep)));
3151
+ if (plans.length === 0) return;
3152
+ const placed = plans.map((plan) => {
3153
+ const laneName = this.options.executor.place({
3154
+ ...plan,
3155
+ nodeId
3156
+ });
3157
+ const lane = this.lanes.get(laneName);
3158
+ if (!lane) throw new Error(`runSearch: the executor placed a cell on unknown lane ${laneName}`);
3159
+ return {
3160
+ plan,
3161
+ lane,
3162
+ reservation: this.cellReservation(lane)
3163
+ };
3164
+ });
3165
+ const hold = placed.reduce((sum, { reservation }) => sum + reservation.usd, 0);
3166
+ const { headroomUsd } = this.state.budget;
3167
+ if (headroomUsd !== null && hold > headroomUsd + USD_TOLERANCE) return;
3168
+ const { maxCells } = this.state.header.budget;
3169
+ if (maxCells !== null && this.state.audit.cells.allocated + placed.length > maxCells) return;
3170
+ for (const { plan, lane, reservation } of placed) await this.recorder.allocateCell({
3171
+ nodeId,
3172
+ taskId: plan.taskId,
3173
+ split: plan.split,
3174
+ rep: plan.rep,
3175
+ stage: plan.stage,
3176
+ lane: lane.name,
3177
+ reservation
3178
+ });
3179
+ await this.refresh();
3180
+ for (const { plan } of placed) {
3181
+ const cellId = searchCellId(this.recorder.searchId, nodeId, plan.taskId, plan.split, plan.rep);
3182
+ this.enqueue(this.state.cell(cellId));
3183
+ }
3184
+ this.pending.set(nodeId, (this.pending.get(nodeId) ?? 0) + placed.length);
3185
+ this.dispatch();
3186
+ }
3187
+ enqueue(cell) {
3188
+ this.lanes.get(cell.lane).queues[STAGE_PRIORITY[cell.stage]].push(cell.cellId);
3189
+ }
3190
+ queuedCount() {
3191
+ let count = 0;
3192
+ for (const lane of this.lanes.values()) for (const queue of lane.queues) count += queue.length;
3193
+ return count;
3194
+ }
3195
+ async cancelQueued(reason) {
3196
+ for (const lane of this.lanes.values()) for (const queue of lane.queues) for (const cellId of queue.splice(0)) {
3197
+ const cell = this.state.cell(cellId);
3198
+ if (cell.attempts === 0) {
3199
+ await this.recorder.cancelCell({
3200
+ cellId,
3201
+ reason
3202
+ });
3203
+ await this.refresh();
3204
+ }
3205
+ const left = (this.pending.get(cell.nodeId) ?? 1) - 1;
3206
+ this.pending.set(cell.nodeId, left);
3207
+ if (left === 0) this.markScreened(cell.nodeId);
3208
+ }
3209
+ }
3210
+ /** No further attempt will run: a final outcome, a cancellation, or a
3211
+ * retryable error that used up its attempts. */
3212
+ cellDone(cell) {
3213
+ return cell.final || cell.cancelled !== null || cell.outcome === "errored" && cell.attempts >= this.maxAttempts;
3214
+ }
3215
+ /** Start a proposal when the rules admit one; otherwise say why not. */
3216
+ async tryExpand() {
3217
+ const { maxNodes, maxCells } = this.state.header.budget;
3218
+ if (maxNodes !== null && this.state.audit.nodes >= maxNodes) return "max-nodes";
3219
+ const { maxExpansions } = this.options;
3220
+ if (maxExpansions !== void 0 && this.completed.length >= maxExpansions) return "max-nodes";
3221
+ if (this.queuedCount() > 0) return "converged";
3222
+ if (this.inFlight.size >= 2 * this.capacity()) return "converged";
3223
+ const view = this.policyView();
3224
+ const { policy } = this.options;
3225
+ const leader = policy.leader(view);
3226
+ if (policy.patience !== void 0 && this.completed.filter((index) => index > (this.expansionOf.get(leader) ?? -1)).length >= policy.patience) return "patience";
3227
+ const screen = this.options.allocation.screenSize(this.state);
3228
+ const operation = this.operationReservation();
3229
+ const children = this.options.proposer.childrenPerProposal ?? 1;
3230
+ const need = operation.usd + children * screen * this.maxCellReservation();
3231
+ const { headroomUsd } = this.state.budget;
3232
+ if (headroomUsd !== null && need > headroomUsd + USD_TOLERANCE) return "budget";
3233
+ if (maxCells !== null && this.state.audit.cells.allocated + children * screen > maxCells) return "budget";
3234
+ const expansion = policy.expand(view);
3235
+ if (expansion === null) return "converged";
3236
+ const index = this.expansions++;
3237
+ const operationId = `expand-${index}`;
3238
+ await this.recorder.startOperation({
3239
+ operationId,
3240
+ operationKind: "candidate-generation",
3241
+ reservation: operation
3242
+ });
3243
+ await this.refresh();
3244
+ const controller = new AbortController();
3245
+ const request = {
3246
+ expansion: index,
3247
+ operationId,
3248
+ operator: expansion.operator,
3249
+ parents: expansion.parents.map((nodeId) => ({
3250
+ nodeId,
3251
+ artifact: this.artifact(nodeId)
3252
+ })),
3253
+ leader,
3254
+ signal: controller.signal
3255
+ };
3256
+ this.proposalController = controller;
3257
+ this.proposal = this.options.proposer.propose(request).then((result) => this.push({
3258
+ kind: "proposal",
3259
+ expansion: index,
3260
+ operationId,
3261
+ request,
3262
+ selection: expansion.selection,
3263
+ result
3264
+ }), (error) => this.push({
3265
+ kind: "proposal",
3266
+ expansion: index,
3267
+ operationId,
3268
+ request,
3269
+ selection: expansion.selection,
3270
+ error
3271
+ }));
3272
+ return null;
3273
+ }
3274
+ async recordProposal(completion) {
3275
+ this.proposal = null;
3276
+ this.proposalController = null;
3277
+ const { operationId, expansion } = completion;
3278
+ const { proposer, codec, admit } = this.options;
3279
+ const result = completion.result;
3280
+ if (!result) {
3281
+ await this.recorder.recordOperation({
3282
+ operationId,
3283
+ operationKind: "candidate-generation",
3284
+ execution: proposer.execution,
3285
+ outcome: {
3286
+ status: "failed",
3287
+ failure: {
3288
+ code: "proposer-error",
3289
+ message: errorMessage(completion.error)
3290
+ }
3291
+ },
3292
+ accounting: interruptedAccounting()
3293
+ });
3294
+ await this.refresh();
3295
+ this.failure ??= { error: completion.error };
3296
+ return;
3297
+ }
3298
+ const { maxNodes } = this.state.header.budget;
3299
+ const room = maxNodes === null ? Number.POSITIVE_INFINITY : maxNodes - this.state.audit.nodes;
3300
+ const parents = completion.request.parents;
3301
+ const blob = {
3302
+ kind: "search-proposal",
3303
+ operator: completion.request.operator,
3304
+ parents: parents.map((parent) => parent.nodeId),
3305
+ selection: completion.selection,
3306
+ children: result.children.slice(0, Math.max(0, room)).map((child) => ({
3307
+ node: codec.node(this.recorder, child.artifact),
3308
+ diffs: parents.map((parent) => codec.diff(this.recorder, parent.artifact, child.artifact)),
3309
+ label: redactText(child.label, { profile: "share" }),
3310
+ rationale: redactText(child.rationale, { profile: "share" }),
3311
+ attribution: child.attribution ?? null,
3312
+ invalid: admit?.(child.artifact) ?? null
3313
+ }))
3314
+ };
3315
+ await this.recorder.recordOperation({
3316
+ operationId,
3317
+ operationKind: "candidate-generation",
3318
+ execution: result.execution ?? proposer.execution,
3319
+ outcome: { status: "completed" },
3320
+ accounting: result.accounting,
3321
+ artifacts: [this.recorder.blob("proposal", blob)]
3322
+ });
3323
+ await this.refresh();
3324
+ this.operationCosts.push(this.state.operation(operationId).spentUsd);
3325
+ this.completed.push(expansion);
3326
+ for (const [index, child] of blob.children.entries()) this.proposed.set(child.node.artifactDigest, result.children[index].artifact);
3327
+ if (result.stop !== void 0 || blob.children.length === 0) this.stopReason ??= "converged";
3328
+ await this.finishProposal(expansion);
3329
+ }
3330
+ /** Register every child a recorded proposal produced, each once. */
3331
+ async finishProposal(expansion) {
3332
+ const operationId = `expand-${expansion}`;
3333
+ const operation = this.state.operation(operationId);
3334
+ const ref = operation?.artifacts.find((artifact) => artifact.role === "proposal");
3335
+ if (!operation?.recorded || operation.outcome !== "completed" || !ref) return;
3336
+ const blob = this.recorder.readBlob(ref);
3337
+ const { proposer } = this.options;
3338
+ for (const [index, child] of blob.children.entries()) {
3339
+ const { nodeId } = await this.recorder.registerNode(child.node);
3340
+ const held = this.proposed.get(child.node.artifactDigest);
3341
+ if (held !== void 0) {
3342
+ this.proposed.delete(child.node.artifactDigest);
3343
+ if (!this.artifacts.has(nodeId)) this.artifacts.set(nodeId, held);
3344
+ }
3345
+ const edgeId = await this.recorder.recordEdge({
3346
+ childNodeId: nodeId,
3347
+ parents: blob.parents,
3348
+ operator: blob.operator,
3349
+ attribution: "explicit",
3350
+ proposer: {
3351
+ kind: proposer.kind,
3352
+ name: proposer.name,
3353
+ operationId,
3354
+ source: proposer.source
3355
+ },
3356
+ proposalKey: `${operationId}:${index}`,
3357
+ selection: blob.selection,
3358
+ rationale: child.rationale,
3359
+ diffs: child.diffs,
3360
+ label: child.label
3361
+ });
3362
+ await this.refresh();
3363
+ if (this.state.node(nodeId).edgeIds[0] !== edgeId) continue;
3364
+ this.expansionOf.set(nodeId, expansion);
3365
+ if (child.invalid !== null) {
3366
+ await this.recorder.decideNode({
3367
+ nodeId,
3368
+ decision: { status: "invalid" },
3369
+ rule: "admission",
3370
+ reason: child.invalid
3371
+ });
3372
+ await this.refresh();
3373
+ continue;
3374
+ }
3375
+ if (this.admitted.has(nodeId)) continue;
3376
+ this.admitted.add(nodeId);
3377
+ await this.allocateFor(nodeId);
3378
+ if ((this.pending.get(nodeId) ?? 0) === 0) this.markScreened(nodeId);
3379
+ }
3380
+ }
3381
+ async close() {
3382
+ await this.refresh();
3383
+ const view = this.policyView();
3384
+ const leader = this.options.policy.leader(view);
3385
+ const { split } = view;
3386
+ const root = this.state.rootNodeId;
3387
+ const rule = this.options.policy.name;
3388
+ const decisions = this.state.nodes().filter((node) => !isTerminal(node)).map((node) => {
3389
+ const scored = this.state.scoredCells(node.nodeId, split).length > 0;
3390
+ const lead = node.nodeId === leader && scored;
3391
+ const against = node.nodeId === leader ? root : leader;
3392
+ const basis = node.nodeId === against || !scored ? null : estimateNode(this.state, node.nodeId, {
3393
+ against,
3394
+ split
3395
+ });
3396
+ return {
3397
+ nodeId: node.nodeId,
3398
+ decision: { status: lead ? "selected" : "rejected" },
3399
+ basis,
3400
+ rule,
3401
+ reason: lead ? `the leader when the search stopped (${this.stopReason}): a budget decision on the ${split} split, which claims nothing` : !this.screenedSet.has(node.nodeId) || !scored ? "the search stopped before this node was measured" : !view.complete(node.nodeId) ? "it left a unit of its screen unscored, so it could not lead" : "it did not beat the leader on the units they share"
3402
+ };
3403
+ });
3404
+ for (const decision of decisions) await this.recorder.decideNode(decision);
3405
+ await this.recorder.close({
3406
+ reason: this.stopReason ?? "converged",
3407
+ claim: null
3408
+ });
3409
+ await this.refresh();
3410
+ }
3411
+ closedResult() {
3412
+ const state = this.state;
3413
+ return {
3414
+ state,
3415
+ leader: state.audit.selectedNodeId ?? state.rootNodeId,
3416
+ reason: state.closed.reason
3417
+ };
3418
+ }
3419
+ policyView() {
3420
+ return searchPolicyView(this.state, {
3421
+ screened: this.screened,
3422
+ screening: this.admitted.size - this.screenedSet.size,
3423
+ expansions: this.completed.length
3424
+ });
3425
+ }
3426
+ artifact(nodeId) {
3427
+ const held = this.artifacts.get(nodeId);
3428
+ if (held !== void 0) return held;
3429
+ const artifact = this.options.codec.load(this.recorder, this.state.node(nodeId));
3430
+ this.artifacts.set(nodeId, artifact);
3431
+ return artifact;
3432
+ }
3433
+ capacity() {
3434
+ let total = 0;
3435
+ for (const lane of this.lanes.values()) total += lane.capacity;
3436
+ const cap = this.state.header.budget.maxConcurrency;
3437
+ return cap === null ? total : Math.min(cap, total);
3438
+ }
3439
+ /** A hard lane holds its enforced maximum. An estimate lane holds 1.5 times
3440
+ * the 99th percentile of its settled cells once 20 settled, else its prior. */
3441
+ cellReservation(lane) {
3442
+ if (lane.costCap === "hard") return {
3443
+ kind: "hard",
3444
+ usd: lane.cellUsd
3445
+ };
3446
+ return {
3447
+ kind: "estimate",
3448
+ usd: estimate(lane.settledCosts, lane.cellUsd)
3449
+ };
3450
+ }
3451
+ maxCellReservation() {
3452
+ let most = 0;
3453
+ for (const lane of this.lanes.values()) most = Math.max(most, this.cellReservation(lane).usd);
3454
+ return most;
3455
+ }
3456
+ operationReservation() {
3457
+ return {
3458
+ kind: "estimate",
3459
+ usd: estimate(this.operationCosts, this.options.proposer.reservationUsd ?? 0)
3460
+ };
3461
+ }
3462
+ pastDeadline() {
3463
+ const deadline = this.state.header.budget.deadline;
3464
+ return deadline !== null && this.now() >= Date.parse(deadline);
3465
+ }
3466
+ async refresh() {
3467
+ this.state = await this.recorder.state();
3468
+ }
3469
+ };
3470
+ /**
3471
+ * A policy's read of a search: `screened` lists the admitted nodes whose screen
3472
+ * finished, in registration order; `screening` counts the admitted nodes still
3473
+ * being screened. Nodes are ranked on the selection split, or on train when
3474
+ * the search declares no selection split. The view reads no test cell.
3475
+ */
3476
+ function searchPolicyView(state, input) {
3477
+ const header = state.header;
3478
+ if (!header) throw new Error(`search ${state.searchId} has not been opened`);
3479
+ const split = header.splits.selection.tasks.length > 0 ? "selection" : "train";
3480
+ return {
3481
+ searchId: state.searchId,
3482
+ seed: header.policy.seed,
3483
+ direction: header.objective.direction,
3484
+ split,
3485
+ rootNodeId: state.rootNodeId,
3486
+ expansions: input.expansions,
3487
+ screened: [...input.screened],
3488
+ screening: input.screening,
3489
+ complete: (nodeId) => {
3490
+ const cells = state.cells({ nodeId }).filter((cell) => cell.split === split);
3491
+ return cells.length > 0 && cells.every((cell) => cell.score !== null);
3492
+ },
3493
+ unitScores: (nodeId) => state.unitScores(nodeId, split),
3494
+ estimate: (nodeId, against) => estimateNode(state, nodeId, {
3495
+ against,
3496
+ split
3497
+ })
3498
+ };
3499
+ }
3500
+ function isTerminal(node) {
3501
+ return node.status !== null && node.status !== "advanced";
3502
+ }
3503
+ function estimate(costs, prior) {
3504
+ if (costs.length < ESTIMATE_FROM_CELLS) return prior;
3505
+ const sorted = [...costs].sort((a, b) => a - b);
3506
+ const p99 = sorted[Math.min(sorted.length - 1, Math.ceil(.99 * sorted.length) - 1)];
3507
+ return Math.round(ESTIMATE_MARGIN * p99 * 1e9) / 1e9;
3508
+ }
3509
+ /** Spend of work whose result was lost: unknown, with no proven floor. */
3510
+ function interruptedAccounting() {
3511
+ return {
3512
+ tokens: {
3513
+ status: "unknown",
3514
+ reason: "the result of this work was lost"
3515
+ },
3516
+ cost: {
3517
+ status: "unknown",
3518
+ knownLowerBoundUsd: 0,
3519
+ reason: "the result of this work was lost, so its spend is unknown"
3520
+ }
3521
+ };
3522
+ }
3523
+ function errorMessage(error) {
3524
+ const text = error instanceof Error ? error.message : String(error);
3525
+ return text.trim().length > 0 ? text : "the proposer failed without a message";
2549
3526
  }
2550
3527
  //#endregion
2551
3528
  //#region src/campaign/search-history-receipt.ts
@@ -2842,6 +3819,20 @@ var SearchRecorder = class SearchRecorder {
2842
3819
  byteLength: bytes.byteLength
2843
3820
  };
2844
3821
  }
3822
+ /**
3823
+ * Read back a blob this recorder stored, by its content address. The bytes
3824
+ * must hash to the reference's digest and length, so a resumed search reads
3825
+ * exactly what the ledger bound.
3826
+ */
3827
+ readBlob(ref) {
3828
+ const hex = ref.sha256.slice(7);
3829
+ const path = join(this.blobDir, `${hex}.json`);
3830
+ const text = this.storage.read(path);
3831
+ if (text === void 0) throw new Error(`search ${this.searchId}: blob ${ref.sha256} (${ref.role}) is missing at ${path}`);
3832
+ const bytes = Buffer.from(text, "utf8");
3833
+ if (createHash("sha256").update(bytes).digest("hex") !== hex || bytes.byteLength !== ref.byteLength) throw new Error(`search ${this.searchId}: blob at ${path} does not match ${ref.sha256}`);
3834
+ return JSON.parse(text);
3835
+ }
2845
3836
  /** Register a node, or return the existing node with the same artifact digest. */
2846
3837
  async registerNode(input) {
2847
3838
  const existing = (await this.ledger.state()).nodeIdForDigest(input.artifactDigest);
@@ -3097,256 +4088,818 @@ function canonicalHeader(header) {
3097
4088
  });
3098
4089
  }
3099
4090
  //#endregion
3100
- //#region src/campaign/presets/run-optimization-recording.ts
3101
- var OptimizationSearch = class OptimizationSearch {
3102
- recorder;
3103
- binding;
3104
- costLedger;
3105
- scenarioIds;
3106
- reps;
3107
- identity;
3108
- proposerName;
3109
- proposalReceiptCount = 0;
3110
- constructor(args) {
3111
- this.recorder = args.recorder;
3112
- this.binding = args.binding;
3113
- this.costLedger = args.costLedger;
3114
- this.scenarioIds = args.scenarioIds;
3115
- this.reps = args.reps;
3116
- this.identity = args.identity;
3117
- this.proposerName = args.proposerName;
3118
- }
3119
- /** Open the search and record the measured baseline as its seeded root. */
3120
- static async open(args) {
3121
- const { binding } = args;
3122
- const identity = {
3123
- model: binding.identity.model,
3124
- agent: binding.identity.agent,
3125
- benchmark: {
3126
- uri: "campaign://scenarios",
3127
- revision: args.splitDigest
3128
- }
3129
- };
3130
- const train = args.scenarios.map((scenario) => ({
3131
- taskId: scenario.id,
3132
- unitId: scenario.id,
3133
- source: {
3134
- uri: `scenario://${scenario.id}`,
3135
- revision: hashCanonical(scenario)
3136
- }
3137
- }));
3138
- const recorder = await SearchRecorder.open({
3139
- ledger: binding.ledger,
3140
- storage: args.storage
3141
- }, {
3142
- subject: binding.identity.subject ?? args.proposerName,
3143
- process: {
3144
- name: "runOptimization",
3145
- executionRef: binding.identity.search
3146
- },
3147
- artifactKind: "prompt",
3148
- objective: {
3149
- metric: "composite",
3150
- direction: "maximize",
3151
- judge: binding.identity.judge ?? { unknown: "runOptimization receives its judges as functions without a pinned source" },
3152
- claim: binding.identity.claim ?? developmentClaim(args.splitDigest)
3153
- },
3154
- splits: {
3155
- train,
3156
- selection: [],
3157
- test: [],
3158
- heldOutUnits: true
3159
- },
3160
- policy: {
3161
- expansion: args.expansion,
3162
- allocation: "uniform",
3163
- seed: args.seed
3164
- },
3165
- budget: {
3166
- maxUsd: args.maxUsd,
3167
- maxCells: null,
3168
- maxNodes: null,
3169
- deadline: null,
3170
- maxConcurrency: null,
3171
- reservedClaimUsd: 0
3172
- },
3173
- containment: null,
3174
- derivedFrom: null,
3175
- identity
3176
- });
3177
- const search = new OptimizationSearch({
3178
- recorder,
3179
- binding,
3180
- costLedger: args.costLedger,
3181
- scenarioIds: args.scenarios.map((scenario) => scenario.id),
3182
- reps: args.reps,
3183
- identity,
3184
- proposerName: args.proposerName
3185
- });
3186
- const root = await recorder.registerNode(surfaceNode(recorder, args.baselineSurface));
3187
- await recorder.recordEdge({
3188
- childNodeId: root.nodeId,
3189
- parents: [],
3190
- operator: "seed",
3191
- attribution: "explicit",
3192
- proposer: null,
3193
- proposalKey: "baseline",
3194
- rationale: { unknown: "the baseline is the caller-supplied starting surface" },
3195
- diffs: [],
3196
- label: "baseline"
3197
- });
3198
- await search.recordCells(root.nodeId, "root", args.baselineCells);
3199
- return {
3200
- search,
3201
- rootNodeId: root.nodeId
3202
- };
3203
- }
3204
- /** Start one generation's candidate-generation operation, before `propose()`. */
3205
- async startGeneration(generation) {
3206
- await this.recorder.startOperation({
3207
- operationId: generationOperationId(generation),
3208
- operationKind: "candidate-generation"
3209
- });
3210
- }
3211
- /** Record the generation's proposal: its operation, then one node and edge per candidate. */
3212
- async recordProposal(input) {
3213
- const operationId = generationOperationId(input.generation);
3214
- await this.recorder.recordOperation({
3215
- operationId,
3216
- operationKind: "candidate-generation",
3217
- execution: this.binding.identity.proposer,
3218
- outcome: { status: "completed" },
3219
- accounting: this.proposalAccounting()
3220
- });
3221
- const nodeIds = [];
3222
- for (const [index, candidate] of input.candidates.entries()) {
3223
- const { nodeId } = await this.recorder.registerNode(surfaceNode(this.recorder, candidate.surface));
3224
- await this.recorder.recordEdge({
3225
- childNodeId: nodeId,
3226
- parents: [input.parent.nodeId],
3227
- operator: "improve",
3228
- attribution: "explicit",
3229
- proposer: {
3230
- kind: "optimizer",
3231
- name: this.proposerName,
3232
- operationId,
3233
- source: this.binding.identity.proposer.source
3234
- },
3235
- proposalKey: `gen-${input.generation}:candidate-${index}`,
3236
- selection: {
3237
- rule: input.selectionRule,
3238
- evidence: input.parent.composite === null ? {} : { parentComposite: input.parent.composite }
3239
- },
3240
- rationale: candidate.rationale,
3241
- diffs: [surfaceDiff(this.recorder, input.parent.surface, candidate.surface)],
3242
- label: candidate.label
3243
- });
3244
- nodeIds.push(nodeId);
3245
- }
3246
- return nodeIds;
3247
- }
3248
- /** Close a generation whose proposer produced nothing or failed. */
3249
- async failGeneration(generation, message) {
3250
- await this.recorder.recordOperation({
3251
- operationId: generationOperationId(generation),
3252
- operationKind: "candidate-generation",
3253
- execution: this.binding.identity.proposer,
3254
- outcome: {
3255
- status: "failed",
3256
- failure: {
3257
- code: "no-candidates",
3258
- message
4091
+ //#region src/campaign/search-policy.ts
4092
+ /**
4093
+ * Expansion policies: which node a search expands next, with which operator,
4094
+ * and which node it currently keeps.
4095
+ *
4096
+ * A policy reads a `SearchPolicyView`: nodes whose screen finished and their
4097
+ * per-unit scores on the policy's split. The view holds no test cells, so a
4098
+ * policy cannot see the sealed claim data. Its decisions spend budget and
4099
+ * claim nothing; a claim comes only from the sealed test split.
4100
+ */
4101
+ /**
4102
+ * The hill climb. The leader starts at the root; a screened node that scored
4103
+ * every unit of its screen takes the lead when its mean on the units it shares
4104
+ * with the leader beats the leader's mean on those units. Nodes are taken in
4105
+ * registration order, so the leader is a pure function of the ledger. The
4106
+ * policy expands the leader, and only once every earlier child is screened,
4107
+ * so each proposal sees every result before it.
4108
+ */
4109
+ function incumbent(options = {}) {
4110
+ const { patience } = options;
4111
+ if (patience !== void 0 && (!Number.isSafeInteger(patience) || patience < 1)) throw new TypeError(`incumbent: patience must be a positive integer, got ${String(patience)}`);
4112
+ return hillClimb({
4113
+ name: patience === void 0 ? "incumbent" : `incumbent(patience=${patience})`,
4114
+ patience,
4115
+ parent: (_view, leader) => ({
4116
+ nodeId: leader,
4117
+ evidence: {}
4118
+ })
4119
+ });
4120
+ }
4121
+ /**
4122
+ * The hill climb with a different parent: each expansion draws two distinct
4123
+ * members of the Pareto frontier (per-unit means on the policy split) with a
4124
+ * PRNG seeded from `seed` and the expansion index, and keeps the one with the
4125
+ * larger crowding distance, the more isolated. Boundary members carry infinite
4126
+ * distance. Ties fall to the higher mean, then to the earlier node. The leader
4127
+ * rule is the incumbent's: a frontier parent explores, and only a node that
4128
+ * beats the leader leads.
4129
+ */
4130
+ function crowdedFrontierParent(options) {
4131
+ const { seed } = options;
4132
+ if (!Number.isInteger(seed)) throw new TypeError(`crowdedFrontierParent: seed must be an integer, got ${String(seed)}`);
4133
+ return hillClimb({
4134
+ name: `crowded-frontier(seed=${seed})`,
4135
+ parent: (view, leader) => {
4136
+ const frontier = paretoMembers(view);
4137
+ if (frontier.length <= 1) return {
4138
+ nodeId: frontier[0]?.nodeId ?? leader,
4139
+ evidence: {}
4140
+ };
4141
+ const rng = mulberry32(seed ^ Math.imul(view.expansions + 1, 2654435761) | 0);
4142
+ const first = Math.floor(rng() * frontier.length);
4143
+ const offset = Math.floor(rng() * (frontier.length - 1));
4144
+ const second = offset >= first ? offset + 1 : offset;
4145
+ const a = frontier[first];
4146
+ const b = frontier[second];
4147
+ const winner = compareCrowded(a, b) <= 0 ? a : b;
4148
+ return {
4149
+ nodeId: winner.nodeId,
4150
+ evidence: {
4151
+ frontierSize: frontier.length,
4152
+ crowding: Number.isFinite(winner.distance) ? winner.distance : -1
3259
4153
  }
3260
- },
3261
- accounting: this.proposalAccounting()
3262
- });
3263
- }
3264
- /** Allocate every designed cell of a node before its campaign runs. */
3265
- async allocate(nodeId) {
3266
- for (const taskId of this.scenarioIds) for (let rep = 0; rep < this.reps; rep++) await this.recorder.allocateCell({
4154
+ };
4155
+ }
4156
+ });
4157
+ }
4158
+ function hillClimb(spec) {
4159
+ const leader = (view) => {
4160
+ let current = view.rootNodeId;
4161
+ for (const nodeId of view.screened) {
4162
+ if (nodeId === current || !view.complete(nodeId)) continue;
4163
+ if (beats(view, nodeId, current)) current = nodeId;
4164
+ }
4165
+ return current;
4166
+ };
4167
+ return {
4168
+ name: spec.name,
4169
+ ...spec.patience === void 0 ? {} : { patience: spec.patience },
4170
+ leader,
4171
+ expand(view) {
4172
+ if (view.screening > 0) return null;
4173
+ const kept = leader(view);
4174
+ const chosen = spec.parent(view, kept);
4175
+ const units = view.unitScores(chosen.nodeId);
4176
+ return {
4177
+ parents: [chosen.nodeId],
4178
+ operator: "improve",
4179
+ selection: {
4180
+ rule: spec.name,
4181
+ evidence: {
4182
+ ...chosen.evidence,
4183
+ units: units.length,
4184
+ ...units.length > 0 ? { mean: mean$1(units.map((unit) => unit.mean)) } : {},
4185
+ isLeader: chosen.nodeId === kept ? 1 : 0
4186
+ }
4187
+ }
4188
+ };
4189
+ }
4190
+ };
4191
+ }
4192
+ /** True when `challenger` improves on `holder` over the units both scored. */
4193
+ function beats(view, challenger, holder) {
4194
+ const held = new Map(view.unitScores(holder).map((unit) => [unit.unitId, unit.mean]));
4195
+ let challengerSum = 0;
4196
+ let holderSum = 0;
4197
+ let shared = 0;
4198
+ for (const unit of view.unitScores(challenger)) {
4199
+ const other = held.get(unit.unitId);
4200
+ if (other === void 0) continue;
4201
+ challengerSum += unit.mean;
4202
+ holderSum += other;
4203
+ shared += 1;
4204
+ }
4205
+ if (shared === 0) return false;
4206
+ return view.direction === "maximize" ? challengerSum > holderSum : challengerSum < holderSum;
4207
+ }
4208
+ /** Non-dominated complete screened nodes over the root's units, with crowding. */
4209
+ function paretoMembers(view) {
4210
+ const units = view.unitScores(view.rootNodeId).map((unit) => unit.unitId);
4211
+ const sign = view.direction === "maximize" ? 1 : -1;
4212
+ const members = [];
4213
+ view.screened.forEach((nodeId, ordinal) => {
4214
+ if (!view.complete(nodeId)) return;
4215
+ const objectives = new Map(view.unitScores(nodeId).map((unit) => [unit.unitId, unit.mean]));
4216
+ if (units.some((unit) => !objectives.has(unit))) return;
4217
+ members.push({
3267
4218
  nodeId,
3268
- taskId,
3269
- split: "train",
3270
- rep,
3271
- stage: "train"
4219
+ ordinal,
4220
+ mean: sign * mean$1([...objectives.values()]),
4221
+ objectives,
4222
+ distance: 0
3272
4223
  });
3273
- }
3274
- /** Settle a node's campaign cells; allocates any the campaign ran beyond the design. */
3275
- async recordCells(nodeId, stage, cells) {
3276
- for (const cell of cells) {
3277
- const cellId = await this.recorder.allocateCell({
3278
- nodeId,
3279
- taskId: cell.scenarioId,
3280
- split: "train",
3281
- rep: cell.rep,
3282
- stage
3283
- });
3284
- await this.recorder.settleCell({
3285
- cellId,
3286
- attempt: 1,
3287
- outcome: cellOutcome(cell),
3288
- accounting: cellAccounting(cell),
3289
- identity: {
3290
- ...this.identity,
3291
- model: this.cellModel(cell)
3292
- },
3293
- wallMs: cell.durationMs,
3294
- traceRef: { unknown: "runCampaign reports no trace id per cell" }
4224
+ });
4225
+ if (members.length === 0 || units.length === 0) return members;
4226
+ return paretoFrontierWithCrowding(members, units.map((unit) => ({
4227
+ name: unit,
4228
+ direction: view.direction,
4229
+ value: (member) => member.objectives.get(unit)
4230
+ }))).map(({ candidate, distance }) => ({
4231
+ ...candidate,
4232
+ distance
4233
+ }));
4234
+ }
4235
+ /** Negative when `a` wins the tournament. */
4236
+ function compareCrowded(a, b) {
4237
+ if (a.distance !== b.distance) return a.distance > b.distance ? -1 : 1;
4238
+ if (a.mean !== b.mean) return a.mean > b.mean ? -1 : 1;
4239
+ return a.ordinal - b.ordinal;
4240
+ }
4241
+ function mean$1(values) {
4242
+ let total = 0;
4243
+ for (const value of values) total += value;
4244
+ return total / values.length;
4245
+ }
4246
+ //#endregion
4247
+ //#region src/campaign/types.ts
4248
+ /** Type guard: a proposal carrying its rationale vs a bare
4249
+ * surface. The loop branches on this to populate `GenerationCandidate`. */
4250
+ function isProposedCandidate(value) {
4251
+ return typeof value === "object" && value !== null && "surface" in value && "label" in value && "rationale" in value;
4252
+ }
4253
+ const LABEL_TRUST_RANK = {
4254
+ unverified: 0,
4255
+ "verified-signal": 1,
4256
+ "human-rated": 2
4257
+ };
4258
+ /** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
4259
+ function labelTrustRank(trust) {
4260
+ return LABEL_TRUST_RANK[trust ?? "unverified"];
4261
+ }
4262
+ //#endregion
4263
+ //#region src/campaign/presets/run-optimization.ts
4264
+ /**
4265
+ * `runOptimization` improves a mutable surface with a caller-owned proposer,
4266
+ * as a search on the one kernel (`runSearch`).
4267
+ *
4268
+ * The baseline is the seeded root. The scenarios are the train split: the
4269
+ * proposer reads their scores, and the policy ranks on them because the loop
4270
+ * has no private selection split. A "generation" is one proposal of
4271
+ * `populationSize` candidates from the node the policy chose (by default the
4272
+ * incumbent, once every earlier candidate is measured). Every candidate is a
4273
+ * node measured on every scenario at every repeat, one cell at a time on the
4274
+ * run's lanes. The kept surface is the policy's leader: a budget decision on
4275
+ * the train split that claims nothing; `runImprovementLoop` compares it with
4276
+ * the baseline on separate cases.
4277
+ *
4278
+ * The search ledger is always written, beside the run (`<runDir>/search/`) or
4279
+ * where `searchLedger` puts it, and it is the run's only checkpoint: running
4280
+ * again on the same run directory and options continues an interrupted run
4281
+ * instead of starting over, and reruns nothing that settled.
4282
+ */
4283
+ /** Improve a surface as a search on the kernel; see the module comment. */
4284
+ async function runOptimization(opts) {
4285
+ const candidateConcurrency = opts.candidateConcurrency ?? 1;
4286
+ if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) throw new Error("runOptimization: runDir is required and must be a non-empty string");
4287
+ if (!Number.isInteger(candidateConcurrency) || candidateConcurrency < 1) throw new Error("runOptimization: candidateConcurrency must be a positive integer");
4288
+ for (const [name, value] of [["populationSize", opts.populationSize], ["maxGenerations", opts.maxGenerations]]) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`runOptimization: ${name} must be a non-negative integer`);
4289
+ const initialFindings = immutableProposalSnapshot(assertProposalFindings(opts.findings ?? [], "runOptimization initial proposal findings"), "initial findings");
4290
+ const baselineSurface = immutableProposalSnapshot(opts.baselineSurface, "baseline surface");
4291
+ const runDir = resolveRunDir(opts.runDir, opts.repo);
4292
+ const storage = opts.storage ?? fsCampaignStorage();
4293
+ const costLedger = opts.costLedger ?? createRunCostLedger({
4294
+ storage,
4295
+ runDir,
4296
+ costCeilingUsd: opts.costCeiling
4297
+ });
4298
+ const reps = opts.reps ?? 1;
4299
+ const seed = opts.seed ?? 42;
4300
+ const requireJudgeScore = (opts.judges?.length ?? 0) > 0;
4301
+ const premeasured = opts.premeasuredBaseline ? validatedPremeasuredBaseline({
4302
+ input: opts.premeasuredBaseline,
4303
+ baselineSurface,
4304
+ scenarios: opts.scenarios,
4305
+ reps,
4306
+ seed,
4307
+ judges: opts.judges ?? [],
4308
+ dispatchRef: surfaceDispatchRef(baselineSurface, opts.dispatchRef),
4309
+ requireJudgeScore
4310
+ }) : void 0;
4311
+ const splitDigest = campaignSplitDigest(opts.scenarios, reps);
4312
+ const binding = opts.searchLedger ?? await defaultBinding(opts, runDir, storage);
4313
+ const { identity } = binding;
4314
+ const execution = {
4315
+ model: identity.model,
4316
+ agent: identity.agent,
4317
+ benchmark: {
4318
+ uri: "campaign://scenarios",
4319
+ revision: splitDigest
4320
+ }
4321
+ };
4322
+ const policy = opts.policy ?? incumbent();
4323
+ const allocation = uniform({ reps });
4324
+ const recorder = await SearchRecorder.open({
4325
+ ledger: binding.ledger,
4326
+ storage
4327
+ }, {
4328
+ subject: identity.subject ?? opts.proposer.kind,
4329
+ process: {
4330
+ name: "runOptimization",
4331
+ executionRef: identity.search
4332
+ },
4333
+ artifactKind: artifactKindOf(baselineSurface),
4334
+ objective: {
4335
+ metric: "composite",
4336
+ direction: "maximize",
4337
+ judge: identity.judge ?? { unknown: "runOptimization receives its judges as functions without a pinned source" },
4338
+ claim: identity.claim ?? developmentClaim(splitDigest)
4339
+ },
4340
+ splits: {
4341
+ train: opts.scenarios.map((scenario) => ({
4342
+ taskId: scenario.id,
4343
+ unitId: scenario.id,
4344
+ source: {
4345
+ uri: `scenario://${scenario.id}`,
4346
+ revision: hashCanonical(scenario)
4347
+ }
4348
+ })),
4349
+ selection: [],
4350
+ test: [],
4351
+ heldOutUnits: true
4352
+ },
4353
+ policy: {
4354
+ expansion: policy.name,
4355
+ allocation: allocation.name,
4356
+ seed
4357
+ },
4358
+ budget: {
4359
+ maxUsd: opts.costCeiling ?? null,
4360
+ maxCells: null,
4361
+ maxNodes: 1 + opts.populationSize * opts.maxGenerations,
4362
+ deadline: null,
4363
+ maxConcurrency: null,
4364
+ reservedClaimUsd: 0
4365
+ },
4366
+ containment: null,
4367
+ derivedFrom: null,
4368
+ identity: execution
4369
+ });
4370
+ const nodes = new SurfaceNodes({
4371
+ opts,
4372
+ recorder,
4373
+ runDir,
4374
+ costLedger,
4375
+ execution,
4376
+ premeasured
4377
+ });
4378
+ const scenarioById = new Map(opts.scenarios.map((scenario) => [scenario.id, scenario]));
4379
+ let findings = initialFindings;
4380
+ const executor = {
4381
+ lanes: () => [{
4382
+ name: "campaign",
4383
+ capacity: candidateConcurrency * (opts.maxConcurrency ?? 2),
4384
+ costCap: "estimate",
4385
+ cellUsd: 0
4386
+ }],
4387
+ place: () => "campaign",
4388
+ run: (work) => nodes.runCell(work, scenarioById),
4389
+ adopt: (work) => nodes.adoptCell(work)
4390
+ };
4391
+ const proposer = opts.proposer;
4392
+ const proposerSource = identity.proposer.source;
4393
+ const port = {
4394
+ name: proposer.kind,
4395
+ kind: "optimizer",
4396
+ source: proposerSource,
4397
+ execution: identity.proposer,
4398
+ childrenPerProposal: opts.populationSize,
4399
+ async propose(request) {
4400
+ const phases = [
4401
+ "search.proposal",
4402
+ "analysis.baseline",
4403
+ "analysis.generation"
4404
+ ];
4405
+ const before = new Set(phases.flatMap((phase) => costLedger.list({ phase })).map(receiptKey));
4406
+ const accounting = () => {
4407
+ const fresh = phases.flatMap((phase) => costLedger.list({ phase })).filter((receipt) => !before.has(receiptKey(receipt)));
4408
+ return {
4409
+ accounting: receiptAccounting(fresh),
4410
+ execution: opts.searchLedger ? identity.proposer : proposalExecution(fresh, identity.model.provider, proposerSource)
4411
+ };
4412
+ };
4413
+ const earlier = await recorder.state();
4414
+ const root = earlier.rootNodeId;
4415
+ const rootCampaign = nodes.campaign(earlier, root);
4416
+ const rootCoverage = campaignCoverage(rootCampaign.cells, opts.scenarios, reps, requireJudgeScore);
4417
+ if (!rootCoverage.complete) throw new Error(`runOptimization: ${premeasured ? "premeasured baseline" : "baseline"} is incomplete (${rootCoverage.scorableCellIds.length}/${rootCoverage.expectedCellIds.length} designed cells scorable) — ${formatCoverageFailures(rootCoverage)}. Refusing to optimize against an incomplete incumbent.`);
4418
+ const generation = nodes.generations(earlier).length;
4419
+ if (opts.analyzeGeneration) {
4420
+ const previous = generation - 1;
4421
+ const candidates = (previous < 0 ? [root] : nodes.expansionNodes(earlier, nodes.generations(earlier)[previous])).map((nodeId) => {
4422
+ const campaign = nodes.campaign(earlier, nodeId);
4423
+ return {
4424
+ surfaceHash: surfaceHash(nodes.surface(earlier, nodeId)),
4425
+ campaign,
4426
+ composite: campaignMeanCompositeOrNull(campaign)
4427
+ };
4428
+ });
4429
+ const history = nodes.history(earlier, policy);
4430
+ if (candidates.some(({ campaign }) => campaign.cells.length > 0)) {
4431
+ const fresh = await opts.analyzeGeneration({
4432
+ generation: previous,
4433
+ runDir: previous < 0 ? rootCampaign.runDir : `${runDir}/candidates`,
4434
+ candidates,
4435
+ history,
4436
+ costLedger,
4437
+ costPhase: previous < 0 ? "analysis.baseline" : "analysis.generation"
4438
+ });
4439
+ if (!Array.isArray(fresh)) throw new TypeError("runOptimization: analyzeGeneration must return an array");
4440
+ findings = immutableProposalSnapshot(assertProposalFindings(fresh, "runOptimization analysis findings"), "analysis findings");
4441
+ }
4442
+ }
4443
+ const state = await recorder.state();
4444
+ const proposalHistory = immutableProposalSnapshot(nodes.history(state, policy), "history");
4445
+ const parent = request.parents[0];
4446
+ const context = Object.freeze({
4447
+ currentSurface: immutableProposalSnapshot(parent.artifact, "current surface"),
4448
+ operator: request.operator,
4449
+ history: proposalHistory,
4450
+ findings: immutableProposalSnapshot(assertProposalFindings(findings, "runOptimization proposal findings"), "findings"),
4451
+ populationSize: opts.populationSize,
4452
+ generation,
4453
+ signal: request.signal,
4454
+ baselineOutcome: immutableProposalSnapshot(nodes.outcome(state, root), "baseline"),
4455
+ incumbentOutcome: immutableProposalSnapshot(nodes.outcome(state, request.leader), "incumbent outcome"),
4456
+ parentOutcome: immutableProposalSnapshot(nodes.outcome(state, parent.nodeId), "parent outcome"),
4457
+ maxImprovementShots: opts.maxImprovementShots,
4458
+ paretoParents: immutableProposalSnapshot(nodes.frontier(state), "Pareto parents"),
4459
+ costLedger,
4460
+ costPhase: "search.proposal"
3295
4461
  });
4462
+ if (proposer.decide?.({ history: proposalHistory }).stop) return {
4463
+ children: [],
4464
+ stop: "the proposer decided to stop",
4465
+ ...accounting()
4466
+ };
4467
+ const proposed = await proposer.propose(context);
4468
+ if (!Array.isArray(proposed)) throw new TypeError("runOptimization: proposer must return an array");
4469
+ return {
4470
+ children: immutableProposalSnapshot(proposed, "candidate outputs").slice(0, opts.populationSize).map((candidate) => isProposedCandidate(candidate) ? {
4471
+ artifact: candidate.surface,
4472
+ label: candidate.label,
4473
+ rationale: candidate.rationale,
4474
+ ...candidate.attribution ? { attribution: candidate.attribution } : {}
4475
+ } : {
4476
+ artifact: candidate,
4477
+ label: "",
4478
+ rationale: ""
4479
+ }),
4480
+ ...accounting()
4481
+ };
3296
4482
  }
4483
+ };
4484
+ const result = await runSearch({
4485
+ recorder,
4486
+ root: baselineSurface,
4487
+ codec: surfaceCodec,
4488
+ policy,
4489
+ allocation,
4490
+ proposer: port,
4491
+ executor,
4492
+ maxExpansions: opts.maxGenerations,
4493
+ signal: opts.signal
4494
+ });
4495
+ const state = result.state;
4496
+ const winnerSurface = nodes.surface(state, result.leader);
4497
+ const firstEdge = state.edge(state.node(result.leader).edgeIds[0]);
4498
+ const winnerRationale = firstEdge.operator === "seed" || !("sha256" in firstEdge.rationale) ? void 0 : recorder.readBlob(firstEdge.rationale).text;
4499
+ const expansions = nodes.generations(state);
4500
+ return {
4501
+ generations: nodes.history(state, policy).map((record) => ({
4502
+ record,
4503
+ surfaces: nodes.expansionNodes(state, expansions[record.generationIndex]).map((nodeId) => {
4504
+ const surface = nodes.surface(state, nodeId);
4505
+ return {
4506
+ surfaceHash: surfaceHash(surface),
4507
+ surface,
4508
+ campaign: nodes.campaign(state, nodeId)
4509
+ };
4510
+ })
4511
+ })),
4512
+ baselineSurface,
4513
+ winnerSurface,
4514
+ winnerSurfaceHash: surfaceHash(winnerSurface),
4515
+ ...firstEdge.operator !== "seed" && firstEdge.label ? { winnerLabel: firstEdge.label } : {},
4516
+ ...winnerRationale ? { winnerRationale } : {},
4517
+ baselineCampaign: nodes.campaign(state, state.rootNodeId),
4518
+ cost: costLedger.summary(),
4519
+ searchHistory: recorder.receipt({
4520
+ producerId: proposer.kind,
4521
+ runId: runDir
4522
+ }),
4523
+ paretoFrontier: nodes.frontier(state)
4524
+ };
4525
+ }
4526
+ /** A mutable surface as a search artifact. */
4527
+ const surfaceCodec = {
4528
+ node: surfaceNode,
4529
+ diff: surfaceDiff,
4530
+ load: (recorder, node) => {
4531
+ const stored = recorder.readBlob(node.artifact);
4532
+ if (stored.kind !== "mutable-surface" || stored.surface === void 0) throw new Error(`runOptimization: node ${node.nodeId} does not hold a mutable surface`);
4533
+ return stored.surface;
4534
+ }
4535
+ };
4536
+ /**
4537
+ * The run's view of its surface nodes: where each one's cells run, its
4538
+ * campaign, and the proposer's reads over them. Everything here derives from
4539
+ * the ledger and the per-cell campaign cache, so a resumed run reads the same.
4540
+ */
4541
+ var SurfaceNodes = class {
4542
+ opts;
4543
+ recorder;
4544
+ runDir;
4545
+ costLedger;
4546
+ execution;
4547
+ premeasured;
4548
+ storage;
4549
+ campaigns = /* @__PURE__ */ new Map();
4550
+ proposals = /* @__PURE__ */ new Map();
4551
+ /** Cells this process ran or adopted, by node, keyed `scenarioId:rep`. */
4552
+ cells = /* @__PURE__ */ new Map();
4553
+ constructor(input) {
4554
+ this.opts = input.opts;
4555
+ this.recorder = input.recorder;
4556
+ this.runDir = input.runDir;
4557
+ this.costLedger = input.costLedger;
4558
+ this.execution = input.execution;
4559
+ this.premeasured = input.premeasured;
4560
+ this.storage = input.opts.storage ?? fsCampaignStorage();
4561
+ }
4562
+ /** `<runDir>/baseline` for the root, `<runDir>/candidates/<surfaceHash>`
4563
+ * for every other node: content-addressed, so a cell's directory needs no
4564
+ * ledger read and one surface keeps one cache across the run's searches. */
4565
+ nodeDir(surface, root) {
4566
+ return root ? `${this.runDir}/baseline` : `${this.runDir}/candidates/${surfaceHash(surface)}`;
4567
+ }
4568
+ surface(state, nodeId) {
4569
+ return surfaceCodec.load(this.recorder, state.node(nodeId));
4570
+ }
4571
+ /** Nodes proposal `expansion` registered, in the order it returned them. */
4572
+ expansionNodes(state, expansion) {
4573
+ const blob = this.proposal(state, expansion);
4574
+ if (!blob) return [];
4575
+ const ids = [];
4576
+ for (const child of blob.children) {
4577
+ const nodeId = state.nodeIdForDigest(child.node.artifactDigest);
4578
+ if (nodeId === void 0 || ids.includes(nodeId)) continue;
4579
+ if (this.origin(state, nodeId).expansion === expansion) ids.push(nodeId);
4580
+ }
4581
+ return ids;
3297
4582
  }
3298
4583
  /**
3299
- * Decide every node and close. The winner is selected; every other node is
3300
- * rejected. Allocated cells a campaign never returned are cancelled.
4584
+ * The scored attempt of a cell an earlier process ran, from its node's
4585
+ * cache; null otherwise, so the kernel runs the cell. A failure is not
4586
+ * adopted: the stop that interrupted the earlier process may have caused it.
3301
4587
  */
3302
- async finish(input) {
3303
- const openCells = (await this.recorder.state()).cells().filter((cell) => cell.attempts === 0 && !cell.cancelled);
3304
- for (const cell of openCells) await this.recorder.cancelCell({
3305
- cellId: cell.cellId,
3306
- reason: "aborted"
4588
+ async adoptCell(work) {
4589
+ if (work.stage === "root" && this.premeasured) return this.premeasuredCell(work);
4590
+ const dir = this.nodeDir(work.artifact, work.stage === "root");
4591
+ const cached = this.storage.read(cellCachePath(dir, `${work.taskId}:${work.rep}`));
4592
+ const cell = cached === void 0 ? void 0 : JSON.parse(cached);
4593
+ if (cell?.manifestHash !== this.manifestFor(work.artifact)) return null;
4594
+ this.remember(work.nodeId, cell);
4595
+ return this.cellResult(cell, work);
4596
+ }
4597
+ /** Run one cell as a one-cell campaign in its node's directory; a cached
4598
+ * cell is read back, not dispatched again. */
4599
+ async runCell(work, scenarios) {
4600
+ if (work.stage === "root" && this.premeasured) return this.premeasuredCell(work);
4601
+ if (!scenarios.has(work.taskId)) throw new Error(`runOptimization: cell ${work.cellId} names unknown scenario ${work.taskId}`);
4602
+ const dir = this.nodeDir(work.artifact, work.stage === "root");
4603
+ const cell = (await this.campaignRun(dir, work.artifact, {
4604
+ signal: work.signal,
4605
+ costPhase: work.stage === "root" ? "search.baseline" : "search.candidate",
4606
+ cellFilter: ({ scenario, rep }) => scenario.id === work.taskId && rep === work.rep
4607
+ })).cells[0];
4608
+ if (!cell) throw new Error(`runOptimization: cell ${work.cellId} produced no campaign cell`);
4609
+ this.remember(work.nodeId, cell);
4610
+ return this.cellResult(cell, work);
4611
+ }
4612
+ /** The node's campaign over the cells the ledger settled: the cells this
4613
+ * process ran, else their cached results or failure receipts. */
4614
+ campaign(state, nodeId) {
4615
+ const root = nodeId === state.rootNodeId;
4616
+ if (root && this.premeasured) return this.premeasured;
4617
+ const surface = this.surface(state, nodeId);
4618
+ const dir = this.nodeDir(surface, root);
4619
+ const settled = state.cells({ nodeId }).filter((cell) => cell.attempts > 0).map((cell) => ({
4620
+ taskId: cell.taskId,
4621
+ rep: cell.rep
4622
+ }));
4623
+ const key = settled.map(({ taskId, rep }) => `${taskId}:${rep}`).sort().join("\n");
4624
+ const held = this.campaigns.get(nodeId);
4625
+ if (held?.cells === key) return held.campaign;
4626
+ const known = this.cells.get(nodeId);
4627
+ const cells = settled.map(({ taskId, rep }) => {
4628
+ const cell = known?.get(`${taskId}:${rep}`) ?? this.storedCell(dir, surface, taskId, rep);
4629
+ if (!cell) throw new Error(`runOptimization: cell ${taskId}:${rep} of node ${nodeId} settled, but ${dir} holds neither its cached result nor its failure receipt`);
4630
+ return cell;
3307
4631
  });
3308
- for (const node of (await this.recorder.state()).nodes()) await this.recorder.decideNode({
3309
- nodeId: node.nodeId,
3310
- decision: node.nodeId === input.winnerNodeId ? { status: "selected" } : { status: "rejected" },
3311
- rule: "incumbent",
3312
- reason: node.nodeId === input.winnerNodeId ? "the incumbent when the loop stopped" : node.ordinal === 0 || input.promotedNodeIds.has(node.nodeId) ? "a later candidate replaced it as the incumbent" : input.incompleteNodeIds.has(node.nodeId) ? "the candidate missed a designed cell and could not be ranked" : "the candidate did not beat the incumbent"
4632
+ cells.sort((a, b) => a.cellId < b.cellId ? -1 : a.cellId > b.cellId ? 1 : 0);
4633
+ const reps = this.opts.reps ?? 1;
4634
+ const seed = this.opts.seed ?? 42;
4635
+ const judges = this.opts.judges ?? [];
4636
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4637
+ const durationMs = cells.reduce((sum, cell) => sum + cell.durationMs, 0);
4638
+ const campaign = {
4639
+ manifestHash: this.manifestFor(surface),
4640
+ splitDigest: campaignSplitDigest(this.opts.scenarios, reps),
4641
+ seed,
4642
+ reps,
4643
+ startedAt: now,
4644
+ endedAt: now,
4645
+ durationMs,
4646
+ cells,
4647
+ aggregates: computeAggregates(cells, judges, seed, this.costLedger.summary({ tags: { runDir: dir } })),
4648
+ runDir: dir,
4649
+ artifactsByPath: {},
4650
+ scenarios: this.opts.scenarios.map(campaignScenarioIdentity)
4651
+ };
4652
+ this.campaigns.set(nodeId, {
4653
+ cells: key,
4654
+ campaign
3313
4655
  });
3314
- await this.recorder.close({
3315
- reason: input.reason,
3316
- claim: null
4656
+ return campaign;
4657
+ }
4658
+ outcome(state, nodeId) {
4659
+ const campaign = this.campaign(state, nodeId);
4660
+ const reps = this.opts.reps ?? 1;
4661
+ const coverage = campaignCoverage(campaign.cells, this.opts.scenarios, reps, (this.opts.judges?.length ?? 0) > 0);
4662
+ const expansion = nodeId === state.rootNodeId ? -1 : this.generationOf(state, this.origin(state, nodeId).expansion);
4663
+ return toScoredSurfaceOutcome(surfaceHash(this.surface(state, nodeId)), campaign, coverage, expansion);
4664
+ }
4665
+ /** The Pareto frontier over every measured, complete node. */
4666
+ frontier(state) {
4667
+ const scored = [];
4668
+ for (const node of state.nodes()) {
4669
+ if (node.status === "invalid" || node.cellCount === 0) continue;
4670
+ const cells = state.cells({ nodeId: node.nodeId });
4671
+ if (cells.some((cell) => cell.attempts === 0 && cell.cancelled === null)) continue;
4672
+ if (cells.some((cell) => cell.score === null)) continue;
4673
+ const campaign = this.campaign(state, node.nodeId);
4674
+ const edge = state.edge(node.edgeIds[0]);
4675
+ scored.push(toParetoParent(this.surface(state, node.nodeId), campaign, node.nodeId === state.rootNodeId ? -1 : this.generationOf(state, this.origin(state, node.nodeId).expansion), edge.operator === "seed" ? void 0 : edge.label || void 0));
4676
+ }
4677
+ return computeParetoFrontier(scored);
4678
+ }
4679
+ /** Kernel expansions that registered a candidate, in order: generation g
4680
+ * is `generations(state)[g]`. */
4681
+ generations(state) {
4682
+ const expansions = [];
4683
+ for (let expansion = 0; state.operation(`expand-${expansion}`); expansion++) if (this.expansionNodes(state, expansion).length > 0) expansions.push(expansion);
4684
+ return expansions;
4685
+ }
4686
+ /** One record per generation, with its candidates and the one that took
4687
+ * the lead, if any. */
4688
+ history(state, policy) {
4689
+ const records = [];
4690
+ const measured = (nodeId) => state.node(nodeId).status !== "invalid" && state.cells({ nodeId }).every((cell) => cell.attempts > 0 || cell.cancelled !== null);
4691
+ const screened = [state.rootNodeId];
4692
+ let leader = policy.leader(searchPolicyView(state, {
4693
+ screened,
4694
+ screening: 0,
4695
+ expansions: 0
4696
+ }));
4697
+ for (const [generation, expansion] of this.generations(state).entries()) {
4698
+ const nodeIds = this.expansionNodes(state, expansion);
4699
+ screened.push(...nodeIds.filter(measured));
4700
+ screened.sort((a, b) => state.node(a).ordinal - state.node(b).ordinal);
4701
+ const next = policy.leader(searchPolicyView(state, {
4702
+ screened,
4703
+ screening: 0,
4704
+ expansions: expansion + 1
4705
+ }));
4706
+ const candidates = [];
4707
+ for (const nodeId of nodeIds) candidates.push(this.candidate(state, nodeId, expansion));
4708
+ records.push({
4709
+ generationIndex: generation,
4710
+ candidates,
4711
+ promoted: next !== leader && nodeIds.includes(next) ? [surfaceHash(this.surface(state, next))] : []
4712
+ });
4713
+ leader = next;
4714
+ }
4715
+ return records;
4716
+ }
4717
+ candidate(state, nodeId, expansion) {
4718
+ const campaign = this.campaign(state, nodeId);
4719
+ const coverage = campaignCoverage(campaign.cells, this.opts.scenarios, this.opts.reps ?? 1, (this.opts.judges?.length ?? 0) > 0);
4720
+ const breakdown = campaignBreakdown(campaign);
4721
+ const child = this.proposal(state, expansion).children.find((entry) => state.nodeIdForDigest(entry.node.artifactDigest) === nodeId);
4722
+ return {
4723
+ surfaceHash: surfaceHash(this.surface(state, nodeId)),
4724
+ composite: campaignMeanCompositeOrNull(campaign),
4725
+ eligibleForPromotion: coverage.complete,
4726
+ coverage: {
4727
+ expectedCells: coverage.expectedCellIds.length,
4728
+ scorableCells: coverage.scorableCellIds.length,
4729
+ unscorableCells: coverage.unscorableCells
4730
+ },
4731
+ dimensions: breakdown.dimensions,
4732
+ scenarios: breakdown.scenarios,
4733
+ ...child.label ? { label: child.label } : {},
4734
+ ...child.rationale ? { rationale: child.rationale } : {},
4735
+ ...child.attribution ? { attribution: child.attribution } : {}
4736
+ };
4737
+ }
4738
+ generationOf(state, expansion) {
4739
+ return this.generations(state).indexOf(expansion);
4740
+ }
4741
+ origin(state, nodeId) {
4742
+ const node = state.node(nodeId);
4743
+ const operationId = state.edge(node.edgeIds[0])?.proposer?.operationId ?? null;
4744
+ const expansion = operationId === null ? null : searchExpansionIndex(operationId);
4745
+ if (expansion === null) throw new Error(`runOptimization: node ${nodeId} was not proposed by this run's search`);
4746
+ return {
4747
+ expansion,
4748
+ index: this.proposal(state, expansion).children.findIndex((child) => child.node.artifactDigest === node.artifactDigest)
4749
+ };
4750
+ }
4751
+ proposal(state, expansion) {
4752
+ const held = this.proposals.get(expansion);
4753
+ if (held) return held;
4754
+ const ref = state.operation(`expand-${expansion}`)?.artifacts.find((artifact) => artifact.role === "proposal");
4755
+ if (!ref) return void 0;
4756
+ const blob = this.recorder.readBlob(ref);
4757
+ this.proposals.set(expansion, blob);
4758
+ return blob;
4759
+ }
4760
+ async campaignRun(runDir, surface, input) {
4761
+ const { baselineSurface: _baselineSurface, premeasuredBaseline: _premeasuredBaseline, dispatchWithSurface, proposer: _proposer, populationSize: _populationSize, maxGenerations: _maxGenerations, candidateConcurrency: _candidateConcurrency, maxImprovementShots: _maxImprovementShots, findings: _findings, analyzeGeneration: _analyzeGeneration, policy: _policy, searchLedger: _searchLedger, ...campaignOptions } = this.opts;
4762
+ return runCampaign({
4763
+ ...campaignOptions,
4764
+ signal: input.signal ?? this.opts.signal,
4765
+ costLedger: this.costLedger,
4766
+ costPhase: input.costPhase,
4767
+ dispatchRef: surfaceDispatchRef(surface, this.opts.dispatchRef),
4768
+ dispatch: (scenario, ctx) => dispatchWithSurface(surface, scenario, ctx),
4769
+ runDir,
4770
+ cellFilter: input.cellFilter,
4771
+ resumable: true,
4772
+ maxConcurrency: 1
3317
4773
  });
3318
- return this.recorder.receipt({
3319
- producerId: this.proposerName,
3320
- runId: input.runId
4774
+ }
4775
+ premeasuredCell(work) {
4776
+ const cell = this.premeasured.cells.find((candidate) => candidate.scenarioId === work.taskId && candidate.rep === work.rep);
4777
+ if (!cell) throw new Error(`runOptimization: premeasured baseline lacks ${work.taskId}:${work.rep}`);
4778
+ return this.cellResult(cell, work);
4779
+ }
4780
+ /** A cell's result as its campaign stored it: the cache of a scored cell,
4781
+ * the final failure receipt of a failed one. */
4782
+ /** A cell's result as its campaign stored it for this surface: the cache of
4783
+ * a scored cell, the final failure receipt of a failed one. A result another
4784
+ * surface left in the same directory does not count. */
4785
+ storedCell(dir, surface, taskId, rep) {
4786
+ const cellId = `${taskId}:${rep}`;
4787
+ const manifestHash = this.manifestFor(surface);
4788
+ const cached = this.storage.read(cellCachePath(dir, cellId));
4789
+ const scored = cached === void 0 ? void 0 : JSON.parse(cached);
4790
+ if (scored?.manifestHash === manifestHash) return scored;
4791
+ const receipt = this.storage.read(join(cellDirectory(dir, cellId), "failure-receipt.json"));
4792
+ const failed = receipt === void 0 ? void 0 : JSON.parse(receipt).cell;
4793
+ return failed?.manifestHash === manifestHash ? failed : void 0;
4794
+ }
4795
+ manifestFor(surface) {
4796
+ return computeManifestHash({
4797
+ scenarios: this.opts.scenarios,
4798
+ judges: this.opts.judges ?? [],
4799
+ dispatchRef: surfaceDispatchRef(surface, this.opts.dispatchRef),
4800
+ seed: this.opts.seed ?? 42,
4801
+ reps: this.opts.reps ?? 1
3321
4802
  });
3322
4803
  }
3323
- /** Spend booked to candidate generation since the previous generation. */
3324
- proposalAccounting() {
3325
- const receipts = this.costLedger.list({ phase: "search.proposal" });
3326
- const fresh = receipts.slice(this.proposalReceiptCount);
3327
- this.proposalReceiptCount = receipts.length;
3328
- return receiptAccounting(fresh);
3329
- }
3330
- cellModel(cell) {
3331
- const resolved = cell.resolvedModel;
3332
- if (resolved === void 0) return this.identity.model;
3333
- const provider = this.identity.model.provider;
3334
- return modelHasSnapshot(resolved) ? {
3335
- provider,
3336
- snapshot: resolved
3337
- } : {
3338
- provider,
3339
- alias: resolved,
3340
- unknown: "the provider reported a moving alias, not a snapshot"
4804
+ remember(nodeId, cell) {
4805
+ const cells = this.cells.get(nodeId) ?? /* @__PURE__ */ new Map();
4806
+ cells.set(`${cell.scenarioId}:${cell.rep}`, cell);
4807
+ this.cells.set(nodeId, cells);
4808
+ }
4809
+ cellResult(cell, work) {
4810
+ return {
4811
+ outcome: cellOutcome(cell),
4812
+ accounting: cellAccounting(cell),
4813
+ identity: {
4814
+ ...this.execution,
4815
+ model: cellModel(cell, this.execution.model)
4816
+ },
4817
+ wallMs: cell.durationMs,
4818
+ placement: {
4819
+ lane: work.lane,
4820
+ boxId: null
4821
+ },
4822
+ traceRef: { unknown: "runCampaign reports no trace id per cell" }
3341
4823
  };
3342
4824
  }
3343
4825
  };
3344
- function generationOperationId(generation) {
3345
- return `candidate-generation:gen-${generation}`;
4826
+ /** The default ledger and identities when the caller binds none. Revisions are
4827
+ * content digests of what the caller declared, and each uri names what its
4828
+ * digest covers: the dispatch ref, the proposer kind, the kernel. */
4829
+ async function defaultBinding(opts, runDir, storage) {
4830
+ const base = `optimization-${createHash("sha256").update(runDir).digest("hex").slice(0, 16)}`;
4831
+ const dispatchRef = opts.dispatchRef ?? "anonymous";
4832
+ const identity = {
4833
+ agent: {
4834
+ uri: `dispatch-ref:${dispatchRef}`,
4835
+ revision: hashCanonical({ dispatchRef })
4836
+ },
4837
+ proposer: {
4838
+ kind: "deterministic",
4839
+ source: {
4840
+ uri: `proposer:${opts.proposer.kind}`,
4841
+ revision: hashCanonical({ proposer: opts.proposer.kind })
4842
+ }
4843
+ },
4844
+ search: SEARCH_KERNEL_SOURCE,
4845
+ model: {
4846
+ provider: "unspecified",
4847
+ alias: "unspecified",
4848
+ unknown: "runOptimization was not told which model the agent runs"
4849
+ }
4850
+ };
4851
+ for (let slot = 1;; slot++) {
4852
+ const path = `${slot === 1 ? `${runDir}/search` : `${runDir}/search-${slot}`}/ledger.jsonl`;
4853
+ const searchId = slot === 1 ? base : `${base}-${slot}`;
4854
+ const ledger = storage.kind === "filesystem" ? openSearchLedger({
4855
+ path,
4856
+ searchId
4857
+ }) : openSearchLedger({
4858
+ path,
4859
+ searchId,
4860
+ store: storage
4861
+ });
4862
+ if (!(await ledger.state()).closed) return {
4863
+ ledger,
4864
+ identity
4865
+ };
4866
+ }
4867
+ }
4868
+ function artifactKindOf(surface) {
4869
+ return typeof surface === "object" && surface.kind === "code" ? "code" : "prompt";
4870
+ }
4871
+ function receiptKey(receipt) {
4872
+ return receipt.callId;
4873
+ }
4874
+ /** A proposal that made a paid call ran a model; one that made none ran code. */
4875
+ function proposalExecution(receipts, provider, source) {
4876
+ const model = receipts.find((receipt) => receipt.channel !== "judge")?.model;
4877
+ if (model === void 0) return {
4878
+ kind: "deterministic",
4879
+ source
4880
+ };
4881
+ return {
4882
+ kind: "model",
4883
+ model: modelIdentity(model, provider),
4884
+ source
4885
+ };
4886
+ }
4887
+ function modelIdentity(model, provider) {
4888
+ return modelHasSnapshot(model) ? {
4889
+ provider,
4890
+ snapshot: model
4891
+ } : {
4892
+ provider,
4893
+ alias: model,
4894
+ unknown: "the provider reported a moving alias, not a snapshot"
4895
+ };
4896
+ }
4897
+ function cellModel(cell, fallback) {
4898
+ return cell.resolvedModel === void 0 ? fallback : modelIdentity(cell.resolvedModel, fallback.provider);
3346
4899
  }
3347
4900
  function cellOutcome(cell) {
3348
- const scores = Object.entries(cell.judgeScores).filter(([, score]) => score.failed !== true && Number.isFinite(score.composite));
3349
- if (cell.error !== void 0 || scores.length === 0) return {
4901
+ const quality = projectCampaignCellQuality(cell);
4902
+ if (quality.score === void 0) return {
3350
4903
  status: "errored",
3351
4904
  metrics: {},
3352
4905
  error: {
@@ -3355,12 +4908,11 @@ function cellOutcome(cell) {
3355
4908
  retryable: false
3356
4909
  }
3357
4910
  };
3358
- const composite = scores.reduce((sum, [, score]) => sum + score.composite, 0) / scores.length;
3359
- const metrics = { composite };
3360
- for (const [judge, score] of scores) metrics[`judge.${judge}`] = score.composite;
4911
+ const metrics = { composite: quality.score };
4912
+ for (const [judge, score] of Object.entries(quality.successfulJudgeScores)) metrics[`judge.${judge}`] = score.composite;
3361
4913
  return {
3362
4914
  status: "passed",
3363
- score: composite,
4915
+ score: quality.score,
3364
4916
  metrics
3365
4917
  };
3366
4918
  }
@@ -3423,332 +4975,6 @@ function receiptAccounting(receipts) {
3423
4975
  }
3424
4976
  };
3425
4977
  }
3426
- //#endregion
3427
- //#region src/campaign/presets/run-optimization.ts
3428
- /**
3429
- * `runOptimization` runs a caller-owned candidate generator for a bounded
3430
- * number of rounds. Each candidate is measured on the same cases, and only a
3431
- * candidate that beats the current best becomes the incumbent. By default the
3432
- * incumbent is also the parent every generation mutates; a `selectParent`
3433
- * policy draws the parent from the Pareto frontier instead.
3434
- * The same loop accepts deterministic, model-backed, or agent-backed
3435
- * proposers; they differ only in how `propose()` picks candidates.
3436
- *
3437
- * `runImprovementLoop` adds a separate final comparison, a release decision,
3438
- * and optional pull request creation.
3439
- */
3440
- /**
3441
- * Improvement loop body: N generations of propose → campaign → rank, maintaining a Pareto frontier and one global incumbent across generations. The parent each generation mutates is the incumbent unless `selectParent` draws it from the frontier.
3442
- */
3443
- async function runOptimization(opts) {
3444
- const { proposer } = opts;
3445
- const candidateConcurrency = opts.candidateConcurrency ?? 1;
3446
- if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) throw new Error("runOptimization: runDir is required and must be a non-empty string");
3447
- if (!Number.isInteger(candidateConcurrency) || candidateConcurrency < 1) throw new Error("runOptimization: candidateConcurrency must be a positive integer");
3448
- const initialFindings = immutableProposalSnapshot(assertProposalFindings(opts.findings ?? [], "runOptimization initial proposal findings"), "initial findings");
3449
- const baselineSurface = immutableProposalSnapshot(opts.baselineSurface, "baseline surface");
3450
- opts.runDir = resolveRunDir(opts.runDir, opts.repo);
3451
- const storage = opts.storage ?? fsCampaignStorage();
3452
- const costLedger = opts.costLedger ?? createRunCostLedger({
3453
- storage,
3454
- runDir: opts.runDir,
3455
- costCeilingUsd: opts.costCeiling
3456
- });
3457
- const requireJudgeScore = (opts.judges?.length ?? 0) > 0;
3458
- const reps = opts.reps ?? 1;
3459
- const premeasuredBaseline = opts.premeasuredBaseline;
3460
- const baselineCampaign = premeasuredBaseline ? validatedPremeasuredBaseline({
3461
- input: premeasuredBaseline,
3462
- baselineSurface,
3463
- scenarios: opts.scenarios,
3464
- reps,
3465
- seed: opts.seed ?? 42,
3466
- judges: opts.judges ?? [],
3467
- dispatchRef: surfaceDispatchRef(baselineSurface, opts.dispatchRef)
3468
- }) : await runCampaign({
3469
- ...opts,
3470
- costLedger,
3471
- costPhase: "search.baseline",
3472
- dispatchRef: surfaceDispatchRef(baselineSurface, opts.dispatchRef),
3473
- dispatch: (scenario, ctx) => opts.dispatchWithSurface(baselineSurface, scenario, ctx),
3474
- runDir: `${opts.runDir}/baseline`
3475
- });
3476
- const baselineCoverage = campaignCoverage(baselineCampaign.cells, opts.scenarios, reps, requireJudgeScore);
3477
- if (!baselineCoverage.complete) {
3478
- const label = opts.premeasuredBaseline ? "premeasured baseline" : "baseline";
3479
- throw new Error(`runOptimization: ${label} is incomplete (${baselineCoverage.scorableCellIds.length}/${baselineCoverage.expectedCellIds.length} designed cells scorable) — ${formatCoverageFailures(baselineCoverage)}. Refusing to optimize against an incomplete incumbent.`);
3480
- }
3481
- const opened = opts.searchLedger ? await OptimizationSearch.open({
3482
- binding: opts.searchLedger,
3483
- storage,
3484
- costLedger,
3485
- scenarios: opts.scenarios,
3486
- reps,
3487
- seed: opts.seed ?? 42,
3488
- splitDigest: baselineCampaign.splitDigest,
3489
- proposerName: proposer.kind,
3490
- expansion: opts.selectParent ? "select-parent" : "incumbent",
3491
- maxUsd: opts.costCeiling ?? null,
3492
- baselineSurface,
3493
- baselineCells: baselineCampaign.cells
3494
- }) : void 0;
3495
- const search = opened?.search;
3496
- const nodeIdByHash = /* @__PURE__ */ new Map();
3497
- if (opened) nodeIdByHash.set(surfaceHash(baselineSurface), opened.rootNodeId);
3498
- const incompleteNodeIds = /* @__PURE__ */ new Set();
3499
- const promotedNodeIds = /* @__PURE__ */ new Set();
3500
- let stopReason = "max-nodes";
3501
- const generations = [];
3502
- const history = [];
3503
- let currentFindings = initialFindings;
3504
- const selectionRankKey = opts.selectionRankKey ?? ((campaign) => [campaignMeanComposite(campaign)]);
3505
- let winnerSurface = baselineSurface;
3506
- let winnerSurfaceHash = surfaceHash(baselineSurface);
3507
- const admittedCandidateHashes = /* @__PURE__ */ new Set([winnerSurfaceHash]);
3508
- let winnerComposite = campaignMeanComposite(baselineCampaign);
3509
- let winnerRankKey = selectionRankKey(baselineCampaign);
3510
- assertFiniteRankKey(winnerRankKey, "selectionRankKey for baseline");
3511
- const baselineOutcome = toScoredSurfaceOutcome(winnerSurfaceHash, baselineCampaign, baselineCoverage, -1);
3512
- let winnerOutcome = baselineOutcome;
3513
- let winnerLabel;
3514
- let winnerRationale;
3515
- const scored = [toParetoParent(baselineSurface, winnerSurfaceHash, baselineCampaign, -1)];
3516
- const measuredByHash = /* @__PURE__ */ new Map([[winnerSurfaceHash, {
3517
- parent: scored[0],
3518
- outcome: baselineOutcome
3519
- }]]);
3520
- if (opts.analyzeGeneration && opts.maxGenerations > 0 && baselineCampaign.cells.length > 0) {
3521
- const fresh = await opts.analyzeGeneration({
3522
- generation: -1,
3523
- runDir: baselineCampaign.runDir,
3524
- candidates: [{
3525
- surfaceHash: winnerSurfaceHash,
3526
- campaign: baselineCampaign,
3527
- composite: winnerComposite
3528
- }],
3529
- history,
3530
- costLedger,
3531
- costPhase: "analysis.baseline"
3532
- });
3533
- if (!Array.isArray(fresh)) throw new TypeError("runOptimization: analyzeGeneration must return an array");
3534
- currentFindings = immutableProposalSnapshot(assertProposalFindings(fresh, "runOptimization baseline analysis findings"), "baseline analysis findings");
3535
- }
3536
- for (let gen = 0; gen < opts.maxGenerations; gen++) {
3537
- const proposalHistory = immutableProposalSnapshot(history, "history");
3538
- if (proposer.decide?.({ history: proposalHistory }).stop) {
3539
- stopReason = "converged";
3540
- break;
3541
- }
3542
- const paretoParents = immutableProposalSnapshot(computeParetoFrontier(scored), "Pareto parents");
3543
- const incumbentOutcome = immutableProposalSnapshot(winnerOutcome, "incumbent outcome");
3544
- const selected = opts.selectParent ? resolveSelectedParent(opts.selectParent(Object.freeze({
3545
- frontier: paretoParents,
3546
- incumbent: incumbentOutcome,
3547
- history: proposalHistory,
3548
- generation: gen
3549
- })), measuredByHash, gen) : void 0;
3550
- const parentSurface = selected ? selected.parent.surface : winnerSurface;
3551
- const parentSurfaceHash = selected ? selected.parent.surfaceHash : winnerSurfaceHash;
3552
- const parentComposite = selected ? selected.outcome.composite : winnerComposite;
3553
- const parentOutcome = selected ? selected.outcome : winnerOutcome;
3554
- const proposalContext = Object.freeze({
3555
- currentSurface: immutableProposalSnapshot(parentSurface, "current surface"),
3556
- history: proposalHistory,
3557
- findings: immutableProposalSnapshot(assertProposalFindings(currentFindings, "runOptimization proposal findings"), "findings"),
3558
- populationSize: opts.populationSize,
3559
- generation: gen,
3560
- signal: opts.signal ?? new AbortController().signal,
3561
- baselineOutcome: immutableProposalSnapshot(baselineOutcome, "baseline outcome"),
3562
- incumbentOutcome,
3563
- parentOutcome: immutableProposalSnapshot(parentOutcome, "parent outcome"),
3564
- maxImprovementShots: opts.maxImprovementShots,
3565
- paretoParents,
3566
- costLedger,
3567
- costPhase: "search.proposal"
3568
- });
3569
- await search?.startGeneration(gen);
3570
- const proposed = await proposer.propose(proposalContext);
3571
- if (!Array.isArray(proposed)) throw new TypeError("runOptimization: proposer must return an array");
3572
- const proposalSnapshot = immutableProposalSnapshot(proposed, "candidate outputs");
3573
- if (proposalSnapshot.length === 0) {
3574
- await search?.failGeneration(gen, "the proposer returned no candidates");
3575
- stopReason = "converged";
3576
- break;
3577
- }
3578
- const candidates = proposalSnapshot.map((p) => isProposedCandidate(p) ? p : {
3579
- surface: p,
3580
- label: "",
3581
- rationale: ""
3582
- });
3583
- const generationHashes = /* @__PURE__ */ new Set();
3584
- for (const { surface } of candidates) {
3585
- const hash = surfaceHash(surface);
3586
- if (admittedCandidateHashes.has(hash) || generationHashes.has(hash)) throw new Error(`runOptimization: duplicate candidate surface hash "${hash}" in generation ${gen}; candidate surfaces must be unique`);
3587
- generationHashes.add(hash);
3588
- }
3589
- for (const hash of generationHashes) admittedCandidateHashes.add(hash);
3590
- if (search) {
3591
- const nodeIds = await search.recordProposal({
3592
- generation: gen,
3593
- parent: {
3594
- nodeId: nodeIdByHash.get(parentSurfaceHash),
3595
- surface: parentSurface,
3596
- composite: parentComposite
3597
- },
3598
- selectionRule: opts.selectParent ? "select-parent" : "incumbent",
3599
- candidates
3600
- });
3601
- for (const [index, nodeId] of nodeIds.entries()) {
3602
- nodeIdByHash.set(surfaceHash(candidates[index].surface), nodeId);
3603
- await search.allocate(nodeId);
3604
- }
3605
- }
3606
- const surfaceResults = await mapConcurrent(candidates, candidateConcurrency, async ({ surface, label, rationale, attribution }, i, signal) => {
3607
- const hash = surfaceHash(surface);
3608
- const campaign = await runCampaign({
3609
- ...opts,
3610
- signal,
3611
- costLedger,
3612
- costPhase: "search.candidate",
3613
- dispatchRef: surfaceDispatchRef(surface, opts.dispatchRef),
3614
- dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
3615
- runDir: `${opts.runDir}/gen-${gen}/candidate-${i}`
3616
- });
3617
- const coverage = campaignCoverage(campaign.cells, opts.scenarios, opts.reps ?? 1, requireJudgeScore);
3618
- const composite = campaignMeanCompositeOrNull(campaign);
3619
- const rankKey = coverage.complete ? selectionRankKey(campaign) : null;
3620
- if (rankKey) assertFiniteRankKey(rankKey, `selectionRankKey for generation ${gen} candidate ${i}`, winnerRankKey.length);
3621
- return {
3622
- surfaceHash: hash,
3623
- surface,
3624
- label,
3625
- rationale,
3626
- ...attribution ? { attribution } : {},
3627
- campaign,
3628
- composite,
3629
- rankKey,
3630
- coverage
3631
- };
3632
- }, opts.signal);
3633
- for (const result of surfaceResults) {
3634
- const { surface, surfaceHash: hash, campaign, coverage, label, rationale } = result;
3635
- if (coverage.complete) {
3636
- const parent = toParetoParent(surface, hash, campaign, gen, label || void 0, rationale || void 0);
3637
- scored.push(parent);
3638
- measuredByHash.set(hash, {
3639
- parent,
3640
- outcome: toScoredSurfaceOutcome(hash, campaign, coverage, gen)
3641
- });
3642
- }
3643
- }
3644
- if (search) for (const result of surfaceResults) {
3645
- const nodeId = nodeIdByHash.get(result.surfaceHash);
3646
- await search.recordCells(nodeId, "train", result.campaign.cells);
3647
- if (!result.coverage.complete) incompleteNodeIds.add(nodeId);
3648
- }
3649
- surfaceResults.sort((a, b) => {
3650
- if (a.coverage.complete !== b.coverage.complete) return a.coverage.complete ? -1 : 1;
3651
- if (a.rankKey && b.rankKey) return compareRankKeys(b.rankKey, a.rankKey);
3652
- return a.surfaceHash.localeCompare(b.surfaceHash);
3653
- });
3654
- const top = surfaceResults.filter((result) => result.coverage.complete && result.composite !== null && result.rankKey !== null)[0];
3655
- const promoted = top && compareRankKeys(top.rankKey, winnerRankKey) > 0 ? [top] : [];
3656
- if (promoted[0]) {
3657
- const top = promoted[0];
3658
- const promotedNodeId = nodeIdByHash.get(top.surfaceHash);
3659
- if (promotedNodeId) promotedNodeIds.add(promotedNodeId);
3660
- winnerSurface = top.surface;
3661
- winnerSurfaceHash = top.surfaceHash;
3662
- winnerComposite = top.composite;
3663
- winnerRankKey = top.rankKey;
3664
- winnerOutcome = toScoredSurfaceOutcome(top.surfaceHash, top.campaign, top.coverage, gen);
3665
- winnerLabel = top.label || void 0;
3666
- winnerRationale = top.rationale || void 0;
3667
- }
3668
- const record = {
3669
- generationIndex: gen,
3670
- candidates: surfaceResults.map((s) => {
3671
- const breakdown = campaignBreakdown(s.campaign);
3672
- const candidate = {
3673
- surfaceHash: s.surfaceHash,
3674
- composite: s.composite,
3675
- ci95: null,
3676
- parentSurfaceHash,
3677
- parentComposite,
3678
- ...s.coverage.complete ? { observedDeltaFromParent: s.composite - parentComposite } : {},
3679
- eligibleForPromotion: s.coverage.complete,
3680
- coverage: {
3681
- expectedCells: s.coverage.expectedCellIds.length,
3682
- scorableCells: s.coverage.scorableCellIds.length,
3683
- unscorableCells: s.coverage.unscorableCells
3684
- },
3685
- dimensions: breakdown.dimensions,
3686
- scenarios: breakdown.scenarios
3687
- };
3688
- if (s.label) candidate.label = s.label;
3689
- if (s.rationale) candidate.rationale = s.rationale;
3690
- if (s.attribution) candidate.attribution = s.attribution;
3691
- return candidate;
3692
- }),
3693
- promoted: promoted.map((p) => p.surfaceHash)
3694
- };
3695
- history.push(record);
3696
- generations.push({
3697
- record,
3698
- surfaces: surfaceResults.map((s) => ({
3699
- surfaceHash: s.surfaceHash,
3700
- surface: s.surface,
3701
- campaign: s.campaign
3702
- }))
3703
- });
3704
- if (opts.analyzeGeneration && gen < opts.maxGenerations - 1) {
3705
- const fresh = await opts.analyzeGeneration({
3706
- generation: gen,
3707
- runDir: `${opts.runDir}/gen-${gen}`,
3708
- candidates: surfaceResults.map((s) => ({
3709
- surfaceHash: s.surfaceHash,
3710
- campaign: s.campaign,
3711
- composite: s.composite
3712
- })),
3713
- history,
3714
- costLedger,
3715
- costPhase: "analysis.generation"
3716
- });
3717
- if (!Array.isArray(fresh)) throw new TypeError("runOptimization: analyzeGeneration must return an array");
3718
- currentFindings = immutableProposalSnapshot(assertProposalFindings(fresh, "runOptimization generation analysis findings"), "generation analysis findings");
3719
- }
3720
- }
3721
- const searchHistory = await search?.finish({
3722
- winnerNodeId: nodeIdByHash.get(winnerSurfaceHash),
3723
- promotedNodeIds,
3724
- incompleteNodeIds,
3725
- reason: stopReason,
3726
- runId: opts.runDir
3727
- });
3728
- return {
3729
- generations,
3730
- baselineSurface,
3731
- winnerSurface,
3732
- winnerSurfaceHash,
3733
- winnerLabel,
3734
- winnerRationale,
3735
- baselineCampaign,
3736
- ...searchHistory ? { searchHistory } : {},
3737
- paretoFrontier: computeParetoFrontier(scored),
3738
- cost: costLedger.summary()
3739
- };
3740
- }
3741
- /** Resolve the parent a `selectParent` policy returned to the measured record
3742
- * this run holds for it. Refuses a surface the run never measured to
3743
- * completion, and a parent whose surface does not hash to its `surfaceHash`. */
3744
- function resolveSelectedParent(returned, measuredByHash, generation) {
3745
- if (typeof returned !== "object" || returned === null || typeof returned.surfaceHash !== "string") throw new TypeError(`runOptimization: selectParent must return a ParetoParent (generation ${generation})`);
3746
- const parent = returned;
3747
- const measured = measuredByHash.get(parent.surfaceHash);
3748
- if (!measured) throw new Error(`runOptimization: selectParent returned surface "${parent.surfaceHash}" in generation ${generation}, which this run has not measured to completion; a parent must be a scored surface from the frontier`);
3749
- if (!surfaceHashMatches(parent.surface, parent.surfaceHash)) throw new Error(`runOptimization: selectParent returned a parent whose surface does not match its surfaceHash "${parent.surfaceHash}" (generation ${generation})`);
3750
- return measured;
3751
- }
3752
4978
  function immutableProposalSnapshot(value, label) {
3753
4979
  try {
3754
4980
  return deepFreeze(structuredClone(value));
@@ -3782,58 +5008,42 @@ function validatedPremeasuredBaseline(args) {
3782
5008
  reps: args.reps
3783
5009
  });
3784
5010
  if (campaign.manifestHash !== expectedManifest) throw new Error("runOptimization: premeasured baseline evaluator identity does not match the requested dispatch and judges");
5011
+ const coverage = campaignCoverage(campaign.cells, args.scenarios, args.reps, args.requireJudgeScore);
5012
+ if (!coverage.complete) throw new Error(`runOptimization: premeasured baseline is incomplete (${coverage.scorableCellIds.length}/${coverage.expectedCellIds.length} designed cells scorable) — ${formatCoverageFailures(coverage)}. Refusing to optimize against an incomplete incumbent.`);
3785
5013
  return campaign;
3786
5014
  }
3787
- /** Build a `ParetoParent` from a scored campaign — objective vector =
3788
- * per-scenario composite, scalar = mean composite. */
3789
- function toParetoParent(surface, hash, campaign, generation, label, rationale) {
5015
+ /** A scored campaign as a frontier member: per-scenario composite objectives. */
5016
+ function toParetoParent(surface, campaign, generation, label) {
3790
5017
  const objectives = {};
3791
5018
  for (const { scenarioId, composite } of campaignBreakdown(campaign).scenarios) objectives[scenarioId] = composite;
3792
5019
  const parent = {
3793
5020
  surface,
3794
- surfaceHash: hash,
5021
+ surfaceHash: surfaceHash(surface),
3795
5022
  objectives,
3796
5023
  composite: campaignMeanComposite(campaign),
3797
5024
  generation
3798
5025
  };
3799
5026
  if (label) parent.label = label;
3800
- if (rationale) parent.rationale = rationale;
3801
5027
  return parent;
3802
5028
  }
3803
- /** The non-dominated set over the per-scenario objective vectors. Every
3804
- * scenario seen across the scored set becomes a `maximize` objective.
3805
- * `runOptimization` admits only complete campaigns to this set; the finite
3806
- * floor remains a defensive fallback for manually constructed/no-judge
3807
- * vectors. Delegates dominance to the package-canonical `paretoFrontier`. */
5029
+ /** The non-dominated set over the per-scenario objective vectors. */
3808
5030
  function computeParetoFrontier(scored) {
3809
5031
  if (scored.length <= 1) return [...scored];
3810
5032
  const ids = /* @__PURE__ */ new Set();
3811
5033
  for (const p of scored) for (const id of Object.keys(p.objectives)) ids.add(id);
3812
5034
  if (ids.size === 0) return [...scored];
3813
- const floor = {};
3814
- for (const id of ids) {
3815
- let min = Number.POSITIVE_INFINITY;
3816
- for (const p of scored) {
3817
- const v = p.objectives[id];
3818
- if (typeof v === "number" && Number.isFinite(v) && v < min) min = v;
3819
- }
3820
- floor[id] = Number.isFinite(min) ? min : 0;
3821
- }
3822
5035
  return paretoFrontier(scored, [...ids].map((id) => ({
3823
5036
  name: id,
3824
5037
  direction: "maximize",
3825
- value: (p) => {
3826
- const v = p.objectives[id];
3827
- return typeof v === "number" && Number.isFinite(v) ? v : floor[id] ?? 0;
3828
- }
5038
+ value: (p) => p.objectives[id] ?? Number.NEGATIVE_INFINITY
3829
5039
  }))).frontier;
3830
5040
  }
3831
- function toScoredSurfaceOutcome(surfaceHash, campaign, coverage, generation) {
5041
+ function toScoredSurfaceOutcome(hash, campaign, coverage, generation) {
3832
5042
  const breakdown = campaignBreakdown(campaign);
3833
5043
  return {
3834
5044
  split: "search",
3835
5045
  generation,
3836
- surfaceHash,
5046
+ surfaceHash: hash,
3837
5047
  composite: campaignMeanComposite(campaign),
3838
5048
  dimensions: breakdown.dimensions,
3839
5049
  scenarios: breakdown.scenarios,
@@ -5253,6 +6463,6 @@ function firstString(value) {
5253
6463
  return typeof value === "string" && value.trim() ? value : void 0;
5254
6464
  }
5255
6465
  //#endregion
5256
- export { readCachedCell as $, isProposedCandidate as A, redTeamDataset as B, assertCompleteSearchHistory as C, searchHistoryCoverageRow as D, createSearchHistoryReceipt as E, runFinalComparison as F, captureFinalEvidencePolicy as G, scoreRedTeamOutput as H, openAutoPr as I, runEval as J, evaluationUnitMap as K, captureJudge as L, dominates as M, paretoFrontier as N, verifySearchHistoryArtifact as O, paretoFrontierWithCrowding as P, tangleTracesRoot as Q, defaultProductionGate as R, SearchHistoryRequiredError as S, assertSearchHistoryMatchesState as T, runCanaries as U, redTeamReport as V, assertIndependentEvaluationSplit as W, planCampaignRun as X, runCampaign as Y, resolveRunDir as Z, SearchRecorder as _, transientDispatchFailure as a, surfaceDiff as b, compareOptimizationMethods as c, combineComparisonCosts as d, buildCellSchedule as et, costFromLedgerSummary as f, runOptimization as g, runImprovementLoop as h, quotaExhaustedUntil as i, labelTrustRank as j, verifySearchHistoryReceipt as k, optimizationTokenUsageFromSummary as l, readGepaCandidatePopulationArtifact as m, JudgeParseError as n, computeManifestHash as nt, aggregateRunScore as o, assertGepaCandidatePopulationSummary as p, reserveFinalEvidence as q, isTransientTransportFailure as r, clamp01 as s, llmJudge as t, cellCachePath as tt, executeOptimizationMethod as u, developmentClaim as v, assertSearchHistoryAdmissionOptions as w, surfaceNode as x, searchEdgeId as y, DEFAULT_RED_TEAM_CORPUS as z };
6466
+ export { scoreRedTeamOutput as $, createSearchHistoryReceipt as A, estimateNodeFromCells as B, searchEdgeId as C, assertCompleteSearchHistory as D, SearchHistoryRequiredError as E, runSearch as F, paretoFrontier as G, searchPosterior as H, searchExpansionIndex as I, captureJudge as J, runFinalComparison as K, searchPolicyView as L, verifySearchHistoryArtifact as M, verifySearchHistoryReceipt as N, assertSearchHistoryAdmissionOptions as O, SEARCH_KERNEL_SOURCE as P, redTeamReport as Q, SEARCH_ESTIMATOR as R, developmentClaim as S, surfaceNode as T, uniform as U, searchCellSetDigest as V, dominates as W, DEFAULT_RED_TEAM_CORPUS as X, defaultProductionGate as Y, redTeamDataset as Z, isProposedCandidate as _, transientDispatchFailure as a, runEval as at, incumbent as b, compareOptimizationMethods as c, resolveRunDir as ct, combineComparisonCosts as d, buildCellSchedule as dt, runCanaries as et, costFromLedgerSummary as f, cellCachePath as ft, runOptimization as g, runImprovementLoop as h, quotaExhaustedUntil as i, reserveFinalEvidence as it, searchHistoryCoverageRow as j, assertSearchHistoryMatchesState as k, optimizationTokenUsageFromSummary as l, tangleTracesRoot as lt, readGepaCandidatePopulationArtifact as m, JudgeParseError as n, captureFinalEvidencePolicy as nt, aggregateRunScore as o, runCampaign as ot, assertGepaCandidatePopulationSummary as p, computeManifestHash as pt, openAutoPr as q, isTransientTransportFailure as r, evaluationUnitMap as rt, clamp01 as s, planCampaignRun as st, llmJudge as t, assertIndependentEvaluationSplit as tt, executeOptimizationMethod as u, readCachedCell as ut, labelTrustRank as v, surfaceDiff as w, SearchRecorder as x, crowdedFrontierParent as y, estimateNode as z };
5257
6467
 
5258
- //# sourceMappingURL=llm-judge-NJOHM0iA.js.map
6468
+ //# sourceMappingURL=llm-judge-Z6MkTkd0.js.map