@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.
- package/CHANGELOG.md +29 -0
- package/README.md +191 -157
- package/dist/analyst/index.d.ts +27 -4
- package/dist/analyst/index.js +6 -6
- package/dist/authenticity/index.js +3 -2
- package/dist/authenticity/index.js.map +1 -1
- package/dist/benchmarks/index.d.ts +12 -4
- package/dist/benchmarks/index.js +9 -9
- package/dist/campaign/index.d.ts +270 -254
- package/dist/campaign/index.js +22 -18
- package/dist/{chunk-GS3FJGUF.js → chunk-22VO7T2I.js} +805 -366
- package/dist/chunk-22VO7T2I.js.map +1 -0
- package/dist/{chunk-LBAHQOBI.js → chunk-4KO6D2BO.js} +38 -12
- package/dist/chunk-4KO6D2BO.js.map +1 -0
- package/dist/{chunk-3FCG7FBV.js → chunk-D5JZ7UDZ.js} +2 -2
- package/dist/{chunk-XJYR7XFV.js → chunk-GC4ATIKK.js} +1 -1
- package/dist/chunk-GC4ATIKK.js.map +1 -0
- 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-S3UZOQ5Y.js → chunk-LOW3U7JZ.js} +2 -2
- package/dist/{chunk-FC5NDO3E.js → chunk-MHPEGJHC.js} +4 -4
- package/dist/{chunk-NJC7U437.js → chunk-MK3Q6GCL.js} +33 -18
- package/dist/chunk-MK3Q6GCL.js.map +1 -0
- package/dist/{chunk-U5CHZ5M3.js → chunk-N7ZS6PEF.js} +4 -4
- package/dist/{chunk-A5S77LSE.js → chunk-QFQZ3U3X.js} +2 -2
- package/dist/chunk-QFQZ3U3X.js.map +1 -0
- package/dist/{chunk-G2GPNLSX.js → chunk-QNVBALEX.js} +4 -4
- package/dist/{chunk-OYZAPX5G.js → chunk-R226UZOI.js} +2 -2
- package/dist/{chunk-6WX7CBAR.js → chunk-SJT4OBVL.js} +20 -9
- package/dist/chunk-SJT4OBVL.js.map +1 -0
- package/dist/{chunk-HZJF4IUO.js → chunk-SUN7QLPB.js} +6 -6
- package/dist/{chunk-HZJF4IUO.js.map → chunk-SUN7QLPB.js.map} +1 -1
- package/dist/{chunk-VJ7T5WIO.js → chunk-WP3GEXKI.js} +3 -3
- package/dist/cli.js +101 -11
- package/dist/cli.js.map +1 -1
- package/dist/contract/index.d.ts +115 -8
- package/dist/contract/index.js +11 -9
- package/dist/contract/index.js.map +1 -1
- package/dist/control.js +3 -3
- package/dist/fuzz.d.ts +8 -1
- package/dist/fuzz.js +7 -6
- package/dist/fuzz.js.map +1 -1
- package/dist/hosted/index.d.ts +8 -2
- package/dist/index.d.ts +36 -10
- package/dist/index.js +18 -16
- 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 +24 -3
- package/dist/rl.js +6 -4
- package/dist/rl.js.map +1 -1
- package/dist/{run-campaign-ZKR5MQMQ.js → run-campaign-I3JXKVAK.js} +3 -3
- package/dist/storyboard/index.js +1 -1
- package/dist/storyboard/index.js.map +1 -1
- package/dist/traces.d.ts +2 -2
- package/dist/traces.js +3 -3
- package/dist/wire/index.d.ts +77 -5
- package/dist/wire/index.js +3 -3
- package/docs/adapters-observability.md +6 -6
- package/docs/building-doctrine.md +5 -5
- package/docs/campaign-proposers.md +142 -131
- package/docs/concepts.md +49 -53
- package/docs/customer-journeys.md +80 -155
- package/docs/design/loop-taxonomy.md +27 -29
- package/docs/design.md +69 -0
- package/docs/distributed-driver.md +14 -14
- package/docs/eval-surface-map.md +11 -11
- package/docs/hosted-ingest-spec.md +4 -4
- package/docs/insight-report.md +32 -27
- package/docs/multi-shot-optimization.md +41 -49
- package/docs/research-report-methodology.md +9 -9
- package/docs/trace-analysis.md +2 -2
- package/docs/wire-protocol.md +16 -16
- package/package.json +2 -1
- package/dist/chunk-6WX7CBAR.js.map +0 -1
- package/dist/chunk-A5S77LSE.js.map +0 -1
- package/dist/chunk-BGVTIE2C.js.map +0 -1
- package/dist/chunk-GS3FJGUF.js.map +0 -1
- package/dist/chunk-LBAHQOBI.js.map +0 -1
- package/dist/chunk-NJC7U437.js.map +0 -1
- package/dist/chunk-XJYR7XFV.js.map +0 -1
- package/docs/auto-research-loop-end-to-end.md +0 -186
- package/docs/improvement-glossary.md +0 -204
- package/docs/self-improvement-map.md +0 -115
- /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-S3UZOQ5Y.js.map → chunk-LOW3U7JZ.js.map} +0 -0
- /package/dist/{chunk-FC5NDO3E.js.map → chunk-MHPEGJHC.js.map} +0 -0
- /package/dist/{chunk-U5CHZ5M3.js.map → chunk-N7ZS6PEF.js.map} +0 -0
- /package/dist/{chunk-G2GPNLSX.js.map → chunk-QNVBALEX.js.map} +0 -0
- /package/dist/{chunk-OYZAPX5G.js.map → chunk-R226UZOI.js.map} +0 -0
- /package/dist/{chunk-VJ7T5WIO.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";
|
|
@@ -80,10 +80,10 @@ import {
|
|
|
80
80
|
import {
|
|
81
81
|
modelHasSnapshot,
|
|
82
82
|
validateRunRecord
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-LOW3U7JZ.js";
|
|
84
84
|
import {
|
|
85
85
|
buildAgentProfileCell
|
|
86
|
-
} from "./chunk-
|
|
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
|
-
|
|
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 };
|
|
4058
|
+
function gepaParetoMethod(config, name = "gepa-pareto") {
|
|
4059
|
+
return gepaMethod(config, true, name);
|
|
3676
4060
|
}
|
|
3677
|
-
function
|
|
3678
|
-
return
|
|
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
|
-
|
|
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",
|
|
@@ -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((
|
|
4965
|
-
throw new Error(
|
|
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({
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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: ["
|
|
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: {
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
5822
|
+
required: ["mode"],
|
|
5448
5823
|
properties: {
|
|
5449
|
-
|
|
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: ["
|
|
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
|
-
|
|
5836
|
+
rationale: {
|
|
5466
5837
|
type: ["string", "null"],
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
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: {
|
|
5488
|
-
|
|
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,
|
|
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
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
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
|
-
|
|
5632
|
-
opts.targetSurface,
|
|
5633
|
-
allowedJsonPaths,
|
|
5634
|
-
objectives
|
|
5635
|
-
);
|
|
6008
|
+
const responseSchema = policyEditAuthorJsonSchema(limit, allowedJsonPaths);
|
|
6009
|
+
const system = policyEditAuthorSystem(responseSchema);
|
|
5636
6010
|
assertPolicyEditAuthorContextBudget(
|
|
5637
|
-
{ system
|
|
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:
|
|
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, {
|
|
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
|
-
|
|
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,
|
|
6120
|
-
if (draft.target.
|
|
6498
|
+
function bindAuthoredEdit(draft, findingByKey, targetSurface, allowedPaths, objective, currentComposite) {
|
|
6499
|
+
if (!allowedPaths.has(draft.target.path)) {
|
|
6121
6500
|
throw new Error(
|
|
6122
|
-
`llmPolicyEditProposer:
|
|
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:
|
|
6170
|
-
direction:
|
|
6524
|
+
metric: objective.key,
|
|
6525
|
+
direction: objective.direction,
|
|
6171
6526
|
amount: draft.expectedGain.amount,
|
|
6172
|
-
|
|
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:
|
|
6539
|
+
surface: targetSurface,
|
|
6179
6540
|
path: draft.target.path,
|
|
6180
6541
|
...draft.target.label ? { label: draft.target.label } : {}
|
|
6181
6542
|
},
|
|
6182
|
-
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
|
|
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
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
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-
|
|
8721
|
+
//# sourceMappingURL=chunk-22VO7T2I.js.map
|