@tangle-network/agent-eval 0.123.1 → 0.123.3

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 (94) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +191 -157
  3. package/dist/analyst/index.d.ts +27 -4
  4. package/dist/analyst/index.js +6 -6
  5. package/dist/authenticity/index.js +3 -2
  6. package/dist/authenticity/index.js.map +1 -1
  7. package/dist/benchmarks/index.d.ts +12 -4
  8. package/dist/benchmarks/index.js +9 -9
  9. package/dist/campaign/index.d.ts +270 -254
  10. package/dist/campaign/index.js +22 -18
  11. package/dist/{chunk-GS3FJGUF.js → chunk-22VO7T2I.js} +805 -366
  12. package/dist/chunk-22VO7T2I.js.map +1 -0
  13. package/dist/{chunk-LBAHQOBI.js → chunk-4KO6D2BO.js} +38 -12
  14. package/dist/chunk-4KO6D2BO.js.map +1 -0
  15. package/dist/{chunk-3FCG7FBV.js → chunk-D5JZ7UDZ.js} +2 -2
  16. package/dist/{chunk-XJYR7XFV.js → chunk-GC4ATIKK.js} +1 -1
  17. package/dist/chunk-GC4ATIKK.js.map +1 -0
  18. package/dist/{chunk-BGVTIE2C.js → chunk-GRCDRKII.js} +13 -2
  19. package/dist/chunk-GRCDRKII.js.map +1 -0
  20. package/dist/{chunk-FHFTYX2Q.js → chunk-JKDNAOF5.js} +2 -2
  21. package/dist/{chunk-S3UZOQ5Y.js → chunk-LOW3U7JZ.js} +2 -2
  22. package/dist/{chunk-FC5NDO3E.js → chunk-MHPEGJHC.js} +4 -4
  23. package/dist/{chunk-NJC7U437.js → chunk-MK3Q6GCL.js} +33 -18
  24. package/dist/chunk-MK3Q6GCL.js.map +1 -0
  25. package/dist/{chunk-U5CHZ5M3.js → chunk-N7ZS6PEF.js} +4 -4
  26. package/dist/{chunk-A5S77LSE.js → chunk-QFQZ3U3X.js} +2 -2
  27. package/dist/chunk-QFQZ3U3X.js.map +1 -0
  28. package/dist/{chunk-G2GPNLSX.js → chunk-QNVBALEX.js} +4 -4
  29. package/dist/{chunk-OYZAPX5G.js → chunk-R226UZOI.js} +2 -2
  30. package/dist/{chunk-6WX7CBAR.js → chunk-SJT4OBVL.js} +20 -9
  31. package/dist/chunk-SJT4OBVL.js.map +1 -0
  32. package/dist/{chunk-HZJF4IUO.js → chunk-SUN7QLPB.js} +6 -6
  33. package/dist/{chunk-HZJF4IUO.js.map → chunk-SUN7QLPB.js.map} +1 -1
  34. package/dist/{chunk-VJ7T5WIO.js → chunk-WP3GEXKI.js} +3 -3
  35. package/dist/cli.js +101 -11
  36. package/dist/cli.js.map +1 -1
  37. package/dist/contract/index.d.ts +115 -8
  38. package/dist/contract/index.js +11 -9
  39. package/dist/contract/index.js.map +1 -1
  40. package/dist/control.js +3 -3
  41. package/dist/fuzz.d.ts +8 -1
  42. package/dist/fuzz.js +7 -6
  43. package/dist/fuzz.js.map +1 -1
  44. package/dist/hosted/index.d.ts +8 -2
  45. package/dist/index.d.ts +36 -10
  46. package/dist/index.js +18 -16
  47. package/dist/index.js.map +1 -1
  48. package/dist/multishot/index.d.ts +2 -2
  49. package/dist/openapi.json +1 -1
  50. package/dist/rl.d.ts +24 -3
  51. package/dist/rl.js +6 -4
  52. package/dist/rl.js.map +1 -1
  53. package/dist/{run-campaign-ZKR5MQMQ.js → run-campaign-I3JXKVAK.js} +3 -3
  54. package/dist/storyboard/index.js +1 -1
  55. package/dist/storyboard/index.js.map +1 -1
  56. package/dist/traces.d.ts +2 -2
  57. package/dist/traces.js +3 -3
  58. package/dist/wire/index.d.ts +77 -5
  59. package/dist/wire/index.js +3 -3
  60. package/docs/adapters-observability.md +6 -6
  61. package/docs/building-doctrine.md +5 -5
  62. package/docs/campaign-proposers.md +142 -131
  63. package/docs/concepts.md +49 -53
  64. package/docs/customer-journeys.md +80 -155
  65. package/docs/design/loop-taxonomy.md +27 -29
  66. package/docs/design.md +69 -0
  67. package/docs/distributed-driver.md +14 -14
  68. package/docs/eval-surface-map.md +11 -11
  69. package/docs/hosted-ingest-spec.md +4 -4
  70. package/docs/insight-report.md +32 -27
  71. package/docs/multi-shot-optimization.md +41 -49
  72. package/docs/research-report-methodology.md +9 -9
  73. package/docs/trace-analysis.md +2 -2
  74. package/docs/wire-protocol.md +16 -16
  75. package/package.json +2 -1
  76. package/dist/chunk-6WX7CBAR.js.map +0 -1
  77. package/dist/chunk-A5S77LSE.js.map +0 -1
  78. package/dist/chunk-BGVTIE2C.js.map +0 -1
  79. package/dist/chunk-GS3FJGUF.js.map +0 -1
  80. package/dist/chunk-LBAHQOBI.js.map +0 -1
  81. package/dist/chunk-NJC7U437.js.map +0 -1
  82. package/dist/chunk-XJYR7XFV.js.map +0 -1
  83. package/docs/auto-research-loop-end-to-end.md +0 -186
  84. package/docs/improvement-glossary.md +0 -204
  85. package/docs/self-improvement-map.md +0 -115
  86. /package/dist/{chunk-3FCG7FBV.js.map → chunk-D5JZ7UDZ.js.map} +0 -0
  87. /package/dist/{chunk-FHFTYX2Q.js.map → chunk-JKDNAOF5.js.map} +0 -0
  88. /package/dist/{chunk-S3UZOQ5Y.js.map → chunk-LOW3U7JZ.js.map} +0 -0
  89. /package/dist/{chunk-FC5NDO3E.js.map → chunk-MHPEGJHC.js.map} +0 -0
  90. /package/dist/{chunk-U5CHZ5M3.js.map → chunk-N7ZS6PEF.js.map} +0 -0
  91. /package/dist/{chunk-G2GPNLSX.js.map → chunk-QNVBALEX.js.map} +0 -0
  92. /package/dist/{chunk-OYZAPX5G.js.map → chunk-R226UZOI.js.map} +0 -0
  93. /package/dist/{chunk-VJ7T5WIO.js.map → chunk-WP3GEXKI.js.map} +0 -0
  94. /package/dist/{run-campaign-ZKR5MQMQ.js.map → run-campaign-I3JXKVAK.js.map} +0 -0
@@ -15,12 +15,13 @@ import {
15
15
  runImprovementLoop,
16
16
  surfaceContentHash,
17
17
  surfaceHash
18
- } from "./chunk-HZJF4IUO.js";
18
+ } from "./chunk-SUN7QLPB.js";
19
19
  import {
20
20
  SearchLedgerConflictError,
21
21
  SearchLedgerError,
22
22
  SearchLedgerIntegrityError,
23
23
  appendSearchLedgerLine,
24
+ assertCampaignDesign,
24
25
  assertRealBackend,
25
26
  canonicalJson,
26
27
  contentHash,
@@ -33,13 +34,12 @@ import {
33
34
  summarizeBackendIntegrity,
34
35
  tryAcquireAtomicFileLock,
35
36
  withSearchLedgerFileLock
36
- } from "./chunk-3FCG7FBV.js";
37
+ } from "./chunk-D5JZ7UDZ.js";
37
38
  import {
38
39
  AnalystRegistry,
39
40
  DEFAULT_TRACE_ANALYST_KINDS,
40
41
  Mutex,
41
42
  POLICY_EDIT_AXES,
42
- POLICY_EDIT_TARGET_SURFACES,
43
43
  admitPolicyEdit,
44
44
  applyPolicyEditToSurface,
45
45
  assertNoJudgeVerdict,
@@ -51,14 +51,14 @@ import {
51
51
  mapConcurrent,
52
52
  policyEditsFromFindings,
53
53
  validatePolicyEditCandidateRecord
54
- } from "./chunk-FC5NDO3E.js";
54
+ } from "./chunk-MHPEGJHC.js";
55
55
  import {
56
56
  callLlm,
57
57
  callLlmJson,
58
58
  costReceiptFromLlm,
59
59
  costReceiptFromLlmError,
60
60
  maximumChargeForLlmRequest
61
- } from "./chunk-NJC7U437.js";
61
+ } from "./chunk-MK3Q6GCL.js";
62
62
  import {
63
63
  eProcess,
64
64
  mcnemar,
@@ -70,7 +70,7 @@ import {
70
70
  import {
71
71
  CostAccountingIncompleteError,
72
72
  CostLedger
73
- } from "./chunk-BGVTIE2C.js";
73
+ } from "./chunk-GRCDRKII.js";
74
74
  import {
75
75
  analyzeTraces
76
76
  } from "./chunk-NUKSVU3W.js";
@@ -80,10 +80,10 @@ import {
80
80
  import {
81
81
  modelHasSnapshot,
82
82
  validateRunRecord
83
- } from "./chunk-S3UZOQ5Y.js";
83
+ } from "./chunk-LOW3U7JZ.js";
84
84
  import {
85
85
  buildAgentProfileCell
86
- } from "./chunk-XJYR7XFV.js";
86
+ } from "./chunk-GC4ATIKK.js";
87
87
  import {
88
88
  canonicalize
89
89
  } from "./chunk-VSMTAMNK.js";
@@ -3404,56 +3404,509 @@ function snippet(s, max = 120) {
3404
3404
  return t.length <= max ? t : `${t.slice(0, max)}\u2026`;
3405
3405
  }
3406
3406
 
3407
+ // src/campaign/presets/compare-optimization-methods.ts
3408
+ import { randomUUID } from "crypto";
3409
+ async function compareOptimizationMethods(opts) {
3410
+ assertOptimizationMethods(opts.methods);
3411
+ assertComparisonPartitions(opts);
3412
+ const seed = opts.seed ?? 42;
3413
+ const confidence = opts.confidence ?? 0.95;
3414
+ assertConfidence(confidence);
3415
+ const optimizationConcurrency = opts.optimizationConcurrency ?? 1;
3416
+ const comparisonCount = opts.methods.length * (opts.methods.length + 1) / 2;
3417
+ const intervalConfidence = 1 - (1 - confidence) / comparisonCount;
3418
+ const minimumResamples = minimumBootstrapResamples(confidence, comparisonCount);
3419
+ const resamples = opts.resamples ?? Math.max(2e3, minimumResamples);
3420
+ assertComparisonControls(opts, seed, resamples, confidence);
3421
+ const storage = opts.storage ?? fsCampaignStorage();
3422
+ const resolvedRunDir = resolveRunDir(opts.runDir, opts.repo);
3423
+ const testCostPhase = `compareOptimizationMethods:test:${randomUUID()}`;
3424
+ const testCostLedger = opts.costLedger ?? createRunCostLedger({
3425
+ storage,
3426
+ runDir: `${resolvedRunDir}/test/cost`,
3427
+ costCeilingUsd: opts.costCeiling
3428
+ });
3429
+ const scoreOnTest = async (surface, tag) => {
3430
+ const campaign = await runCampaign({
3431
+ ...opts,
3432
+ storage,
3433
+ costLedger: testCostLedger,
3434
+ costPhase: testCostPhase,
3435
+ scenarios: opts.testScenarios.map((scenario) => structuredClone(scenario)),
3436
+ dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
3437
+ runDir: `${resolvedRunDir}/${tag}`
3438
+ });
3439
+ const byScenario = {};
3440
+ for (const { scenarioId, composite } of campaignBreakdown(campaign).scenarios) {
3441
+ byScenario[scenarioId] = composite;
3442
+ }
3443
+ return byScenario;
3444
+ };
3445
+ const scenarioIds = opts.testScenarios.map((s) => s.id).sort();
3446
+ const align = (byScenario, label) => {
3447
+ const missing = scenarioIds.filter((id) => !(id in byScenario));
3448
+ if (missing.length > 0) {
3449
+ throw new Error(
3450
+ `compareOptimizationMethods: ${label} produced no test score for scenario(s) [${missing.join(
3451
+ ", "
3452
+ )}]. A cell failed or its judges returned nothing. Fix the dispatch or judge; the comparison will not replace missing scores with zero.`
3453
+ );
3454
+ }
3455
+ return scenarioIds.map((id) => byScenario[id]);
3456
+ };
3457
+ const optimized = await mapConcurrent(opts.methods, optimizationConcurrency, async (method) => {
3458
+ const out = await method.optimize(
3459
+ createOptimizationMethodInput(opts, method.name, resolvedRunDir, seed)
3460
+ );
3461
+ assertOptimizationResult(method.name, out);
3462
+ const winnerSurface = structuredClone(out.winnerSurface);
3463
+ return {
3464
+ name: method.name,
3465
+ winnerSurface,
3466
+ cost: out.cost,
3467
+ durationMs: out.durationMs
3468
+ };
3469
+ });
3470
+ const baselineArr = align(await scoreOnTest(opts.baselineSurface, "test/baseline"), "baseline");
3471
+ const testScoresBySurface = /* @__PURE__ */ new Map([[surfaceContentHash(opts.baselineSurface), baselineArr]]);
3472
+ const winners = [];
3473
+ for (const winner of optimized) {
3474
+ const surfaceKey = surfaceContentHash(winner.winnerSurface);
3475
+ let arr = testScoresBySurface.get(surfaceKey);
3476
+ if (!arr) {
3477
+ const byScenario = await scoreOnTest(
3478
+ winner.winnerSurface,
3479
+ `test/methods/${slug(winner.name)}`
3480
+ );
3481
+ arr = align(byScenario, `method "${winner.name}"`);
3482
+ testScoresBySurface.set(surfaceKey, arr);
3483
+ }
3484
+ winners.push({
3485
+ ...winner,
3486
+ arr
3487
+ });
3488
+ }
3489
+ const scores = winners.map((w) => {
3490
+ const boot = pairedBootstrap(baselineArr, w.arr, {
3491
+ seed,
3492
+ resamples,
3493
+ confidence: intervalConfidence,
3494
+ statistic: "mean"
3495
+ });
3496
+ const score = {
3497
+ name: w.name,
3498
+ baselineComposite: mean2(baselineArr),
3499
+ winnerComposite: mean2(w.arr),
3500
+ lift: boot.mean,
3501
+ liftCi: { low: boot.low, high: boot.high },
3502
+ optimizationCost: w.cost,
3503
+ scenarioScores: scenarioIds.map((scenarioId, index) => ({
3504
+ scenarioId,
3505
+ baselineComposite: baselineArr[index],
3506
+ winnerComposite: w.arr[index],
3507
+ lift: w.arr[index] - baselineArr[index]
3508
+ })),
3509
+ winnerSurface: w.winnerSurface,
3510
+ rank: 0
3511
+ };
3512
+ if (w.durationMs !== void 0) score.durationMs = w.durationMs;
3513
+ return score;
3514
+ });
3515
+ scores.sort((a, b) => b.lift - a.lift);
3516
+ for (let start = 0; start < scores.length; ) {
3517
+ let end = start + 1;
3518
+ while (end < scores.length && scores[end].lift === scores[start].lift) end += 1;
3519
+ const tied = scores.slice(start, end);
3520
+ if (tied.every((score) => score.optimizationCost.accountingComplete)) {
3521
+ tied.sort((a, b) => a.optimizationCost.totalCostUsd - b.optimizationCost.totalCostUsd);
3522
+ scores.splice(start, tied.length, ...tied);
3523
+ }
3524
+ start = end;
3525
+ }
3526
+ scores.forEach((s, i) => {
3527
+ s.rank = i + 1;
3528
+ });
3529
+ const best = scores[0];
3530
+ const byName = new Map(winners.map((w) => [w.name, w]));
3531
+ const bestArr = byName.get(best.name).arr;
3532
+ const pairwise = scores.slice(1).map((other) => {
3533
+ const otherArr = byName.get(other.name).arr;
3534
+ const boot = pairedBootstrap(otherArr, bestArr, {
3535
+ seed,
3536
+ resamples,
3537
+ confidence: intervalConfidence,
3538
+ statistic: "mean"
3539
+ });
3540
+ const favored = boot.low > 0 ? best.name : boot.high < 0 ? other.name : "tie";
3541
+ return {
3542
+ a: best.name,
3543
+ b: other.name,
3544
+ deltaMean: boot.mean,
3545
+ low: boot.low,
3546
+ high: boot.high,
3547
+ favored
3548
+ };
3549
+ });
3550
+ const optimizationCost = combineCosts(
3551
+ scores.map((score) => ({ label: `method '${score.name}'`, cost: score.optimizationCost }))
3552
+ );
3553
+ const testCost = costFromLedgerSummary(testCostLedger.summary({ phase: testCostPhase }));
3554
+ const totalCost = combineCosts([
3555
+ { label: "optimization", cost: optimizationCost },
3556
+ { label: "final test", cost: testCost }
3557
+ ]);
3558
+ return {
3559
+ scores,
3560
+ best,
3561
+ pairwise,
3562
+ testScenarioIds: scenarioIds,
3563
+ optimizationCost,
3564
+ testCost,
3565
+ totalCost,
3566
+ confidence,
3567
+ intervalConfidence,
3568
+ comparisonCount,
3569
+ seed,
3570
+ resamples,
3571
+ reps: opts.reps ?? 1
3572
+ };
3573
+ }
3574
+ function assertOptimizationMethods(methods) {
3575
+ if (!Array.isArray(methods) || methods.length === 0) {
3576
+ throw new Error("compareOptimizationMethods: no methods to compare");
3577
+ }
3578
+ const names = /* @__PURE__ */ new Set();
3579
+ const pathOwners = /* @__PURE__ */ new Map();
3580
+ for (const method of methods) {
3581
+ if (!method || typeof method !== "object" || typeof method.optimize !== "function") {
3582
+ throw new Error("compareOptimizationMethods: every method must provide optimize(input)");
3583
+ }
3584
+ if (!method.name || method.name.trim() !== method.name) {
3585
+ throw new Error("compareOptimizationMethods: method names must be trimmed and non-empty");
3586
+ }
3587
+ if (names.has(method.name)) {
3588
+ throw new Error(`compareOptimizationMethods: duplicate method name '${method.name}'`);
3589
+ }
3590
+ names.add(method.name);
3591
+ const pathKey = slug(method.name);
3592
+ const prior = pathOwners.get(pathKey);
3593
+ if (prior) {
3594
+ throw new Error(
3595
+ `compareOptimizationMethods: method names '${prior}' and '${method.name}' map to the same run path '${pathKey}'`
3596
+ );
3597
+ }
3598
+ pathOwners.set(pathKey, method.name);
3599
+ }
3600
+ }
3601
+ function assertOptimizationResult(name, result) {
3602
+ if (!result || typeof result !== "object") {
3603
+ throw new Error(`compareOptimizationMethods: method '${name}' returned no result`);
3604
+ }
3605
+ try {
3606
+ surfaceContentHash(result.winnerSurface);
3607
+ } catch (cause) {
3608
+ throw new Error(
3609
+ `compareOptimizationMethods: method '${name}' returned an invalid winnerSurface`,
3610
+ { cause }
3611
+ );
3612
+ }
3613
+ assertComparisonCost(result.cost, `method '${name}'`);
3614
+ if (result.durationMs !== void 0 && (!Number.isFinite(result.durationMs) || result.durationMs < 0)) {
3615
+ throw new Error(`compareOptimizationMethods: method '${name}' returned an invalid durationMs`);
3616
+ }
3617
+ }
3618
+ function assertComparisonControls(opts, seed, resamples, confidence) {
3619
+ if (!opts.judges || opts.judges.length === 0) {
3620
+ throw new Error("compareOptimizationMethods: at least one judge is required");
3621
+ }
3622
+ if (typeof opts.dispatchWithSurface !== "function") {
3623
+ throw new Error("compareOptimizationMethods: dispatchWithSurface must be a function");
3624
+ }
3625
+ try {
3626
+ surfaceContentHash(opts.baselineSurface);
3627
+ } catch (cause) {
3628
+ throw new Error("compareOptimizationMethods: baselineSurface is invalid", { cause });
3629
+ }
3630
+ const judgeNames = /* @__PURE__ */ new Set();
3631
+ for (const judge of opts.judges) {
3632
+ if (!judge || typeof judge !== "object" || typeof judge.name !== "string" || judge.name.trim().length === 0 || judge.name.trim() !== judge.name || typeof judge.score !== "function" || !Array.isArray(judge.dimensions) || judge.dimensions.length === 0) {
3633
+ throw new Error(
3634
+ "compareOptimizationMethods: every judge needs a trimmed name, at least one dimension, and score(input)"
3635
+ );
3636
+ }
3637
+ if (judgeNames.has(judge.name)) {
3638
+ throw new Error(`compareOptimizationMethods: duplicate judge name '${judge.name}'`);
3639
+ }
3640
+ judgeNames.add(judge.name);
3641
+ const dimensionKeys = /* @__PURE__ */ new Set();
3642
+ for (const dimension of judge.dimensions) {
3643
+ if (!dimension || typeof dimension.key !== "string" || dimension.key.trim().length === 0 || dimension.key.trim() !== dimension.key || typeof dimension.description !== "string" || dimension.description.trim().length === 0) {
3644
+ throw new Error(
3645
+ `compareOptimizationMethods: judge '${judge.name}' has an invalid dimension`
3646
+ );
3647
+ }
3648
+ if (dimensionKeys.has(dimension.key)) {
3649
+ throw new Error(
3650
+ `compareOptimizationMethods: judge '${judge.name}' has duplicate dimension '${dimension.key}'`
3651
+ );
3652
+ }
3653
+ dimensionKeys.add(dimension.key);
3654
+ }
3655
+ }
3656
+ if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
3657
+ throw new Error("compareOptimizationMethods: runDir must be a non-empty string");
3658
+ }
3659
+ if (!Number.isSafeInteger(seed)) {
3660
+ throw new Error(`compareOptimizationMethods: seed must be a safe integer, got ${String(seed)}`);
3661
+ }
3662
+ if (!Number.isSafeInteger(resamples) || resamples <= 0 || resamples > 1e6) {
3663
+ throw new Error(
3664
+ `compareOptimizationMethods: resamples must be a positive safe integer no greater than 1000000, got ${String(resamples)}`
3665
+ );
3666
+ }
3667
+ if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) {
3668
+ throw new Error(
3669
+ `compareOptimizationMethods: confidence must be a finite number in (0,1), got ${String(confidence)}`
3670
+ );
3671
+ }
3672
+ const minimumResamples = minimumBootstrapResamples(
3673
+ confidence,
3674
+ opts.methods.length * (opts.methods.length + 1) / 2
3675
+ );
3676
+ if (resamples < minimumResamples) {
3677
+ throw new Error(
3678
+ `compareOptimizationMethods: resamples must be at least ${minimumResamples} for simultaneous confidence ${confidence} across ${opts.methods.length} methods, got ${resamples}`
3679
+ );
3680
+ }
3681
+ if (opts.optimizationConcurrency !== void 0 && (!Number.isSafeInteger(opts.optimizationConcurrency) || opts.optimizationConcurrency <= 0)) {
3682
+ throw new Error(
3683
+ "compareOptimizationMethods: optimizationConcurrency must be a positive safe integer"
3684
+ );
3685
+ }
3686
+ if (opts.maxConcurrency !== void 0 && (!Number.isSafeInteger(opts.maxConcurrency) || opts.maxConcurrency <= 0)) {
3687
+ throw new Error("compareOptimizationMethods: maxConcurrency must be a positive safe integer");
3688
+ }
3689
+ if (opts.dispatchTimeoutMs !== void 0 && (!Number.isSafeInteger(opts.dispatchTimeoutMs) || opts.dispatchTimeoutMs < 0 || opts.dispatchTimeoutMs > 2147483647)) {
3690
+ throw new Error(
3691
+ "compareOptimizationMethods: dispatchTimeoutMs must be a non-negative safe integer no greater than 2147483647"
3692
+ );
3693
+ }
3694
+ if (opts.costCeiling !== void 0 && (!Number.isFinite(opts.costCeiling) || opts.costCeiling < 0)) {
3695
+ throw new Error(
3696
+ "compareOptimizationMethods: costCeiling must be a finite number greater than or equal to 0"
3697
+ );
3698
+ }
3699
+ if (opts.costCeiling !== void 0 && opts.costLedger !== void 0 && opts.costLedger.costCeilingUsd !== opts.costCeiling) {
3700
+ throw new Error(
3701
+ "compareOptimizationMethods: costCeiling must match the shared CostLedger ceiling"
3702
+ );
3703
+ }
3704
+ }
3705
+ function assertComparisonPartitions(opts) {
3706
+ const legacy = opts;
3707
+ if (legacy.holdoutScenarios !== void 0) {
3708
+ throw new Error(
3709
+ "compareOptimizationMethods: holdoutScenarios is ambiguous and no longer accepted. Provide disjoint trainScenarios, selectionScenarios, and testScenarios; selection may be reused adaptively, test must remain untouched."
3710
+ );
3711
+ }
3712
+ const partitions = [
3713
+ { name: "trainScenarios", scenarios: opts.trainScenarios },
3714
+ { name: "selectionScenarios", scenarios: opts.selectionScenarios },
3715
+ { name: "testScenarios", scenarios: opts.testScenarios }
3716
+ ];
3717
+ const owner = /* @__PURE__ */ new Map();
3718
+ for (const partition of partitions) {
3719
+ if (!Array.isArray(partition.scenarios) || partition.scenarios.length === 0) {
3720
+ throw new Error(`compareOptimizationMethods: ${partition.name} is empty`);
3721
+ }
3722
+ if (partition.name === "testScenarios" && partition.scenarios.length < 2) {
3723
+ throw new Error(
3724
+ "compareOptimizationMethods: testScenarios requires at least 2 scenarios to estimate uncertainty"
3725
+ );
3726
+ }
3727
+ const local = /* @__PURE__ */ new Set();
3728
+ const duplicates = /* @__PURE__ */ new Set();
3729
+ const overlaps = /* @__PURE__ */ new Map();
3730
+ for (const scenario of partition.scenarios) {
3731
+ if (local.has(scenario.id)) duplicates.add(scenario.id);
3732
+ local.add(scenario.id);
3733
+ const prior = owner.get(scenario.id);
3734
+ if (prior !== void 0 && prior !== partition.name) overlaps.set(scenario.id, prior);
3735
+ }
3736
+ if (duplicates.size > 0) {
3737
+ throw new Error(
3738
+ `compareOptimizationMethods: ${partition.name} contains duplicate scenario id(s) [${[
3739
+ ...duplicates
3740
+ ].join(", ")}]`
3741
+ );
3742
+ }
3743
+ if (overlaps.size > 0) {
3744
+ const detail = [...overlaps].map(([id, prior]) => `${id} (${prior} \u2229 ${partition.name})`).join(", ");
3745
+ throw new Error(
3746
+ `compareOptimizationMethods: trainScenarios, selectionScenarios, and testScenarios must be pairwise disjoint; overlap: [${detail}]`
3747
+ );
3748
+ }
3749
+ assertCampaignDesign(partition.scenarios, opts.reps ?? 1);
3750
+ for (const id of local) owner.set(id, partition.name);
3751
+ }
3752
+ }
3753
+ function mean2(xs) {
3754
+ return xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
3755
+ }
3756
+ function slug(name) {
3757
+ return name.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "method";
3758
+ }
3759
+ function assertConfidence(confidence) {
3760
+ if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) {
3761
+ throw new Error(
3762
+ `compareOptimizationMethods: confidence must be a finite number in (0,1), got ${String(confidence)}`
3763
+ );
3764
+ }
3765
+ }
3766
+ function minimumBootstrapResamples(confidence, comparisonCount) {
3767
+ const exact = 2 * comparisonCount / (1 - confidence);
3768
+ return Math.ceil(exact - Number.EPSILON * Math.max(1, exact) * 32);
3769
+ }
3770
+ function createOptimizationMethodInput(opts, methodName, resolvedRunDir, seed) {
3771
+ const cloneScenarios = (scenarios) => Object.freeze(scenarios.map((scenario) => structuredClone(scenario)));
3772
+ const judges = opts.judges.map(
3773
+ (judge) => Object.freeze({
3774
+ ...judge,
3775
+ dimensions: Object.freeze(
3776
+ judge.dimensions.map((dimension) => Object.freeze({ ...dimension }))
3777
+ )
3778
+ })
3779
+ );
3780
+ return Object.freeze({
3781
+ baselineSurface: structuredClone(opts.baselineSurface),
3782
+ trainScenarios: cloneScenarios(opts.trainScenarios),
3783
+ selectionScenarios: cloneScenarios(opts.selectionScenarios),
3784
+ dispatchWithSurface: opts.dispatchWithSurface,
3785
+ judges: Object.freeze(judges),
3786
+ runDir: `${resolvedRunDir}/optimization/${slug(methodName)}`,
3787
+ seed,
3788
+ runOptions: Object.freeze({ ...opts.optimizationRunOptions ?? {} })
3789
+ });
3790
+ }
3791
+ function costFromLedgerSummary(summary) {
3792
+ const cost = {
3793
+ totalCostUsd: summary.totalCostUsd,
3794
+ accountingComplete: summary.accountingComplete,
3795
+ incompleteReasons: [...summary.incompleteReasons]
3796
+ };
3797
+ assertComparisonCost(cost, "cost ledger");
3798
+ return cost;
3799
+ }
3800
+ function combineCosts(entries) {
3801
+ return {
3802
+ totalCostUsd: entries.reduce((total, entry) => total + entry.cost.totalCostUsd, 0),
3803
+ accountingComplete: entries.every((entry) => entry.cost.accountingComplete),
3804
+ incompleteReasons: entries.flatMap(
3805
+ (entry) => entry.cost.incompleteReasons.map((reason) => `${entry.label}: ${reason}`)
3806
+ )
3807
+ };
3808
+ }
3809
+ function assertComparisonCost(cost, label) {
3810
+ if (!cost || typeof cost !== "object") {
3811
+ throw new Error(`compareOptimizationMethods: ${label} returned no cost`);
3812
+ }
3813
+ if (!Number.isFinite(cost.totalCostUsd) || cost.totalCostUsd < 0) {
3814
+ throw new Error(`compareOptimizationMethods: ${label} returned an invalid totalCostUsd`);
3815
+ }
3816
+ if (typeof cost.accountingComplete !== "boolean") {
3817
+ throw new Error(`compareOptimizationMethods: ${label} returned invalid accountingComplete`);
3818
+ }
3819
+ if (!Array.isArray(cost.incompleteReasons) || cost.incompleteReasons.some(
3820
+ (reason) => typeof reason !== "string" || reason.trim().length === 0
3821
+ )) {
3822
+ throw new Error(`compareOptimizationMethods: ${label} returned invalid incompleteReasons`);
3823
+ }
3824
+ if (cost.accountingComplete !== (cost.incompleteReasons.length === 0)) {
3825
+ throw new Error(
3826
+ `compareOptimizationMethods: ${label} returned inconsistent cost completeness and reasons`
3827
+ );
3828
+ }
3829
+ }
3830
+
3407
3831
  // src/campaign/presets/run-skill-opt.ts
3408
3832
  async function runSkillOpt(opts) {
3409
- if (opts.trainScenarios.length === 0) throw new Error("runSkillOpt: trainScenarios is empty");
3410
- if (opts.holdoutScenarios.length === 0) throw new Error("runSkillOpt: holdoutScenarios is empty");
3833
+ const legacy = opts;
3834
+ if (legacy.holdoutScenarios !== void 0) {
3835
+ throw new Error(
3836
+ "runSkillOpt: holdoutScenarios was renamed to selectionScenarios because SkillOpt adaptively reuses it for edit acceptance. Provide selectionScenarios and score any final test outside runSkillOpt."
3837
+ );
3838
+ }
3839
+ if (!Array.isArray(opts.trainScenarios) || opts.trainScenarios.length === 0)
3840
+ throw new Error("runSkillOpt: trainScenarios is empty");
3841
+ if (!Array.isArray(opts.selectionScenarios) || opts.selectionScenarios.length === 0)
3842
+ throw new Error("runSkillOpt: selectionScenarios is empty");
3411
3843
  if (!opts.judges || opts.judges.length === 0) {
3412
3844
  throw new Error(
3413
3845
  "runSkillOpt: at least one judge is required \u2014 scoring (and therefore acceptance) is meaningless without one, and would report a silent zero lift."
3414
3846
  );
3415
3847
  }
3416
- const holdoutIds = new Set(opts.holdoutScenarios.map((s) => s.id));
3417
- const overlap = opts.trainScenarios.filter((s) => holdoutIds.has(s.id)).map((s) => s.id);
3848
+ const selectionIds = new Set(opts.selectionScenarios.map((s) => s.id));
3849
+ const overlap = opts.trainScenarios.filter((s) => selectionIds.has(s.id)).map((s) => s.id);
3418
3850
  if (overlap.length > 0) {
3419
3851
  throw new Error(
3420
- `runSkillOpt: trainScenarios and holdoutScenarios must be disjoint (overlap: [${overlap.join(
3852
+ `runSkillOpt: trainScenarios and selectionScenarios must be disjoint (overlap: [${overlap.join(
3421
3853
  ", "
3422
- )}]) \u2014 a shared scenario leaks the held-out acceptance axis into the proposal evidence.`
3854
+ )}]) \u2014 a shared scenario leaks the selection axis into the proposal evidence.`
3423
3855
  );
3424
3856
  }
3857
+ const maxEpochs = opts.maxEpochs;
3425
3858
  const patchesPerEpoch = opts.patchesPerEpoch ?? 2;
3426
3859
  const initialBudget = opts.editBudget ?? 3;
3427
3860
  const minImprovement = opts.minImprovement ?? 0;
3428
- if (minImprovement < 0) {
3429
- throw new Error(
3430
- "runSkillOpt: minImprovement must be >= 0 \u2014 a negative threshold would accept held-out regressions, breaking the monotonic-lift contract."
3431
- );
3432
- }
3433
- const patience = opts.patience ?? opts.maxEpochs;
3861
+ const patience = opts.patience ?? maxEpochs;
3434
3862
  const budgetAnneal = opts.budgetAnneal ?? true;
3435
3863
  const rejectedBufferSize = opts.rejectedBufferSize ?? 12;
3436
3864
  const slowMetaEvery = opts.slowMetaEvery ?? 2;
3437
- opts.runDir = resolveRunDir(opts.runDir, opts.repo);
3865
+ const evidenceK = opts.evidenceK ?? 3;
3866
+ assertPositiveSafeInteger("maxEpochs", maxEpochs);
3867
+ assertPositiveSafeInteger("patchesPerEpoch", patchesPerEpoch);
3868
+ assertPositiveSafeInteger("editBudget", initialBudget);
3869
+ assertFiniteNonNegative("minImprovement", minImprovement);
3870
+ assertPositiveSafeInteger("patience", patience);
3871
+ assertNonNegativeSafeInteger("rejectedBufferSize", rejectedBufferSize);
3872
+ assertNonNegativeSafeInteger("slowMetaEvery", slowMetaEvery);
3873
+ assertPositiveSafeInteger("evidenceK", evidenceK);
3874
+ if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
3875
+ throw new Error("runSkillOpt: runDir must be a non-empty string");
3876
+ }
3877
+ const runDir = resolveRunDir(opts.runDir, opts.repo);
3438
3878
  const storage = opts.storage ?? fsCampaignStorage();
3439
3879
  const costLedger = opts.costLedger ?? createRunCostLedger({
3440
3880
  storage,
3441
- runDir: opts.runDir,
3881
+ runDir,
3442
3882
  costCeilingUsd: opts.costCeiling
3443
3883
  });
3444
- const scoreHoldout = async (surface, tag) => {
3445
- const campaign = await runScoringCampaign(opts, opts.holdoutScenarios, surface, tag, costLedger);
3884
+ const scoreSelection = async (surface, tag) => {
3885
+ const campaign = await runScoringCampaign(
3886
+ opts,
3887
+ opts.selectionScenarios,
3888
+ surface,
3889
+ tag,
3890
+ costLedger,
3891
+ runDir
3892
+ );
3446
3893
  return campaignMeanComposite(campaign);
3447
3894
  };
3448
- const evidenceK = opts.evidenceK ?? 3;
3449
3895
  const trainEvidence = async (surface, tag) => {
3450
- const campaign = await runScoringCampaign(opts, opts.trainScenarios, surface, tag, costLedger);
3896
+ const campaign = await runScoringCampaign(
3897
+ opts,
3898
+ opts.trainScenarios,
3899
+ surface,
3900
+ tag,
3901
+ costLedger,
3902
+ runDir
3903
+ );
3451
3904
  return toEvidence(campaign, evidenceK);
3452
3905
  };
3453
3906
  let current = opts.baselineSurface;
3454
3907
  let currentEvidence = await trainEvidence(current, "baseline-train");
3455
- const baselineHoldout = await scoreHoldout(current, "baseline-holdout");
3456
- let currentHoldout = baselineHoldout;
3908
+ const baselineSelection = await scoreSelection(current, "baseline-selection");
3909
+ let currentSelection = baselineSelection;
3457
3910
  const buffer = [];
3458
3911
  const acceptedEdits = [];
3459
3912
  const rejectedAll = [];
@@ -3462,9 +3915,9 @@ async function runSkillOpt(opts) {
3462
3915
  let sinceAccept = 0;
3463
3916
  let metaNote;
3464
3917
  let epochsRun = 0;
3465
- for (let epoch = 0; epoch < opts.maxEpochs; epoch++) {
3918
+ for (let epoch = 0; epoch < maxEpochs; epoch++) {
3466
3919
  epochsRun++;
3467
- const patches = await opts.proposer.proposePatches({
3920
+ const proposed = await opts.proposer.proposePatches({
3468
3921
  surface: current,
3469
3922
  evidence: currentEvidence,
3470
3923
  editBudget: budget,
@@ -3475,6 +3928,10 @@ async function runSkillOpt(opts) {
3475
3928
  costLedger,
3476
3929
  costPhase: "skill-opt.proposal"
3477
3930
  });
3931
+ if (!Array.isArray(proposed)) {
3932
+ throw new Error("runSkillOpt: proposer.proposePatches() must return an array");
3933
+ }
3934
+ const patches = proposed.slice(0, patchesPerEpoch);
3478
3935
  let accepted = null;
3479
3936
  const rejectedThisEpoch = [];
3480
3937
  for (let i = 0; i < patches.length; i++) {
@@ -3488,23 +3945,26 @@ async function runSkillOpt(opts) {
3488
3945
  });
3489
3946
  continue;
3490
3947
  }
3491
- const candidateHoldout = await scoreHoldout(candidate, `epoch-${epoch}-cand-${i}-holdout`);
3492
- if (candidateHoldout > currentHoldout + minImprovement) {
3948
+ const candidateSelection = await scoreSelection(
3949
+ candidate,
3950
+ `epoch-${epoch}-cand-${i}-selection`
3951
+ );
3952
+ if (candidateSelection > currentSelection + minImprovement) {
3493
3953
  accepted = {
3494
3954
  epoch,
3495
3955
  label: patch.label,
3496
3956
  rationale: patch.rationale,
3497
- holdoutDelta: candidateHoldout - currentHoldout
3957
+ selectionDelta: candidateSelection - currentSelection
3498
3958
  };
3499
3959
  current = candidate;
3500
- currentHoldout = candidateHoldout;
3960
+ currentSelection = candidateSelection;
3501
3961
  currentEvidence = await trainEvidence(current, `epoch-${epoch}-train`);
3502
3962
  break;
3503
3963
  }
3504
3964
  rejectedThisEpoch.push({
3505
3965
  label: patch.label,
3506
3966
  rationale: patch.rationale,
3507
- reason: `held-out ${candidateHoldout.toFixed(3)} \u2264 current ${currentHoldout.toFixed(3)}`
3967
+ reason: `selection ${candidateSelection.toFixed(3)} \u2264 current ${currentSelection.toFixed(3)}`
3508
3968
  });
3509
3969
  }
3510
3970
  if (accepted) {
@@ -3528,16 +3988,16 @@ async function runSkillOpt(opts) {
3528
3988
  proposed: patches.length,
3529
3989
  accepted,
3530
3990
  rejected: rejectedThisEpoch,
3531
- holdoutComposite: currentHoldout
3991
+ selectionComposite: currentSelection
3532
3992
  });
3533
3993
  if (sinceAccept >= patience) break;
3534
3994
  }
3535
3995
  const cost = costLedger.summary();
3536
3996
  return {
3537
3997
  winnerSurface: current,
3538
- baselineHoldoutComposite: baselineHoldout,
3539
- winnerHoldoutComposite: currentHoldout,
3540
- lift: currentHoldout - baselineHoldout,
3998
+ baselineSelectionComposite: baselineSelection,
3999
+ winnerSelectionComposite: currentSelection,
4000
+ selectionLift: currentSelection - baselineSelection,
3541
4001
  acceptedEdits,
3542
4002
  rejectedEdits: rejectedAll,
3543
4003
  epochsRun,
@@ -3546,15 +4006,30 @@ async function runSkillOpt(opts) {
3546
4006
  cost
3547
4007
  };
3548
4008
  }
3549
- function runScoringCampaign(opts, scenarios, surface, tag, costLedger) {
4009
+ function runScoringCampaign(opts, scenarios, surface, tag, costLedger, runDir) {
3550
4010
  return runCampaign({
3551
4011
  ...opts,
3552
4012
  costLedger,
3553
4013
  scenarios,
3554
4014
  dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
3555
- runDir: `${opts.runDir}/${tag}`
4015
+ runDir: `${runDir}/${tag}`
3556
4016
  });
3557
4017
  }
4018
+ function assertPositiveSafeInteger(name, value) {
4019
+ if (!Number.isSafeInteger(value) || value <= 0) {
4020
+ throw new Error(`runSkillOpt: ${name} must be a positive safe integer`);
4021
+ }
4022
+ }
4023
+ function assertNonNegativeSafeInteger(name, value) {
4024
+ if (!Number.isSafeInteger(value) || value < 0) {
4025
+ throw new Error(`runSkillOpt: ${name} must be a non-negative safe integer`);
4026
+ }
4027
+ }
4028
+ function assertFiniteNonNegative(name, value) {
4029
+ if (!Number.isFinite(value) || value < 0) {
4030
+ throw new Error(`runSkillOpt: ${name} must be a finite number greater than or equal to 0`);
4031
+ }
4032
+ }
3558
4033
  function toEvidence(campaign, k) {
3559
4034
  const { dimensions, scenarios } = campaignBreakdown(campaign);
3560
4035
  const weakScenarios = [...scenarios].sort((a, b) => a.composite - b.composite).slice(0, k);
@@ -3565,7 +4040,7 @@ function buildMetaNote(accepted, rejected) {
3565
4040
  const parts = [];
3566
4041
  if (accepted.length > 0) {
3567
4042
  parts.push(
3568
- `Edits that improved held-out so far: ${accepted.map((a) => `"${a.label}" (+${a.holdoutDelta.toFixed(3)})`).join("; ")}. Build on these.`
4043
+ `Edits that improved selection so far: ${accepted.map((a) => `"${a.label}" (+${a.selectionDelta.toFixed(3)})`).join("; ")}. Build on these.`
3569
4044
  );
3570
4045
  }
3571
4046
  if (rejected.length > 0) {
@@ -3576,231 +4051,108 @@ function buildMetaNote(accepted, rejected) {
3576
4051
  return parts.join(" ");
3577
4052
  }
3578
4053
 
3579
- // src/campaign/presets/compare-proposers.ts
3580
- async function compareProposers(opts) {
3581
- return compareProposerEntries(opts);
4054
+ // src/campaign/presets/builtin-optimization-methods.ts
4055
+ function gepaReflectionMethod(config, name = "gepa-reflection") {
4056
+ return gepaMethod(config, false, name);
3582
4057
  }
3583
- async function compareProposerEntries(opts) {
3584
- if (opts.proposers.length === 0) throw new Error("compareProposers: no proposers to compare");
3585
- const seed = opts.seed ?? 42;
3586
- const resamples = opts.resamples ?? 2e3;
3587
- const confidence = opts.confidence ?? 0.95;
3588
- const scoreOnHoldout = async (surface, tag) => {
3589
- const campaign = await runCampaign({
3590
- ...opts,
3591
- scenarios: opts.holdoutScenarios,
3592
- dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
3593
- runDir: `${opts.runDir}/${tag}`
3594
- });
3595
- const byScenario = {};
3596
- for (const { scenarioId, composite } of campaignBreakdown(campaign).scenarios) {
3597
- byScenario[scenarioId] = composite;
3598
- }
3599
- return byScenario;
3600
- };
3601
- const scenarioIds = [...new Set(opts.holdoutScenarios.map((s) => s.id))].sort();
3602
- if (scenarioIds.length === 0) throw new Error("compareProposers: holdoutScenarios is empty");
3603
- const align = (byScenario, label) => {
3604
- const missing = scenarioIds.filter((id) => !(id in byScenario));
3605
- if (missing.length > 0) {
3606
- throw new Error(
3607
- `compareProposers: ${label} produced no held-out score for scenario(s) [${missing.join(
3608
- ", "
3609
- )}] \u2014 a cell errored or its judges returned nothing. Refusing to fabricate a 0 (it would corrupt the lift comparison). Fix the dispatch/judge or drop the scenario.`
3610
- );
3611
- }
3612
- return scenarioIds.map((id) => byScenario[id]);
3613
- };
3614
- const baselineArr = align(
3615
- await scoreOnHoldout(opts.baselineSurface, "compare-baseline"),
3616
- "baseline"
3617
- );
3618
- const winners = [];
3619
- for (const proposer of opts.proposers) {
3620
- const out = await proposer.optimize();
3621
- const byScenario = await scoreOnHoldout(out.winnerSurface, `compare-${slug(proposer.name)}`);
3622
- winners.push({
3623
- name: proposer.name,
3624
- winnerSurface: out.winnerSurface,
3625
- costUsd: out.costUsd,
3626
- durationMs: out.durationMs,
3627
- arr: align(byScenario, `proposer "${proposer.name}"`)
3628
- });
3629
- }
3630
- const scores = winners.map((w) => {
3631
- const boot = pairedBootstrap(baselineArr, w.arr, {
3632
- seed,
3633
- resamples,
3634
- confidence,
3635
- statistic: "mean"
3636
- });
3637
- const score = {
3638
- name: w.name,
3639
- baselineComposite: mean2(baselineArr),
3640
- winnerComposite: mean2(w.arr),
3641
- lift: boot.mean,
3642
- liftCi: { low: boot.low, high: boot.high },
3643
- costUsd: w.costUsd,
3644
- winnerSurface: w.winnerSurface,
3645
- rank: 0
3646
- };
3647
- if (w.durationMs !== void 0) score.durationMs = w.durationMs;
3648
- return score;
3649
- });
3650
- scores.sort((a, b) => b.lift - a.lift || a.costUsd - b.costUsd);
3651
- scores.forEach((s, i) => {
3652
- s.rank = i + 1;
3653
- });
3654
- const best = scores[0];
3655
- const byName = new Map(winners.map((w) => [w.name, w]));
3656
- const bestArr = byName.get(best.name).arr;
3657
- const pairwise = scores.slice(1).map((other) => {
3658
- const otherArr = byName.get(other.name).arr;
3659
- const boot = pairedBootstrap(otherArr, bestArr, {
3660
- seed,
3661
- resamples,
3662
- confidence,
3663
- statistic: "mean"
3664
- });
3665
- const favored = boot.low > 0 ? best.name : boot.high < 0 ? other.name : "tie";
3666
- return {
3667
- a: best.name,
3668
- b: other.name,
3669
- deltaMean: boot.mean,
3670
- low: boot.low,
3671
- high: boot.high,
3672
- favored
3673
- };
3674
- });
3675
- return { scores, best, pairwise, holdoutScenarioIds: scenarioIds };
4058
+ function gepaParetoMethod(config, name = "gepa-pareto") {
4059
+ return gepaMethod(config, true, name);
3676
4060
  }
3677
- function mean2(xs) {
3678
- return xs.length === 0 ? 0 : xs.reduce((a, b) => a + b, 0) / xs.length;
3679
- }
3680
- function slug(name) {
3681
- return name.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
3682
- }
3683
- function gepaReflectionEntry(config, name = "gepa-reflection") {
3684
- return gepaEntry(config, false, name);
3685
- }
3686
- function gepaParetoEntry(config, name = "gepa-pareto") {
3687
- return gepaEntry(config, true, name);
3688
- }
3689
- function gepaEntry(config, combineParents, name) {
3690
- return {
4061
+ function gepaMethod(config, combineParents, name) {
4062
+ return improvementLoopMethod(
4063
+ config,
3691
4064
  name,
3692
- async optimize() {
3693
- const started = Date.now();
3694
- const proposer = gepaProposer({
3695
- llm: config.llm,
3696
- model: config.model,
3697
- target: config.target,
3698
- combineParents,
3699
- ...config.mutationPrimitives ? { mutationPrimitives: config.mutationPrimitives } : {}
3700
- });
3701
- const result = await runImprovementLoop({
3702
- scenarios: config.trainScenarios,
3703
- holdoutScenarios: config.holdoutScenarios,
3704
- baselineSurface: config.baselineSurface,
3705
- dispatchWithSurface: config.dispatchWithSurface,
3706
- judges: config.judges,
3707
- proposer,
3708
- populationSize: config.populationSize ?? 2,
3709
- maxGenerations: config.maxGenerations ?? 3,
3710
- gate: defaultProductionGate({
3711
- holdoutScenarios: config.holdoutScenarios,
3712
- deltaThreshold: 0
3713
- }),
3714
- autoOnPromote: "none",
3715
- runDir: `${config.runDir}/${slug(name)}-loop`,
3716
- ...config.seed !== void 0 ? { seed: config.seed } : {},
3717
- // Flow findings to the proposer's propose(). These reach runOptimization
3718
- // unchanged (runImprovementLoop extends RunOptimizationOptions and
3719
- // forwards {...opts}); ctx.findings/report/analyzeGeneration are consumed there.
3720
- ...config.findings !== void 0 ? { findings: config.findings } : {},
3721
- ...config.analyzeGeneration ? { analyzeGeneration: config.analyzeGeneration } : {},
3722
- ...config.report !== void 0 ? { report: config.report } : {}
3723
- });
3724
- return {
3725
- winnerSurface: result.winnerSurface,
3726
- costUsd: result.cost.totalCostUsd,
3727
- durationMs: Date.now() - started
3728
- };
3729
- }
3730
- };
4065
+ () => gepaProposer({
4066
+ llm: config.llm,
4067
+ model: config.model,
4068
+ target: config.target,
4069
+ combineParents,
4070
+ ...config.mutationPrimitives ? { mutationPrimitives: config.mutationPrimitives } : {}
4071
+ })
4072
+ );
3731
4073
  }
3732
- function skillOptEntry(config, name = "skill-opt") {
4074
+ function skillOptMethod(config, name = "skill-opt") {
3733
4075
  return {
3734
4076
  name,
3735
- async optimize() {
4077
+ async optimize(input) {
3736
4078
  const started = Date.now();
4079
+ if (typeof input.baselineSurface !== "string") {
4080
+ throw new Error(`${name}: SkillOpt requires a string baselineSurface`);
4081
+ }
3737
4082
  const proposer = skillOptProposer({
3738
4083
  llm: config.llm,
3739
4084
  model: config.model,
3740
4085
  target: config.target
3741
4086
  });
3742
4087
  const result = await runSkillOpt({
3743
- baselineSurface: config.baselineSurface,
3744
- dispatchWithSurface: config.dispatchWithSurface,
3745
- judges: config.judges,
4088
+ ...input.runOptions,
4089
+ ...config.runOptions ?? {},
4090
+ baselineSurface: input.baselineSurface,
4091
+ dispatchWithSurface: input.dispatchWithSurface,
4092
+ judges: [...input.judges],
3746
4093
  proposer,
3747
- trainScenarios: config.trainScenarios,
3748
- holdoutScenarios: config.holdoutScenarios,
4094
+ trainScenarios: [...input.trainScenarios],
4095
+ selectionScenarios: [...input.selectionScenarios],
3749
4096
  maxEpochs: config.maxEpochs ?? 6,
3750
- runDir: `${config.runDir}/${slug(name)}-loop`,
3751
- ...config.seed !== void 0 ? { seed: config.seed } : {}
4097
+ runDir: `${input.runDir}/loop`,
4098
+ seed: config.seed ?? input.seed
3752
4099
  });
3753
4100
  return {
3754
4101
  winnerSurface: result.winnerSurface,
3755
- costUsd: result.totalCostUsd,
4102
+ cost: costFromLedgerSummary(result.cost),
3756
4103
  durationMs: Date.now() - started
3757
4104
  };
3758
4105
  }
3759
4106
  };
3760
4107
  }
3761
- function fapoEscalationEntry(config, name = "fapo-escalation") {
4108
+ function fapoEscalationMethod(config, name = "fapo-escalation") {
4109
+ return improvementLoopMethod(config, name, () => {
4110
+ const promptProposer = config.promptProposer ?? gepaProposer({
4111
+ llm: config.llm,
4112
+ model: config.model,
4113
+ target: config.target,
4114
+ combineParents: true,
4115
+ ...config.mutationPrimitives ? { mutationPrimitives: config.mutationPrimitives } : {}
4116
+ });
4117
+ const parameterProposer = config.parameterProposer ?? (config.parameterCandidates ? parameterSweepProposer({ candidates: config.parameterCandidates }) : void 0);
4118
+ return fapoProposer({
4119
+ ...config.fapo ?? {},
4120
+ promptProposer,
4121
+ ...parameterProposer ? { parameterProposer } : {},
4122
+ ...config.structuralProposer ? { structuralProposer: config.structuralProposer } : {}
4123
+ });
4124
+ });
4125
+ }
4126
+ function improvementLoopMethod(config, name, createProposer) {
3762
4127
  return {
3763
4128
  name,
3764
- async optimize() {
4129
+ async optimize(input) {
3765
4130
  const started = Date.now();
3766
- const promptProposer = config.promptProposer ?? gepaProposer({
3767
- llm: config.llm,
3768
- model: config.model,
3769
- target: config.target,
3770
- combineParents: true,
3771
- ...config.mutationPrimitives ? { mutationPrimitives: config.mutationPrimitives } : {}
3772
- });
3773
- const parameterProposer = config.parameterProposer ?? (config.parameterCandidates ? parameterSweepProposer({ candidates: config.parameterCandidates }) : void 0);
3774
- const structuralProposer = config.structuralProposer;
3775
- const proposer = fapoProposer({
3776
- ...config.fapo ?? {},
3777
- promptProposer,
3778
- ...parameterProposer ? { parameterProposer } : {},
3779
- ...structuralProposer ? { structuralProposer } : {}
3780
- });
3781
4131
  const result = await runImprovementLoop({
3782
- scenarios: config.trainScenarios,
3783
- holdoutScenarios: config.holdoutScenarios,
3784
- baselineSurface: config.baselineSurface,
3785
- dispatchWithSurface: config.dispatchWithSurface,
3786
- judges: config.judges,
3787
- proposer,
4132
+ ...input.runOptions,
4133
+ ...config.runOptions ?? {},
4134
+ scenarios: [...input.trainScenarios],
4135
+ holdoutScenarios: [...input.selectionScenarios],
4136
+ baselineSurface: input.baselineSurface,
4137
+ dispatchWithSurface: input.dispatchWithSurface,
4138
+ judges: [...input.judges],
4139
+ proposer: createProposer(),
3788
4140
  populationSize: config.populationSize ?? 2,
3789
4141
  maxGenerations: config.maxGenerations ?? 3,
3790
4142
  gate: defaultProductionGate({
3791
- holdoutScenarios: config.holdoutScenarios,
4143
+ holdoutScenarios: [...input.selectionScenarios],
3792
4144
  deltaThreshold: 0
3793
4145
  }),
3794
4146
  autoOnPromote: "none",
3795
- runDir: `${config.runDir}/${slug(name)}-loop`,
3796
- ...config.seed !== void 0 ? { seed: config.seed } : {},
4147
+ runDir: `${input.runDir}/loop`,
4148
+ seed: config.seed ?? input.seed,
3797
4149
  ...config.findings !== void 0 ? { findings: config.findings } : {},
3798
4150
  ...config.analyzeGeneration ? { analyzeGeneration: config.analyzeGeneration } : {},
3799
4151
  ...config.report !== void 0 ? { report: config.report } : {}
3800
4152
  });
3801
4153
  return {
3802
- winnerSurface: result.winnerSurface,
3803
- costUsd: result.cost.totalCostUsd,
4154
+ winnerSurface: result.gateResult.decision === "ship" ? result.winnerSurface : input.baselineSurface,
4155
+ cost: costFromLedgerSummary(result.cost),
3804
4156
  durationMs: Date.now() - started
3805
4157
  };
3806
4158
  }
@@ -3808,7 +4160,7 @@ function fapoEscalationEntry(config, name = "fapo-escalation") {
3808
4160
  }
3809
4161
 
3810
4162
  // src/completion-verifier.ts
3811
- import { randomUUID } from "crypto";
4163
+ import { randomUUID as randomUUID2 } from "crypto";
3812
4164
  function completionVerdict(input) {
3813
4165
  if (input.requirements.length === 0) {
3814
4166
  throw new Error(
@@ -4068,7 +4420,7 @@ ${content.slice(0, maxContentChars)}`
4068
4420
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
4069
4421
  const started = Date.now();
4070
4422
  await record({
4071
- eventId: randomUUID(),
4423
+ eventId: randomUUID2(),
4072
4424
  provider: "correctness-checker",
4073
4425
  model,
4074
4426
  endpoint: "/chat",
@@ -4100,7 +4452,7 @@ ${content.slice(0, maxContentChars)}`
4100
4452
  const resp = paid.value;
4101
4453
  const raw = resp.choices?.[0]?.message?.content ?? "";
4102
4454
  await record({
4103
- eventId: randomUUID(),
4455
+ eventId: randomUUID2(),
4104
4456
  provider: "correctness-checker",
4105
4457
  model,
4106
4458
  endpoint: "/chat",
@@ -4116,7 +4468,7 @@ ${content.slice(0, maxContentChars)}`
4116
4468
  } catch (err) {
4117
4469
  lastErr = err;
4118
4470
  await record({
4119
- eventId: randomUUID(),
4471
+ eventId: randomUUID2(),
4120
4472
  provider: "correctness-checker",
4121
4473
  model,
4122
4474
  endpoint: "/chat",
@@ -4960,9 +5312,24 @@ function compositeProposer(opts) {
4960
5312
  const members = opts.proposers;
4961
5313
  if (members.length === 0)
4962
5314
  throw new Error("compositeProposer: at least one member proposer required");
5315
+ const memberKinds = /* @__PURE__ */ new Set();
5316
+ for (const member of members) {
5317
+ if (!member.kind || member.kind.trim() !== member.kind) {
5318
+ throw new Error("compositeProposer: member kinds must be trimmed and non-empty");
5319
+ }
5320
+ if (member.kind.includes(":")) {
5321
+ throw new Error(`compositeProposer: member kind '${member.kind}' must not contain ':'`);
5322
+ }
5323
+ if (memberKinds.has(member.kind)) {
5324
+ throw new Error(`compositeProposer: duplicate member kind '${member.kind}'`);
5325
+ }
5326
+ memberKinds.add(member.kind);
5327
+ }
4963
5328
  const weights = opts.weights ?? members.map(() => 1);
4964
- if (weights.length !== members.length || weights.some((w) => !(w > 0))) {
4965
- throw new Error("compositeProposer: weights must match proposers length and be positive");
5329
+ if (weights.length !== members.length || weights.some((weight) => !Number.isFinite(weight) || weight <= 0)) {
5330
+ throw new Error(
5331
+ "compositeProposer: weights must match proposers length and be finite and positive"
5332
+ );
4966
5333
  }
4967
5334
  return {
4968
5335
  kind: `composite(${members.map((m) => m.kind).join("+")})`,
@@ -4985,7 +5352,11 @@ function compositeProposer(opts) {
4985
5352
  const share = shares[i] ?? 0;
4986
5353
  if (!member || share === 0 || ctx.signal.aborted) continue;
4987
5354
  try {
4988
- const proposals = await member.propose({ ...ctx, populationSize: share });
5355
+ const proposals = await member.propose({
5356
+ ...ctx,
5357
+ history: historyForMember(ctx.history, member.kind),
5358
+ populationSize: share
5359
+ });
4989
5360
  for (const proposal of proposals) {
4990
5361
  const isCandidate = typeof proposal === "object" && proposal !== null && "surface" in proposal;
4991
5362
  const surface = isCandidate ? proposal.surface : proposal;
@@ -5019,7 +5390,11 @@ function compositeProposer(opts) {
5019
5390
  return pool.slice(0, ctx.populationSize);
5020
5391
  },
5021
5392
  decide(args) {
5022
- const votes = members.filter((m) => typeof m.decide === "function").map((m) => m.decide(args));
5393
+ const votes = members.filter((m) => typeof m.decide === "function").map(
5394
+ (m) => m.decide({
5395
+ history: historyForMember(args.history, m.kind)
5396
+ })
5397
+ );
5023
5398
  if (votes.length === 0) return { stop: false };
5024
5399
  const allStop = votes.every((v) => v.stop);
5025
5400
  return allStop ? {
@@ -5029,6 +5404,15 @@ function compositeProposer(opts) {
5029
5404
  }
5030
5405
  };
5031
5406
  }
5407
+ function historyForMember(history, memberKind) {
5408
+ const prefix = `${memberKind}:`;
5409
+ return history.map((generation) => ({
5410
+ ...generation,
5411
+ candidates: generation.candidates.map(
5412
+ (candidate) => candidate.label?.startsWith(prefix) ? { ...candidate, label: candidate.label.slice(prefix.length) } : candidate
5413
+ )
5414
+ }));
5415
+ }
5032
5416
 
5033
5417
  // src/campaign/proposers/halo.ts
5034
5418
  import { execFile } from "child_process";
@@ -5252,7 +5636,7 @@ function coerceCandidateSurface(surface) {
5252
5636
 
5253
5637
  // src/campaign/proposers/policy-edit-author-context.ts
5254
5638
  function selectPolicyEditAuthorRows(rows, options) {
5255
- assertPositiveSafeInteger(options.limit, "limit");
5639
+ assertPositiveSafeInteger2(options.limit, "limit");
5256
5640
  const unique2 = /* @__PURE__ */ new Map();
5257
5641
  for (const row of rows) {
5258
5642
  if (!row.scenarioId || row.scenarioId.trim() !== row.scenarioId) {
@@ -5296,7 +5680,7 @@ function selectPolicyEditAuthorRows(rows, options) {
5296
5680
  return selected;
5297
5681
  }
5298
5682
  function assertPolicyEditAuthorContextBudget(value, maxChars) {
5299
- assertPositiveSafeInteger(maxChars, "maxChars");
5683
+ assertPositiveSafeInteger2(maxChars, "maxChars");
5300
5684
  const json = JSON.stringify(value);
5301
5685
  if (json === void 0) {
5302
5686
  throw new Error("assertPolicyEditAuthorContextBudget: value must serialize to JSON");
@@ -5312,7 +5696,7 @@ function assertPolicyEditAuthorContextBudget(value, maxChars) {
5312
5696
  function compareScenarioId(a, b) {
5313
5697
  return a.scenarioId < b.scenarioId ? -1 : a.scenarioId > b.scenarioId ? 1 : 0;
5314
5698
  }
5315
- function assertPositiveSafeInteger(value, name) {
5699
+ function assertPositiveSafeInteger2(value, name) {
5316
5700
  if (!Number.isSafeInteger(value) || value <= 0) {
5317
5701
  throw new Error(`${name} must be a positive safe integer (got ${value})`);
5318
5702
  }
@@ -5327,6 +5711,7 @@ var JSON_POLICY_EDIT_TARGET_SURFACES = [
5327
5711
  "agent-profile"
5328
5712
  ];
5329
5713
  var NonEmptyStringSchema = z.string().trim().min(1);
5714
+ var JsonObjectKeySchema = z.string().min(1).refine((key) => key.trim() === key, "JSON object keys must not have surrounding whitespace");
5330
5715
  var JsonValueSchema = z.lazy(
5331
5716
  () => z.union([
5332
5717
  z.string(),
@@ -5334,42 +5719,32 @@ var JsonValueSchema = z.lazy(
5334
5719
  z.boolean(),
5335
5720
  z.null(),
5336
5721
  z.array(JsonValueSchema),
5337
- z.record(NonEmptyStringSchema, JsonValueSchema)
5722
+ z.record(JsonObjectKeySchema, JsonValueSchema)
5338
5723
  ])
5339
5724
  );
5340
5725
  var AuthoredJsonChangeSchema = z.discriminatedUnion("mode", [
5341
5726
  z.object({
5342
- kind: z.literal("json"),
5343
5727
  mode: z.literal("set"),
5344
- path: NonEmptyStringSchema,
5345
5728
  value: JsonValueSchema
5346
5729
  }).strict(),
5347
5730
  z.object({
5348
- kind: z.literal("json"),
5349
5731
  mode: z.literal("merge"),
5350
- path: NonEmptyStringSchema,
5351
5732
  value: JsonValueSchema
5352
5733
  }).strict(),
5353
5734
  z.object({
5354
- kind: z.literal("json"),
5355
- mode: z.literal("remove"),
5356
- path: NonEmptyStringSchema
5735
+ mode: z.literal("remove")
5357
5736
  }).strict()
5358
5737
  ]);
5359
5738
  var AuthoredPolicyEditSchema = z.object({
5360
5739
  axis: z.enum(POLICY_EDIT_AXES),
5361
5740
  target: z.object({
5362
- surface: z.enum(POLICY_EDIT_TARGET_SURFACES),
5363
5741
  path: NonEmptyStringSchema,
5364
5742
  label: NonEmptyStringSchema.max(200).nullable()
5365
5743
  }).strict(),
5366
5744
  change: AuthoredJsonChangeSchema,
5367
5745
  claim: NonEmptyStringSchema.max(2e3),
5368
5746
  expectedGain: z.object({
5369
- metric: NonEmptyStringSchema.max(400),
5370
- direction: z.enum(["increase", "decrease"]),
5371
5747
  amount: z.number().finite().positive(),
5372
- unit: z.enum(["absolute", "relative", "percent", "score"]).nullable(),
5373
5748
  rationale: NonEmptyStringSchema.max(2e3).nullable()
5374
5749
  }).strict(),
5375
5750
  confidence: z.number().finite().min(0).max(1),
@@ -5410,11 +5785,15 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
5410
5785
  target: {
5411
5786
  type: "object",
5412
5787
  additionalProperties: false,
5413
- required: ["surface", "path", "label"],
5788
+ required: ["path", "label"],
5414
5789
  properties: {
5415
- surface: { type: "string", enum: [...POLICY_EDIT_TARGET_SURFACES] },
5416
5790
  path: { type: "string", minLength: 1 },
5417
- label: { type: ["string", "null"], maxLength: 200 }
5791
+ label: {
5792
+ type: ["string", "null"],
5793
+ minLength: 1,
5794
+ maxLength: 200,
5795
+ pattern: "\\S"
5796
+ }
5418
5797
  }
5419
5798
  },
5420
5799
  change: {
@@ -5422,51 +5801,44 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
5422
5801
  {
5423
5802
  type: "object",
5424
5803
  additionalProperties: false,
5425
- required: ["kind", "mode", "path", "value"],
5804
+ required: ["mode", "value"],
5426
5805
  properties: {
5427
- kind: { const: "json" },
5428
5806
  mode: { const: "set" },
5429
- path: { type: "string", minLength: 1 },
5430
5807
  value: {}
5431
5808
  }
5432
5809
  },
5433
5810
  {
5434
5811
  type: "object",
5435
5812
  additionalProperties: false,
5436
- required: ["kind", "mode", "path", "value"],
5813
+ required: ["mode", "value"],
5437
5814
  properties: {
5438
- kind: { const: "json" },
5439
5815
  mode: { const: "merge" },
5440
- path: { type: "string", minLength: 1 },
5441
5816
  value: {}
5442
5817
  }
5443
5818
  },
5444
5819
  {
5445
5820
  type: "object",
5446
5821
  additionalProperties: false,
5447
- required: ["kind", "mode", "path"],
5822
+ required: ["mode"],
5448
5823
  properties: {
5449
- kind: { const: "json" },
5450
- mode: { const: "remove" },
5451
- path: { type: "string", minLength: 1 }
5824
+ mode: { const: "remove" }
5452
5825
  }
5453
5826
  }
5454
5827
  ]
5455
5828
  },
5456
- claim: { type: "string", minLength: 1, maxLength: 2e3 },
5829
+ claim: { type: "string", minLength: 1, maxLength: 2e3, pattern: "\\S" },
5457
5830
  expectedGain: {
5458
5831
  type: "object",
5459
5832
  additionalProperties: false,
5460
- required: ["metric", "direction", "amount", "unit", "rationale"],
5833
+ required: ["amount", "rationale"],
5461
5834
  properties: {
5462
- metric: { type: "string", minLength: 1, maxLength: 400 },
5463
- direction: { type: "string", enum: ["increase", "decrease"] },
5464
5835
  amount: { type: "number", exclusiveMinimum: 0 },
5465
- unit: {
5836
+ rationale: {
5466
5837
  type: ["string", "null"],
5467
- enum: ["absolute", "relative", "percent", "score", null]
5468
- },
5469
- rationale: { type: ["string", "null"], maxLength: 2e3 }
5838
+ minLength: 1,
5839
+ maxLength: 2e3,
5840
+ pattern: "\\S"
5841
+ }
5470
5842
  }
5471
5843
  },
5472
5844
  confidence: { type: "number", minimum: 0, maximum: 1 },
@@ -5480,18 +5852,28 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
5480
5852
  type: "array",
5481
5853
  minItems: 1,
5482
5854
  uniqueItems: true,
5483
- items: { type: "string", minLength: 1 }
5855
+ items: { type: "string", minLength: 1, pattern: "\\S" }
5484
5856
  }
5485
5857
  }
5486
5858
  },
5487
- rationale: { type: ["string", "null"], maxLength: 4e3 },
5488
- validationPlan: { type: ["string", "null"], maxLength: 2e3 }
5859
+ rationale: {
5860
+ type: ["string", "null"],
5861
+ minLength: 1,
5862
+ maxLength: 4e3,
5863
+ pattern: "\\S"
5864
+ },
5865
+ validationPlan: {
5866
+ type: ["string", "null"],
5867
+ minLength: 1,
5868
+ maxLength: 2e3,
5869
+ pattern: "\\S"
5870
+ }
5489
5871
  }
5490
5872
  }
5491
5873
  }
5492
5874
  }
5493
5875
  };
5494
- function policyEditAuthorJsonSchema(maxItems, targetSurface, allowedJsonPaths, objectives) {
5876
+ function policyEditAuthorJsonSchema(maxItems, allowedJsonPaths) {
5495
5877
  const schema = JSON.parse(JSON.stringify(POLICY_EDIT_AUTHOR_JSON_SCHEMA));
5496
5878
  const properties = schema.properties;
5497
5879
  const edits = properties.edits;
@@ -5500,24 +5882,7 @@ function policyEditAuthorJsonSchema(maxItems, targetSurface, allowedJsonPaths, o
5500
5882
  const itemProperties = item.properties;
5501
5883
  const target = itemProperties.target;
5502
5884
  const targetProperties = target.properties;
5503
- targetProperties.surface = { type: "string", enum: [targetSurface] };
5504
5885
  targetProperties.path = { type: "string", enum: [...allowedJsonPaths] };
5505
- const change = itemProperties.change;
5506
- for (const variant of change.anyOf) {
5507
- const variantProperties = variant.properties;
5508
- variantProperties.path = { type: "string", enum: [...allowedJsonPaths] };
5509
- }
5510
- const expectedGain = itemProperties.expectedGain;
5511
- const gainProperties = expectedGain.properties;
5512
- gainProperties.metric = { type: "string", enum: objectives.map((objective) => objective.key) };
5513
- gainProperties.direction = {
5514
- type: "string",
5515
- enum: [...new Set(objectives.map((objective) => objective.direction))]
5516
- };
5517
- gainProperties.unit = {
5518
- type: "string",
5519
- enum: [...new Set(objectives.map((objective) => objective.unit))]
5520
- };
5521
5886
  return schema;
5522
5887
  }
5523
5888
  var DEFAULT_POLICY_EDIT_HISTORY_LIMITS = Object.freeze({
@@ -5531,14 +5896,21 @@ var POLICY_EDIT_AUTHOR_SYSTEM = [
5531
5896
  "You author strictly typed PolicyEdit candidates over one JSON surface.",
5532
5897
  'Return exactly one JSON object with shape {"edits":[...]}; emit an empty edits array when no evidence supports a change.',
5533
5898
  `axis must be one of: ${POLICY_EDIT_AXES.join(", ")}.`,
5534
- `target.surface must be one of: ${POLICY_EDIT_TARGET_SURFACES.join(", ")}.`,
5535
- "target.path and change.path must be the same caller-allowed JSON path.",
5536
- 'change must be exactly one operation: {"kind":"json","mode":"set","path":string,"value":json}, {"kind":"json","mode":"merge","path":string,"value":json}, or {"kind":"json","mode":"remove","path":string}.',
5899
+ "target.path must be one caller-allowed JSON path.",
5900
+ 'change must be exactly one operation: {"mode":"set","value":json}, {"mode":"merge","value":json}, or {"mode":"remove"}.',
5901
+ "The caller binds target surface, JSON operation kind and path, and objective metric, direction, and unit; do not emit those fields.",
5537
5902
  "Nullable fields required by the response schema must be null when they do not apply.",
5538
5903
  "Every edit must cite one or more supplied finding keys in source.findingKeys. Do not emit persistent finding IDs, analyst IDs, or evidence references; the caller binds those from the cited findings.",
5539
5904
  "Treat expectedGain and confidence as forecasts, never as measured evidence. Learn from baselineOutcome, incumbentOutcome, and observedDeltaFromParent.",
5540
5905
  "Do not invent a finding, path, field, score, or task fact. Do not include schemaVersion, editId, metadata, prose, or undeclared keys."
5541
5906
  ].join("\n");
5907
+ function policyEditAuthorSystem(responseSchema) {
5908
+ return [
5909
+ POLICY_EDIT_AUTHOR_SYSTEM,
5910
+ "The exact required response JSON Schema follows. Obey it even when the provider does not enforce response_format:",
5911
+ JSON.stringify(responseSchema)
5912
+ ].join("\n");
5913
+ }
5542
5914
  function llmPolicyEditProposer(opts) {
5543
5915
  const allowedJsonPaths = validateAllowedJsonPaths(opts.allowedJsonPaths);
5544
5916
  const allowedPathSet = new Set(allowedJsonPaths);
@@ -5593,6 +5965,12 @@ function llmPolicyEditProposer(opts) {
5593
5965
  { currentSurface, allowedJsonPaths, objectives, targetSurface: opts.targetSurface },
5594
5966
  scenarioIds
5595
5967
  );
5968
+ const modelSurface = redactCurrentSurfaceForModel(
5969
+ currentSurface,
5970
+ allowedJsonPaths,
5971
+ opts.redactCurrentSurfaceForModel
5972
+ );
5973
+ assertSurfaceIsTaskAgnostic(modelSurface, scenarioIds);
5596
5974
  const measuredSources = measuredSourceMeasurements(ctx);
5597
5975
  const findings = citableFindings(ctx.findings, measuredSources, maxFindings);
5598
5976
  const findingByKey = new Map(
@@ -5605,7 +5983,7 @@ function llmPolicyEditProposer(opts) {
5605
5983
  objectives,
5606
5984
  candidateCount: limit,
5607
5985
  generation: ctx.generation,
5608
- currentSurface,
5986
+ currentSurface: modelSurface,
5609
5987
  findings: findings.map(
5610
5988
  (finding, index) => renderFinding(finding, `finding-${index + 1}`, scenarioIds, measuredSources)
5611
5989
  ),
@@ -5627,21 +6005,17 @@ function llmPolicyEditProposer(opts) {
5627
6005
  objectiveByKey
5628
6006
  )
5629
6007
  };
5630
- const responseSchema = policyEditAuthorJsonSchema(
5631
- limit,
5632
- opts.targetSurface,
5633
- allowedJsonPaths,
5634
- objectives
5635
- );
6008
+ const responseSchema = policyEditAuthorJsonSchema(limit, allowedJsonPaths);
6009
+ const system = policyEditAuthorSystem(responseSchema);
5636
6010
  assertPolicyEditAuthorContextBudget(
5637
- { system: POLICY_EDIT_AUTHOR_SYSTEM, authorContext, responseSchema },
6011
+ { system, authorContext, responseSchema },
5638
6012
  maxAuthorContextChars
5639
6013
  );
5640
6014
  const userContent = JSON.stringify(authorContext);
5641
6015
  const request = {
5642
6016
  model: opts.model,
5643
6017
  messages: [
5644
- { role: "system", content: POLICY_EDIT_AUTHOR_SYSTEM },
6018
+ { role: "system", content: system },
5645
6019
  { role: "user", content: userContent }
5646
6020
  ],
5647
6021
  jsonSchema: {
@@ -5660,7 +6034,12 @@ function llmPolicyEditProposer(opts) {
5660
6034
  maximumCharge: maximumChargeForLlmRequest(request, opts.llm),
5661
6035
  tags: { generation: String(ctx.generation) },
5662
6036
  signal: ctx.signal,
5663
- execute: (signal, callId) => callLlmJson(request, { ...opts.llm, signal, idempotencyKey: callId }),
6037
+ execute: (signal, callId) => callLlmJson(request, {
6038
+ ...opts.llm,
6039
+ jsonPayloadMode: "exact",
6040
+ signal,
6041
+ idempotencyKey: callId
6042
+ }),
5664
6043
  receipt: ({ result }) => costReceiptFromLlm(result),
5665
6044
  receiptFromError: costReceiptFromLlmError
5666
6045
  });
@@ -5678,7 +6057,7 @@ function llmPolicyEditProposer(opts) {
5678
6057
  findingByKey,
5679
6058
  opts.targetSurface,
5680
6059
  allowedPathSet,
5681
- objectiveByKey,
6060
+ objectives[0],
5682
6061
  ctx.incumbentOutcome?.composite ?? ctx.baselineOutcome?.composite
5683
6062
  )
5684
6063
  );
@@ -6116,18 +6495,10 @@ function parseAuthorResponse(value) {
6116
6495
  const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
6117
6496
  throw new Error(`llmPolicyEditProposer: invalid PolicyEdit response: ${issues}`);
6118
6497
  }
6119
- function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, objectiveByKey, currentComposite) {
6120
- if (draft.target.surface !== targetSurface) {
6498
+ function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, objective, currentComposite) {
6499
+ if (!allowedPaths.has(draft.target.path)) {
6121
6500
  throw new Error(
6122
- `llmPolicyEditProposer: target surface '${draft.target.surface}' does not match '${targetSurface}'`
6123
- );
6124
- }
6125
- if (draft.target.path !== draft.change.path) {
6126
- throw new Error("llmPolicyEditProposer: target.path must equal change.path");
6127
- }
6128
- if (!allowedPaths.has(draft.change.path)) {
6129
- throw new Error(
6130
- `llmPolicyEditProposer: JSON path '${draft.change.path}' is outside allowedJsonPaths`
6501
+ `llmPolicyEditProposer: JSON path '${draft.target.path}' is outside allowedJsonPaths`
6131
6502
  );
6132
6503
  }
6133
6504
  const cited = draft.source.findingKeys.map((findingKey) => {
@@ -6143,22 +6514,6 @@ function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, obje
6143
6514
  if (evidenceRefs.length === 0) {
6144
6515
  throw new Error("llmPolicyEditProposer: authored edit has no cited evidence");
6145
6516
  }
6146
- const objective = objectiveByKey.get(draft.expectedGain.metric);
6147
- if (!objective) {
6148
- throw new Error(
6149
- `llmPolicyEditProposer: unknown forecast objective '${draft.expectedGain.metric}'`
6150
- );
6151
- }
6152
- if (draft.expectedGain.direction !== objective.direction) {
6153
- throw new Error(
6154
- `llmPolicyEditProposer: forecast direction for '${objective.key}' must be '${objective.direction}'`
6155
- );
6156
- }
6157
- if (draft.expectedGain.unit !== objective.unit) {
6158
- throw new Error(
6159
- `llmPolicyEditProposer: forecast unit for '${objective.key}' must be '${objective.unit}'`
6160
- );
6161
- }
6162
6517
  const maxGain = currentComposite === void 0 ? objective.scale.max - objective.scale.min : objective.scale.max - currentComposite;
6163
6518
  if (draft.expectedGain.amount > maxGain) {
6164
6519
  throw new Error(
@@ -6166,20 +6521,26 @@ function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, obje
6166
6521
  );
6167
6522
  }
6168
6523
  const expectedGain = {
6169
- metric: draft.expectedGain.metric,
6170
- direction: draft.expectedGain.direction,
6524
+ metric: objective.key,
6525
+ direction: objective.direction,
6171
6526
  amount: draft.expectedGain.amount,
6172
- ...draft.expectedGain.unit ? { unit: draft.expectedGain.unit } : {},
6527
+ unit: objective.unit,
6173
6528
  ...draft.expectedGain.rationale ? { rationale: draft.expectedGain.rationale } : {}
6174
6529
  };
6530
+ const change = draft.change.mode === "remove" ? { kind: "json", mode: "remove", path: draft.target.path } : {
6531
+ kind: "json",
6532
+ mode: draft.change.mode,
6533
+ path: draft.target.path,
6534
+ value: draft.change.value
6535
+ };
6175
6536
  const init = {
6176
6537
  axis: draft.axis,
6177
6538
  target: {
6178
- surface: draft.target.surface,
6539
+ surface: targetSurface,
6179
6540
  path: draft.target.path,
6180
6541
  ...draft.target.label ? { label: draft.target.label } : {}
6181
6542
  },
6182
- change: draft.change,
6543
+ change,
6183
6544
  claim: draft.claim,
6184
6545
  expectedGain,
6185
6546
  confidence: draft.confidence,
@@ -6209,6 +6570,65 @@ function parseJsonSurface(surface) {
6209
6570
  }
6210
6571
  return parsed;
6211
6572
  }
6573
+ function redactCurrentSurfaceForModel(surface, allowedJsonPaths, redact) {
6574
+ if (!redact) return surface;
6575
+ const redacted = redact(structuredClone(surface));
6576
+ const parsed = JsonValueSchema.safeParse(redacted);
6577
+ if (!parsed.success) {
6578
+ const detail = formatJsonValidationError(parsed.error);
6579
+ throw new Error(
6580
+ `llmPolicyEditProposer: redactCurrentSurfaceForModel returned invalid JSON (${detail})`
6581
+ );
6582
+ }
6583
+ if (!parsed.data || typeof parsed.data !== "object" || Array.isArray(parsed.data)) {
6584
+ throw new Error("llmPolicyEditProposer: redactCurrentSurfaceForModel must return a JSON object");
6585
+ }
6586
+ for (const path of allowedJsonPaths) {
6587
+ if (!jsonValuesEqual(readJsonPath(surface, path), readJsonPath(parsed.data, path))) {
6588
+ throw new Error(
6589
+ `llmPolicyEditProposer: redactCurrentSurfaceForModel must not change or hide editable JSON path '${path}'`
6590
+ );
6591
+ }
6592
+ }
6593
+ return parsed.data;
6594
+ }
6595
+ function formatJsonValidationError(error) {
6596
+ const messages = [...new Set(collectZodMessages(error.issues))];
6597
+ const informative = messages.filter(
6598
+ (message) => message !== "Invalid input" && message !== "Invalid key in record"
6599
+ );
6600
+ const custom = informative.filter((message) => !message.startsWith("Invalid input: expected"));
6601
+ return (custom.length > 0 ? custom : informative).join("; ") || "invalid JSON value";
6602
+ }
6603
+ function collectZodMessages(value) {
6604
+ if (Array.isArray(value)) return value.flatMap(collectZodMessages);
6605
+ if (!value || typeof value !== "object") return [];
6606
+ const issue = value;
6607
+ return [
6608
+ ...typeof issue.message === "string" ? [issue.message] : [],
6609
+ ...collectZodMessages(issue.errors),
6610
+ ...collectZodMessages(issue.issues)
6611
+ ];
6612
+ }
6613
+ function readJsonPath(root, path) {
6614
+ let cursor = root;
6615
+ for (const part of path.split(".").map((segment) => segment.trim()).filter(Boolean)) {
6616
+ if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) return void 0;
6617
+ cursor = cursor[part];
6618
+ }
6619
+ return cursor;
6620
+ }
6621
+ function jsonValuesEqual(left, right) {
6622
+ if (left === right) return true;
6623
+ if (left === void 0 || right === void 0 || left === null || right === null) return false;
6624
+ if (Array.isArray(left) || Array.isArray(right)) {
6625
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => jsonValuesEqual(value, right[index]));
6626
+ }
6627
+ if (typeof left !== "object" || typeof right !== "object") return false;
6628
+ const leftKeys = Object.keys(left);
6629
+ const rightKeys = Object.keys(right);
6630
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && jsonValuesEqual(left[key], right[key]));
6631
+ }
6212
6632
  function citableFindings(inputs, measuredSources, limit) {
6213
6633
  if (inputs.length === 0) {
6214
6634
  throw new Error("llmPolicyEditProposer: at least one analyst finding is required");
@@ -6438,6 +6858,33 @@ ${block}
6438
6858
  }
6439
6859
 
6440
6860
  // src/campaign/proposers/trace-analyst.ts
6861
+ async function analyzeOtlpTraceFile(opts) {
6862
+ if (!opts.apiKey) throw new Error("analyzeOtlpTraceFile: apiKey is required");
6863
+ if (!opts.model) throw new Error("analyzeOtlpTraceFile: model is required");
6864
+ const aiService = createAnalystAi({
6865
+ provider: opts.provider ?? "openai",
6866
+ apiKey: opts.apiKey,
6867
+ baseUrl: opts.baseUrl,
6868
+ model: opts.model
6869
+ });
6870
+ const registry = new AnalystRegistry();
6871
+ for (const spec of opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS) {
6872
+ registry.register(createTraceAnalystKind(spec, { ai: aiService, model: opts.model }));
6873
+ }
6874
+ const result = await registry.run(
6875
+ opts.runId,
6876
+ { traceStore: new OtlpFileTraceStore({ path: opts.tracePath }) },
6877
+ {
6878
+ signal: opts.signal ?? new AbortController().signal,
6879
+ chainFindings: true,
6880
+ ...opts.costLedger ? { costLedger: opts.costLedger } : {},
6881
+ ...opts.costPhase ? { costPhase: opts.costPhase } : {},
6882
+ ...opts.priorFindings === void 0 ? {} : { priorFindings: opts.priorFindings }
6883
+ }
6884
+ );
6885
+ if (result.findings.length === 0) throw noFindingsError(result);
6886
+ return result.findings;
6887
+ }
6441
6888
  function renderFindings(findings) {
6442
6889
  return findings.map((f, i) => {
6443
6890
  const action = f.recommended_action ? `
@@ -6456,30 +6903,20 @@ function traceAnalystProposer(opts) {
6456
6903
  }
6457
6904
  const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
6458
6905
  const produceFindings = opts.analyze ?? (async (path, c) => {
6459
- const aiService = createAnalystAi({
6906
+ const priorFindings = await opts.resolvePriorFindings?.(c);
6907
+ return analyzeOtlpTraceFile({
6908
+ tracePath: path,
6909
+ runId: `trace-analyst-gen-${c.generation}`,
6460
6910
  provider: opts.provider ?? "openai",
6461
6911
  apiKey: opts.apiKey,
6462
6912
  baseUrl: opts.baseUrl,
6463
- model: opts.model
6913
+ model: opts.model,
6914
+ kinds,
6915
+ signal: c.signal,
6916
+ costLedger: c.costLedger,
6917
+ costPhase: c.costPhase,
6918
+ ...priorFindings === void 0 ? {} : { priorFindings }
6464
6919
  });
6465
- const registry = new AnalystRegistry();
6466
- for (const spec of kinds) {
6467
- registry.register(createTraceAnalystKind(spec, { ai: aiService, model: opts.model }));
6468
- }
6469
- const priorFindings = await opts.resolvePriorFindings?.(c);
6470
- const result = await registry.run(
6471
- `trace-analyst-gen-${c.generation}`,
6472
- { traceStore: new OtlpFileTraceStore({ path }) },
6473
- {
6474
- signal: c.signal,
6475
- chainFindings: true,
6476
- costLedger: c.costLedger,
6477
- costPhase: c.costPhase,
6478
- ...priorFindings === void 0 ? {} : { priorFindings }
6479
- }
6480
- );
6481
- if (result.findings.length === 0) throw noFindingsError(result);
6482
- return result.findings;
6483
6920
  });
6484
6921
  return analysisEditProposer({
6485
6922
  kind: "trace-analyst",
@@ -8241,12 +8678,13 @@ export {
8241
8678
  skillOptProposer,
8242
8679
  SkillPatchParseError,
8243
8680
  parseSkillPatchResponse,
8681
+ compareOptimizationMethods,
8682
+ costFromLedgerSummary,
8244
8683
  runSkillOpt,
8245
- compareProposers,
8246
- gepaReflectionEntry,
8247
- gepaParetoEntry,
8248
- skillOptEntry,
8249
- fapoEscalationEntry,
8684
+ gepaReflectionMethod,
8685
+ gepaParetoMethod,
8686
+ skillOptMethod,
8687
+ fapoEscalationMethod,
8250
8688
  makePlaybackDispatch,
8251
8689
  scoreUserStory,
8252
8690
  userStoryScoreboard,
@@ -8265,6 +8703,7 @@ export {
8265
8703
  llmPolicyEditProposer,
8266
8704
  projectPolicyEditHistory,
8267
8705
  memoryCurationProposer,
8706
+ analyzeOtlpTraceFile,
8268
8707
  traceAnalystProposer,
8269
8708
  scoreDiscrimination,
8270
8709
  selectDiscriminative,
@@ -8279,4 +8718,4 @@ export {
8279
8718
  verifyCodeSurface,
8280
8719
  resolveWorktreePath
8281
8720
  };
8282
- //# sourceMappingURL=chunk-GS3FJGUF.js.map
8721
+ //# sourceMappingURL=chunk-22VO7T2I.js.map