@tangle-network/agent-eval 0.123.2 → 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.
- package/CHANGELOG.md +24 -0
- package/README.md +48 -4
- package/dist/analyst/index.d.ts +18 -3
- package/dist/analyst/index.js +5 -5
- package/dist/benchmarks/index.d.ts +10 -3
- package/dist/benchmarks/index.js +7 -7
- package/dist/campaign/index.d.ts +241 -221
- package/dist/campaign/index.js +20 -16
- package/dist/{chunk-RQP5UTK5.js → chunk-22VO7T2I.js} +689 -356
- package/dist/chunk-22VO7T2I.js.map +1 -0
- package/dist/{chunk-EBDOTTZJ.js → chunk-4KO6D2BO.js} +3 -3
- package/dist/{chunk-3FCG7FBV.js → chunk-D5JZ7UDZ.js} +2 -2
- package/dist/{chunk-BGVTIE2C.js → chunk-GRCDRKII.js} +13 -2
- package/dist/chunk-GRCDRKII.js.map +1 -0
- package/dist/{chunk-FHFTYX2Q.js → chunk-JKDNAOF5.js} +2 -2
- package/dist/{chunk-WXQTVEKM.js → chunk-MHPEGJHC.js} +3 -3
- package/dist/{chunk-J7S4YM27.js → chunk-MK3Q6GCL.js} +28 -14
- package/dist/chunk-MK3Q6GCL.js.map +1 -0
- package/dist/{chunk-DNVPOYUS.js → chunk-N7ZS6PEF.js} +2 -2
- package/dist/{chunk-4SOQ4ND2.js → chunk-QFQZ3U3X.js} +1 -1
- package/dist/chunk-QFQZ3U3X.js.map +1 -0
- package/dist/{chunk-WMJR67FX.js → chunk-QNVBALEX.js} +4 -4
- package/dist/{chunk-E3HAD4A3.js → chunk-SJT4OBVL.js} +2 -2
- package/dist/{chunk-HQY7LBV2.js → chunk-SUN7QLPB.js} +6 -6
- package/dist/{chunk-HQY7LBV2.js.map → chunk-SUN7QLPB.js.map} +1 -1
- package/dist/{chunk-5YMKIFYP.js → chunk-WP3GEXKI.js} +3 -3
- package/dist/cli.js +3 -3
- package/dist/contract/index.d.ts +18 -3
- package/dist/contract/index.js +7 -7
- package/dist/fuzz.d.ts +8 -1
- package/dist/fuzz.js +4 -4
- package/dist/index.d.ts +27 -9
- package/dist/index.js +14 -12
- package/dist/index.js.map +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/rl.d.ts +15 -2
- package/dist/rl.js +4 -2
- package/dist/rl.js.map +1 -1
- package/dist/{run-campaign-ZKR5MQMQ.js → run-campaign-I3JXKVAK.js} +3 -3
- package/dist/traces.d.ts +2 -2
- package/dist/traces.js +1 -1
- package/dist/wire/index.d.ts +16 -1
- package/dist/wire/index.js +3 -3
- package/docs/campaign-proposers.md +142 -131
- package/docs/concepts.md +30 -34
- package/docs/design/loop-taxonomy.md +2 -3
- package/docs/design.md +0 -1
- package/docs/multi-shot-optimization.md +41 -49
- package/package.json +1 -1
- package/dist/chunk-4SOQ4ND2.js.map +0 -1
- package/dist/chunk-BGVTIE2C.js.map +0 -1
- package/dist/chunk-J7S4YM27.js.map +0 -1
- package/dist/chunk-RQP5UTK5.js.map +0 -1
- package/docs/improvement-glossary.md +0 -204
- package/docs/self-improvement-map.md +0 -115
- /package/dist/{chunk-EBDOTTZJ.js.map → chunk-4KO6D2BO.js.map} +0 -0
- /package/dist/{chunk-3FCG7FBV.js.map → chunk-D5JZ7UDZ.js.map} +0 -0
- /package/dist/{chunk-FHFTYX2Q.js.map → chunk-JKDNAOF5.js.map} +0 -0
- /package/dist/{chunk-WXQTVEKM.js.map → chunk-MHPEGJHC.js.map} +0 -0
- /package/dist/{chunk-DNVPOYUS.js.map → chunk-N7ZS6PEF.js.map} +0 -0
- /package/dist/{chunk-WMJR67FX.js.map → chunk-QNVBALEX.js.map} +0 -0
- /package/dist/{chunk-E3HAD4A3.js.map → chunk-SJT4OBVL.js.map} +0 -0
- /package/dist/{chunk-5YMKIFYP.js.map → chunk-WP3GEXKI.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
73
|
+
} from "./chunk-GRCDRKII.js";
|
|
74
74
|
import {
|
|
75
75
|
analyzeTraces
|
|
76
76
|
} from "./chunk-NUKSVU3W.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
|
-
|
|
3410
|
-
if (
|
|
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
|
|
3417
|
-
const overlap = opts.trainScenarios.filter((s) =>
|
|
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
|
|
3852
|
+
`runSkillOpt: trainScenarios and selectionScenarios must be disjoint (overlap: [${overlap.join(
|
|
3421
3853
|
", "
|
|
3422
|
-
)}]) \u2014 a shared scenario leaks the
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3881
|
+
runDir,
|
|
3442
3882
|
costCeilingUsd: opts.costCeiling
|
|
3443
3883
|
});
|
|
3444
|
-
const
|
|
3445
|
-
const campaign = await runScoringCampaign(
|
|
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(
|
|
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
|
|
3456
|
-
let
|
|
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 <
|
|
3918
|
+
for (let epoch = 0; epoch < maxEpochs; epoch++) {
|
|
3466
3919
|
epochsRun++;
|
|
3467
|
-
const
|
|
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
|
|
3492
|
-
|
|
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
|
-
|
|
3957
|
+
selectionDelta: candidateSelection - currentSelection
|
|
3498
3958
|
};
|
|
3499
3959
|
current = candidate;
|
|
3500
|
-
|
|
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: `
|
|
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
|
-
|
|
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
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
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: `${
|
|
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
|
|
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/
|
|
3580
|
-
|
|
3581
|
-
return
|
|
4054
|
+
// src/campaign/presets/builtin-optimization-methods.ts
|
|
4055
|
+
function gepaReflectionMethod(config, name = "gepa-reflection") {
|
|
4056
|
+
return gepaMethod(config, false, name);
|
|
3582
4057
|
}
|
|
3583
|
-
|
|
3584
|
-
|
|
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 };
|
|
3676
|
-
}
|
|
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);
|
|
4058
|
+
function gepaParetoMethod(config, name = "gepa-pareto") {
|
|
4059
|
+
return gepaMethod(config, true, name);
|
|
3685
4060
|
}
|
|
3686
|
-
function
|
|
3687
|
-
return
|
|
3688
|
-
|
|
3689
|
-
function gepaEntry(config, combineParents, name) {
|
|
3690
|
-
return {
|
|
4061
|
+
function gepaMethod(config, combineParents, name) {
|
|
4062
|
+
return improvementLoopMethod(
|
|
4063
|
+
config,
|
|
3691
4064
|
name,
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
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
|
|
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
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4088
|
+
...input.runOptions,
|
|
4089
|
+
...config.runOptions ?? {},
|
|
4090
|
+
baselineSurface: input.baselineSurface,
|
|
4091
|
+
dispatchWithSurface: input.dispatchWithSurface,
|
|
4092
|
+
judges: [...input.judges],
|
|
3746
4093
|
proposer,
|
|
3747
|
-
trainScenarios:
|
|
3748
|
-
|
|
4094
|
+
trainScenarios: [...input.trainScenarios],
|
|
4095
|
+
selectionScenarios: [...input.selectionScenarios],
|
|
3749
4096
|
maxEpochs: config.maxEpochs ?? 6,
|
|
3750
|
-
runDir: `${
|
|
3751
|
-
|
|
4097
|
+
runDir: `${input.runDir}/loop`,
|
|
4098
|
+
seed: config.seed ?? input.seed
|
|
3752
4099
|
});
|
|
3753
4100
|
return {
|
|
3754
4101
|
winnerSurface: result.winnerSurface,
|
|
3755
|
-
|
|
4102
|
+
cost: costFromLedgerSummary(result.cost),
|
|
3756
4103
|
durationMs: Date.now() - started
|
|
3757
4104
|
};
|
|
3758
4105
|
}
|
|
3759
4106
|
};
|
|
3760
4107
|
}
|
|
3761
|
-
function
|
|
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
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
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:
|
|
4143
|
+
holdoutScenarios: [...input.selectionScenarios],
|
|
3792
4144
|
deltaThreshold: 0
|
|
3793
4145
|
}),
|
|
3794
4146
|
autoOnPromote: "none",
|
|
3795
|
-
runDir: `${
|
|
3796
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
4471
|
+
eventId: randomUUID2(),
|
|
4120
4472
|
provider: "correctness-checker",
|
|
4121
4473
|
model,
|
|
4122
4474
|
endpoint: "/chat",
|
|
@@ -5284,7 +5636,7 @@ function coerceCandidateSurface(surface) {
|
|
|
5284
5636
|
|
|
5285
5637
|
// src/campaign/proposers/policy-edit-author-context.ts
|
|
5286
5638
|
function selectPolicyEditAuthorRows(rows, options) {
|
|
5287
|
-
|
|
5639
|
+
assertPositiveSafeInteger2(options.limit, "limit");
|
|
5288
5640
|
const unique2 = /* @__PURE__ */ new Map();
|
|
5289
5641
|
for (const row of rows) {
|
|
5290
5642
|
if (!row.scenarioId || row.scenarioId.trim() !== row.scenarioId) {
|
|
@@ -5328,7 +5680,7 @@ function selectPolicyEditAuthorRows(rows, options) {
|
|
|
5328
5680
|
return selected;
|
|
5329
5681
|
}
|
|
5330
5682
|
function assertPolicyEditAuthorContextBudget(value, maxChars) {
|
|
5331
|
-
|
|
5683
|
+
assertPositiveSafeInteger2(maxChars, "maxChars");
|
|
5332
5684
|
const json = JSON.stringify(value);
|
|
5333
5685
|
if (json === void 0) {
|
|
5334
5686
|
throw new Error("assertPolicyEditAuthorContextBudget: value must serialize to JSON");
|
|
@@ -5344,7 +5696,7 @@ function assertPolicyEditAuthorContextBudget(value, maxChars) {
|
|
|
5344
5696
|
function compareScenarioId(a, b) {
|
|
5345
5697
|
return a.scenarioId < b.scenarioId ? -1 : a.scenarioId > b.scenarioId ? 1 : 0;
|
|
5346
5698
|
}
|
|
5347
|
-
function
|
|
5699
|
+
function assertPositiveSafeInteger2(value, name) {
|
|
5348
5700
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
5349
5701
|
throw new Error(`${name} must be a positive safe integer (got ${value})`);
|
|
5350
5702
|
}
|
|
@@ -5372,37 +5724,27 @@ var JsonValueSchema = z.lazy(
|
|
|
5372
5724
|
);
|
|
5373
5725
|
var AuthoredJsonChangeSchema = z.discriminatedUnion("mode", [
|
|
5374
5726
|
z.object({
|
|
5375
|
-
kind: z.literal("json"),
|
|
5376
5727
|
mode: z.literal("set"),
|
|
5377
|
-
path: NonEmptyStringSchema,
|
|
5378
5728
|
value: JsonValueSchema
|
|
5379
5729
|
}).strict(),
|
|
5380
5730
|
z.object({
|
|
5381
|
-
kind: z.literal("json"),
|
|
5382
5731
|
mode: z.literal("merge"),
|
|
5383
|
-
path: NonEmptyStringSchema,
|
|
5384
5732
|
value: JsonValueSchema
|
|
5385
5733
|
}).strict(),
|
|
5386
5734
|
z.object({
|
|
5387
|
-
|
|
5388
|
-
mode: z.literal("remove"),
|
|
5389
|
-
path: NonEmptyStringSchema
|
|
5735
|
+
mode: z.literal("remove")
|
|
5390
5736
|
}).strict()
|
|
5391
5737
|
]);
|
|
5392
5738
|
var AuthoredPolicyEditSchema = z.object({
|
|
5393
5739
|
axis: z.enum(POLICY_EDIT_AXES),
|
|
5394
5740
|
target: z.object({
|
|
5395
|
-
surface: z.enum(POLICY_EDIT_TARGET_SURFACES),
|
|
5396
5741
|
path: NonEmptyStringSchema,
|
|
5397
5742
|
label: NonEmptyStringSchema.max(200).nullable()
|
|
5398
5743
|
}).strict(),
|
|
5399
5744
|
change: AuthoredJsonChangeSchema,
|
|
5400
5745
|
claim: NonEmptyStringSchema.max(2e3),
|
|
5401
5746
|
expectedGain: z.object({
|
|
5402
|
-
metric: NonEmptyStringSchema.max(400),
|
|
5403
|
-
direction: z.enum(["increase", "decrease"]),
|
|
5404
5747
|
amount: z.number().finite().positive(),
|
|
5405
|
-
unit: z.enum(["absolute", "relative", "percent", "score"]).nullable(),
|
|
5406
5748
|
rationale: NonEmptyStringSchema.max(2e3).nullable()
|
|
5407
5749
|
}).strict(),
|
|
5408
5750
|
confidence: z.number().finite().min(0).max(1),
|
|
@@ -5443,11 +5785,15 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
|
|
|
5443
5785
|
target: {
|
|
5444
5786
|
type: "object",
|
|
5445
5787
|
additionalProperties: false,
|
|
5446
|
-
required: ["
|
|
5788
|
+
required: ["path", "label"],
|
|
5447
5789
|
properties: {
|
|
5448
|
-
surface: { type: "string", enum: [...POLICY_EDIT_TARGET_SURFACES] },
|
|
5449
5790
|
path: { type: "string", minLength: 1 },
|
|
5450
|
-
label: {
|
|
5791
|
+
label: {
|
|
5792
|
+
type: ["string", "null"],
|
|
5793
|
+
minLength: 1,
|
|
5794
|
+
maxLength: 200,
|
|
5795
|
+
pattern: "\\S"
|
|
5796
|
+
}
|
|
5451
5797
|
}
|
|
5452
5798
|
},
|
|
5453
5799
|
change: {
|
|
@@ -5455,51 +5801,44 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
|
|
|
5455
5801
|
{
|
|
5456
5802
|
type: "object",
|
|
5457
5803
|
additionalProperties: false,
|
|
5458
|
-
required: ["
|
|
5804
|
+
required: ["mode", "value"],
|
|
5459
5805
|
properties: {
|
|
5460
|
-
kind: { const: "json" },
|
|
5461
5806
|
mode: { const: "set" },
|
|
5462
|
-
path: { type: "string", minLength: 1 },
|
|
5463
5807
|
value: {}
|
|
5464
5808
|
}
|
|
5465
5809
|
},
|
|
5466
5810
|
{
|
|
5467
5811
|
type: "object",
|
|
5468
5812
|
additionalProperties: false,
|
|
5469
|
-
required: ["
|
|
5813
|
+
required: ["mode", "value"],
|
|
5470
5814
|
properties: {
|
|
5471
|
-
kind: { const: "json" },
|
|
5472
5815
|
mode: { const: "merge" },
|
|
5473
|
-
path: { type: "string", minLength: 1 },
|
|
5474
5816
|
value: {}
|
|
5475
5817
|
}
|
|
5476
5818
|
},
|
|
5477
5819
|
{
|
|
5478
5820
|
type: "object",
|
|
5479
5821
|
additionalProperties: false,
|
|
5480
|
-
required: ["
|
|
5822
|
+
required: ["mode"],
|
|
5481
5823
|
properties: {
|
|
5482
|
-
|
|
5483
|
-
mode: { const: "remove" },
|
|
5484
|
-
path: { type: "string", minLength: 1 }
|
|
5824
|
+
mode: { const: "remove" }
|
|
5485
5825
|
}
|
|
5486
5826
|
}
|
|
5487
5827
|
]
|
|
5488
5828
|
},
|
|
5489
|
-
claim: { type: "string", minLength: 1, maxLength: 2e3 },
|
|
5829
|
+
claim: { type: "string", minLength: 1, maxLength: 2e3, pattern: "\\S" },
|
|
5490
5830
|
expectedGain: {
|
|
5491
5831
|
type: "object",
|
|
5492
5832
|
additionalProperties: false,
|
|
5493
|
-
required: ["
|
|
5833
|
+
required: ["amount", "rationale"],
|
|
5494
5834
|
properties: {
|
|
5495
|
-
metric: { type: "string", minLength: 1, maxLength: 400 },
|
|
5496
|
-
direction: { type: "string", enum: ["increase", "decrease"] },
|
|
5497
5835
|
amount: { type: "number", exclusiveMinimum: 0 },
|
|
5498
|
-
|
|
5836
|
+
rationale: {
|
|
5499
5837
|
type: ["string", "null"],
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5838
|
+
minLength: 1,
|
|
5839
|
+
maxLength: 2e3,
|
|
5840
|
+
pattern: "\\S"
|
|
5841
|
+
}
|
|
5503
5842
|
}
|
|
5504
5843
|
},
|
|
5505
5844
|
confidence: { type: "number", minimum: 0, maximum: 1 },
|
|
@@ -5513,18 +5852,28 @@ var POLICY_EDIT_AUTHOR_JSON_SCHEMA = {
|
|
|
5513
5852
|
type: "array",
|
|
5514
5853
|
minItems: 1,
|
|
5515
5854
|
uniqueItems: true,
|
|
5516
|
-
items: { type: "string", minLength: 1 }
|
|
5855
|
+
items: { type: "string", minLength: 1, pattern: "\\S" }
|
|
5517
5856
|
}
|
|
5518
5857
|
}
|
|
5519
5858
|
},
|
|
5520
|
-
rationale: {
|
|
5521
|
-
|
|
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
|
+
}
|
|
5522
5871
|
}
|
|
5523
5872
|
}
|
|
5524
5873
|
}
|
|
5525
5874
|
}
|
|
5526
5875
|
};
|
|
5527
|
-
function policyEditAuthorJsonSchema(maxItems,
|
|
5876
|
+
function policyEditAuthorJsonSchema(maxItems, allowedJsonPaths) {
|
|
5528
5877
|
const schema = JSON.parse(JSON.stringify(POLICY_EDIT_AUTHOR_JSON_SCHEMA));
|
|
5529
5878
|
const properties = schema.properties;
|
|
5530
5879
|
const edits = properties.edits;
|
|
@@ -5533,24 +5882,7 @@ function policyEditAuthorJsonSchema(maxItems, targetSurface, allowedJsonPaths, o
|
|
|
5533
5882
|
const itemProperties = item.properties;
|
|
5534
5883
|
const target = itemProperties.target;
|
|
5535
5884
|
const targetProperties = target.properties;
|
|
5536
|
-
targetProperties.surface = { type: "string", enum: [targetSurface] };
|
|
5537
5885
|
targetProperties.path = { type: "string", enum: [...allowedJsonPaths] };
|
|
5538
|
-
const change = itemProperties.change;
|
|
5539
|
-
for (const variant of change.anyOf) {
|
|
5540
|
-
const variantProperties = variant.properties;
|
|
5541
|
-
variantProperties.path = { type: "string", enum: [...allowedJsonPaths] };
|
|
5542
|
-
}
|
|
5543
|
-
const expectedGain = itemProperties.expectedGain;
|
|
5544
|
-
const gainProperties = expectedGain.properties;
|
|
5545
|
-
gainProperties.metric = { type: "string", enum: objectives.map((objective) => objective.key) };
|
|
5546
|
-
gainProperties.direction = {
|
|
5547
|
-
type: "string",
|
|
5548
|
-
enum: [...new Set(objectives.map((objective) => objective.direction))]
|
|
5549
|
-
};
|
|
5550
|
-
gainProperties.unit = {
|
|
5551
|
-
type: "string",
|
|
5552
|
-
enum: [...new Set(objectives.map((objective) => objective.unit))]
|
|
5553
|
-
};
|
|
5554
5886
|
return schema;
|
|
5555
5887
|
}
|
|
5556
5888
|
var DEFAULT_POLICY_EDIT_HISTORY_LIMITS = Object.freeze({
|
|
@@ -5564,9 +5896,9 @@ var POLICY_EDIT_AUTHOR_SYSTEM = [
|
|
|
5564
5896
|
"You author strictly typed PolicyEdit candidates over one JSON surface.",
|
|
5565
5897
|
'Return exactly one JSON object with shape {"edits":[...]}; emit an empty edits array when no evidence supports a change.',
|
|
5566
5898
|
`axis must be one of: ${POLICY_EDIT_AXES.join(", ")}.`,
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
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.",
|
|
5570
5902
|
"Nullable fields required by the response schema must be null when they do not apply.",
|
|
5571
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.",
|
|
5572
5904
|
"Treat expectedGain and confidence as forecasts, never as measured evidence. Learn from baselineOutcome, incumbentOutcome, and observedDeltaFromParent.",
|
|
@@ -5673,12 +6005,7 @@ function llmPolicyEditProposer(opts) {
|
|
|
5673
6005
|
objectiveByKey
|
|
5674
6006
|
)
|
|
5675
6007
|
};
|
|
5676
|
-
const responseSchema = policyEditAuthorJsonSchema(
|
|
5677
|
-
limit,
|
|
5678
|
-
opts.targetSurface,
|
|
5679
|
-
allowedJsonPaths,
|
|
5680
|
-
objectives
|
|
5681
|
-
);
|
|
6008
|
+
const responseSchema = policyEditAuthorJsonSchema(limit, allowedJsonPaths);
|
|
5682
6009
|
const system = policyEditAuthorSystem(responseSchema);
|
|
5683
6010
|
assertPolicyEditAuthorContextBudget(
|
|
5684
6011
|
{ system, authorContext, responseSchema },
|
|
@@ -5707,7 +6034,12 @@ function llmPolicyEditProposer(opts) {
|
|
|
5707
6034
|
maximumCharge: maximumChargeForLlmRequest(request, opts.llm),
|
|
5708
6035
|
tags: { generation: String(ctx.generation) },
|
|
5709
6036
|
signal: ctx.signal,
|
|
5710
|
-
execute: (signal, callId) => callLlmJson(request, {
|
|
6037
|
+
execute: (signal, callId) => callLlmJson(request, {
|
|
6038
|
+
...opts.llm,
|
|
6039
|
+
jsonPayloadMode: "exact",
|
|
6040
|
+
signal,
|
|
6041
|
+
idempotencyKey: callId
|
|
6042
|
+
}),
|
|
5711
6043
|
receipt: ({ result }) => costReceiptFromLlm(result),
|
|
5712
6044
|
receiptFromError: costReceiptFromLlmError
|
|
5713
6045
|
});
|
|
@@ -5725,7 +6057,7 @@ function llmPolicyEditProposer(opts) {
|
|
|
5725
6057
|
findingByKey,
|
|
5726
6058
|
opts.targetSurface,
|
|
5727
6059
|
allowedPathSet,
|
|
5728
|
-
|
|
6060
|
+
objectives[0],
|
|
5729
6061
|
ctx.incumbentOutcome?.composite ?? ctx.baselineOutcome?.composite
|
|
5730
6062
|
)
|
|
5731
6063
|
);
|
|
@@ -6163,18 +6495,10 @@ function parseAuthorResponse(value) {
|
|
|
6163
6495
|
const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
6164
6496
|
throw new Error(`llmPolicyEditProposer: invalid PolicyEdit response: ${issues}`);
|
|
6165
6497
|
}
|
|
6166
|
-
function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths,
|
|
6167
|
-
if (draft.target.
|
|
6498
|
+
function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, objective, currentComposite) {
|
|
6499
|
+
if (!allowedPaths.has(draft.target.path)) {
|
|
6168
6500
|
throw new Error(
|
|
6169
|
-
`llmPolicyEditProposer:
|
|
6170
|
-
);
|
|
6171
|
-
}
|
|
6172
|
-
if (draft.target.path !== draft.change.path) {
|
|
6173
|
-
throw new Error("llmPolicyEditProposer: target.path must equal change.path");
|
|
6174
|
-
}
|
|
6175
|
-
if (!allowedPaths.has(draft.change.path)) {
|
|
6176
|
-
throw new Error(
|
|
6177
|
-
`llmPolicyEditProposer: JSON path '${draft.change.path}' is outside allowedJsonPaths`
|
|
6501
|
+
`llmPolicyEditProposer: JSON path '${draft.target.path}' is outside allowedJsonPaths`
|
|
6178
6502
|
);
|
|
6179
6503
|
}
|
|
6180
6504
|
const cited = draft.source.findingKeys.map((findingKey) => {
|
|
@@ -6190,22 +6514,6 @@ function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, obje
|
|
|
6190
6514
|
if (evidenceRefs.length === 0) {
|
|
6191
6515
|
throw new Error("llmPolicyEditProposer: authored edit has no cited evidence");
|
|
6192
6516
|
}
|
|
6193
|
-
const objective = objectiveByKey.get(draft.expectedGain.metric);
|
|
6194
|
-
if (!objective) {
|
|
6195
|
-
throw new Error(
|
|
6196
|
-
`llmPolicyEditProposer: unknown forecast objective '${draft.expectedGain.metric}'`
|
|
6197
|
-
);
|
|
6198
|
-
}
|
|
6199
|
-
if (draft.expectedGain.direction !== objective.direction) {
|
|
6200
|
-
throw new Error(
|
|
6201
|
-
`llmPolicyEditProposer: forecast direction for '${objective.key}' must be '${objective.direction}'`
|
|
6202
|
-
);
|
|
6203
|
-
}
|
|
6204
|
-
if (draft.expectedGain.unit !== objective.unit) {
|
|
6205
|
-
throw new Error(
|
|
6206
|
-
`llmPolicyEditProposer: forecast unit for '${objective.key}' must be '${objective.unit}'`
|
|
6207
|
-
);
|
|
6208
|
-
}
|
|
6209
6517
|
const maxGain = currentComposite === void 0 ? objective.scale.max - objective.scale.min : objective.scale.max - currentComposite;
|
|
6210
6518
|
if (draft.expectedGain.amount > maxGain) {
|
|
6211
6519
|
throw new Error(
|
|
@@ -6213,20 +6521,26 @@ function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, obje
|
|
|
6213
6521
|
);
|
|
6214
6522
|
}
|
|
6215
6523
|
const expectedGain = {
|
|
6216
|
-
metric:
|
|
6217
|
-
direction:
|
|
6524
|
+
metric: objective.key,
|
|
6525
|
+
direction: objective.direction,
|
|
6218
6526
|
amount: draft.expectedGain.amount,
|
|
6219
|
-
|
|
6527
|
+
unit: objective.unit,
|
|
6220
6528
|
...draft.expectedGain.rationale ? { rationale: draft.expectedGain.rationale } : {}
|
|
6221
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
|
+
};
|
|
6222
6536
|
const init = {
|
|
6223
6537
|
axis: draft.axis,
|
|
6224
6538
|
target: {
|
|
6225
|
-
surface:
|
|
6539
|
+
surface: targetSurface,
|
|
6226
6540
|
path: draft.target.path,
|
|
6227
6541
|
...draft.target.label ? { label: draft.target.label } : {}
|
|
6228
6542
|
},
|
|
6229
|
-
change
|
|
6543
|
+
change,
|
|
6230
6544
|
claim: draft.claim,
|
|
6231
6545
|
expectedGain,
|
|
6232
6546
|
confidence: draft.confidence,
|
|
@@ -6544,6 +6858,33 @@ ${block}
|
|
|
6544
6858
|
}
|
|
6545
6859
|
|
|
6546
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
|
+
}
|
|
6547
6888
|
function renderFindings(findings) {
|
|
6548
6889
|
return findings.map((f, i) => {
|
|
6549
6890
|
const action = f.recommended_action ? `
|
|
@@ -6562,30 +6903,20 @@ function traceAnalystProposer(opts) {
|
|
|
6562
6903
|
}
|
|
6563
6904
|
const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
|
|
6564
6905
|
const produceFindings = opts.analyze ?? (async (path, c) => {
|
|
6565
|
-
const
|
|
6906
|
+
const priorFindings = await opts.resolvePriorFindings?.(c);
|
|
6907
|
+
return analyzeOtlpTraceFile({
|
|
6908
|
+
tracePath: path,
|
|
6909
|
+
runId: `trace-analyst-gen-${c.generation}`,
|
|
6566
6910
|
provider: opts.provider ?? "openai",
|
|
6567
6911
|
apiKey: opts.apiKey,
|
|
6568
6912
|
baseUrl: opts.baseUrl,
|
|
6569
|
-
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 }
|
|
6570
6919
|
});
|
|
6571
|
-
const registry = new AnalystRegistry();
|
|
6572
|
-
for (const spec of kinds) {
|
|
6573
|
-
registry.register(createTraceAnalystKind(spec, { ai: aiService, model: opts.model }));
|
|
6574
|
-
}
|
|
6575
|
-
const priorFindings = await opts.resolvePriorFindings?.(c);
|
|
6576
|
-
const result = await registry.run(
|
|
6577
|
-
`trace-analyst-gen-${c.generation}`,
|
|
6578
|
-
{ traceStore: new OtlpFileTraceStore({ path }) },
|
|
6579
|
-
{
|
|
6580
|
-
signal: c.signal,
|
|
6581
|
-
chainFindings: true,
|
|
6582
|
-
costLedger: c.costLedger,
|
|
6583
|
-
costPhase: c.costPhase,
|
|
6584
|
-
...priorFindings === void 0 ? {} : { priorFindings }
|
|
6585
|
-
}
|
|
6586
|
-
);
|
|
6587
|
-
if (result.findings.length === 0) throw noFindingsError(result);
|
|
6588
|
-
return result.findings;
|
|
6589
6920
|
});
|
|
6590
6921
|
return analysisEditProposer({
|
|
6591
6922
|
kind: "trace-analyst",
|
|
@@ -8347,12 +8678,13 @@ export {
|
|
|
8347
8678
|
skillOptProposer,
|
|
8348
8679
|
SkillPatchParseError,
|
|
8349
8680
|
parseSkillPatchResponse,
|
|
8681
|
+
compareOptimizationMethods,
|
|
8682
|
+
costFromLedgerSummary,
|
|
8350
8683
|
runSkillOpt,
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
fapoEscalationEntry,
|
|
8684
|
+
gepaReflectionMethod,
|
|
8685
|
+
gepaParetoMethod,
|
|
8686
|
+
skillOptMethod,
|
|
8687
|
+
fapoEscalationMethod,
|
|
8356
8688
|
makePlaybackDispatch,
|
|
8357
8689
|
scoreUserStory,
|
|
8358
8690
|
userStoryScoreboard,
|
|
@@ -8371,6 +8703,7 @@ export {
|
|
|
8371
8703
|
llmPolicyEditProposer,
|
|
8372
8704
|
projectPolicyEditHistory,
|
|
8373
8705
|
memoryCurationProposer,
|
|
8706
|
+
analyzeOtlpTraceFile,
|
|
8374
8707
|
traceAnalystProposer,
|
|
8375
8708
|
scoreDiscrimination,
|
|
8376
8709
|
selectDiscriminative,
|
|
@@ -8385,4 +8718,4 @@ export {
|
|
|
8385
8718
|
verifyCodeSurface,
|
|
8386
8719
|
resolveWorktreePath
|
|
8387
8720
|
};
|
|
8388
|
-
//# sourceMappingURL=chunk-
|
|
8721
|
+
//# sourceMappingURL=chunk-22VO7T2I.js.map
|