@tangle-network/agent-bench 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/HARNESS.md +43 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +1 -1
- package/dist/benchmarks/_harness.js +1 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
- package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
- package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
- package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
- package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
- package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
- package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
- package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
- package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
- package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
- package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
- package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
- package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
- package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
- package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
- package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
- package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
- package/dist/{chunk-PWQVGAJB.js → chunk-V7AEBY6U.js} +21 -21
- package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
- package/dist/chunk-WSKWVEQB.js.map +1 -0
- package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
- package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
- package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
- package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +5 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +2 -2
- package/src/gate.ts +1 -1
- package/src/hev-eval.mts +5 -2
- package/src/hev-improve.mts +118 -73
- package/src/official-optimizer-config.mts +89 -0
- package/src/official-optimizer-config.test.mts +88 -0
- package/src/profiles.ts +2 -2
- package/src/quant-arena/README.md +144 -0
- package/src/quant-arena/backtest.test.mts +135 -0
- package/src/quant-arena/backtest.ts +218 -0
- package/src/quant-arena/data.test.mts +44 -0
- package/src/quant-arena/data.ts +141 -0
- package/src/quant-arena/driver.test.mts +253 -0
- package/src/quant-arena/driver.ts +219 -0
- package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
- package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
- package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
- package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
- package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
- package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
- package/src/quant-arena/holdout-certify.mts +206 -0
- package/src/quant-arena/holdout-certify.test.mts +82 -0
- package/src/quant-arena/leak-audit.test.mts +79 -0
- package/src/quant-arena/leak-audit.ts +95 -0
- package/src/quant-arena/make-fixtures.mts +161 -0
- package/src/quant-arena/multiplicity.test.mts +68 -0
- package/src/quant-arena/multiplicity.ts +87 -0
- package/src/quant-arena/nautilus-certify.ts +31 -0
- package/src/quant-arena/oms.ts +90 -0
- package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
- package/src/quant-arena/python/pyproject.toml +8 -0
- package/src/quant-arena/python/uv.lock +1297 -0
- package/src/quant-arena/python/vbt-worker.py +192 -0
- package/src/quant-arena/quant-loop.mts +813 -0
- package/src/quant-arena/quant-loop.test.mts +75 -0
- package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
- package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
- package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
- package/src/quant-arena/types.ts +133 -0
- package/src/quant-arena/vbt-client.ts +321 -0
- package/src/quant-arena/vbt-parity.test.mts +183 -0
- package/src/quant-arena/windows.test.mts +45 -0
- package/src/quant-arena/windows.ts +54 -0
- package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
- package/src/rollout-ledger/backfill-swe-arena.test.mts +342 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +225 -0
- package/src/swe-arena/activation.test.mts +300 -0
- package/src/swe-arena/arms.ts +30 -14
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +102 -1
- package/src/swe-arena/cell-evidence.mts +41 -9
- package/src/swe-arena/factory-command-container.ts +284 -0
- package/src/swe-arena/factory-judge-child.mts +228 -0
- package/src/swe-arena/factory.test.mts +643 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
- package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
- package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
- package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
- package/src/swe-arena/fixtures/run-report/README.md +43 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
- package/src/swe-arena/fixtures.ts +135 -2
- package/src/swe-arena/gepa-seat.mts +877 -0
- package/src/swe-arena/gepa-seat.test.mts +1059 -0
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/outer-loop.mts +710 -33
- package/src/swe-arena/premeasured-from-cells.mts +281 -0
- package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
- package/src/swe-arena/proposer-fanout.mts +327 -31
- package/src/swe-arena/proposer-fanout.test.mts +246 -0
- package/src/swe-arena/proposer-provenance.mts +172 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/run-experiment.mts +367 -1
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.test.mts +5 -3
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-arena/types.ts +52 -0
- package/src/swe-code-improve.mts +24 -25
- package/src/swe-improve.mts +129 -96
- package/src/swe-local-proof.mts +6 -1
- package/src/swe-stream.mts +4 -2
- package/src/swe-structural.mts +245 -837
- package/src/tb-container-executor.test.mts +30 -6
- package/src/tb-supervisor-sidecar.mts +2 -1
- package/src/trata-gepa.mts +182 -245
- package/dist/chunk-LRRD7NAG.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
- /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
- /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
- /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
- /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
- /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
- /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
- /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
- /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
- /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
- /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
- /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
- /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
- /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
- /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
- /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
- /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
- /package/dist/{chunk-PWQVGAJB.js.map → chunk-V7AEBY6U.js.map} +0 -0
- /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
- /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
- /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
- /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GEPA proposer seat using agent-eval's official
|
|
3
|
+
* `gepaOptimizationMethod` as one author in the swe-arena fan-out.
|
|
4
|
+
*
|
|
5
|
+
* Two-tier evaluator, the critical shape:
|
|
6
|
+
*
|
|
7
|
+
* INNER (what GEPA's own loop calls, many times, budget-capped): the
|
|
8
|
+
* candidate is ONE change-space file's content as a string. Each inner call
|
|
9
|
+
* gets a detached worktree at the incumbent commit, writes its own candidate,
|
|
10
|
+
* and runs the existing pre-filter smoke cell on one PUBLIC instance through
|
|
11
|
+
* the injected `SmokeRunner`. Score = smoke resolve (1/0) + verify-pass
|
|
12
|
+
* fraction as a bounded tiebreak. Inner calls are capped by
|
|
13
|
+
* `maxMetricCalls` (default 10; each smoke costs minutes of arm time).
|
|
14
|
+
*
|
|
15
|
+
* OUTER: GEPA's best candidate is written back to the surface file in the
|
|
16
|
+
* scratch worktree and the seat returns `applied: true` — from there the
|
|
17
|
+
* fan-out treats it EXACTLY like any other author's work: change-space
|
|
18
|
+
* check, activation-predicate gate, smoke pre-filter, then the full exam,
|
|
19
|
+
* with staircase label = the seat name (`gepa-author`).
|
|
20
|
+
*
|
|
21
|
+
* DATA BOUNDARIES (both fail-closed):
|
|
22
|
+
* - PUBLIC ONLY crosses the bridge: the only scenario ids serialized to the
|
|
23
|
+
* GEPA process name the public smoke instance; `assertNoPrivateLeak`
|
|
24
|
+
* re-checks every string headed to the bridge against the score split.
|
|
25
|
+
* - Holdout/final cases NEVER cross: the adapter's own API has no test-set
|
|
26
|
+
* field (`GepaBridgeInput` in agent-eval src/campaign/gepa-optimization-
|
|
27
|
+
* method.ts — "The final comparison cases are not accepted by this API and
|
|
28
|
+
* cannot be serialized here"), and its Python side hard-rejects one
|
|
29
|
+
* (`gepa_bridge.py` `_validate_input`: `if "testSet" in value ... raise`).
|
|
30
|
+
* This module never mentions holdout instances to begin with.
|
|
31
|
+
*
|
|
32
|
+
* OPTIONAL RUNTIME (fails at provenance time, before a candidate slot is used):
|
|
33
|
+
* - Python: `agent_eval_rpc.gepa_bridge` + a GEPA build with
|
|
34
|
+
* `optimize_anything`/`OptimizeAnythingConfig` must import —
|
|
35
|
+
* `probeGepaRuntime` throws with the pip install instruction otherwise.
|
|
36
|
+
* The TypeScript adapter is a pinned package dependency and imported directly.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
40
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
41
|
+
import { dirname, join } from 'node:path'
|
|
42
|
+
import {
|
|
43
|
+
type DispatchContext,
|
|
44
|
+
createRunCostLedger,
|
|
45
|
+
fsCampaignStorage,
|
|
46
|
+
type GepaOptimizationMethodConfig,
|
|
47
|
+
type GepaOptimizationRecipe,
|
|
48
|
+
gepaOptimizationMethod,
|
|
49
|
+
type JudgeConfig,
|
|
50
|
+
type MutableSurface,
|
|
51
|
+
type OptimizationMethod,
|
|
52
|
+
type OptimizationMethodInput,
|
|
53
|
+
type OptimizationMethodProvenance,
|
|
54
|
+
type Scenario,
|
|
55
|
+
} from '@tangle-network/agent-eval/campaign'
|
|
56
|
+
import { officialOptimizerModel } from '../official-optimizer-config.mts'
|
|
57
|
+
import { ACTIVATION_PREDICATE_RELPATH, type ActivationPredicate } from './activation.mts'
|
|
58
|
+
import { changeSpaceViolations, type OuterLoopConfig } from './outer-loop.mts'
|
|
59
|
+
import type { AuthorFn, ProposerSpec, SmokeRunner, SmokeVerdict } from './proposer-fanout.mts'
|
|
60
|
+
import { runOk } from './proc.ts'
|
|
61
|
+
import {
|
|
62
|
+
createDetachedWorktree,
|
|
63
|
+
pruneDetachedWorktrees,
|
|
64
|
+
removeDetachedWorktree,
|
|
65
|
+
} from './scratch-worktree.ts'
|
|
66
|
+
import type { ScoreSplit } from './score-split.mts'
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Spec.
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export const GEPA_ENGINES = ['gepa', 'omni'] as const
|
|
73
|
+
export type GepaEngineName = (typeof GEPA_ENGINES)[number]
|
|
74
|
+
|
|
75
|
+
export const DEFAULT_MAX_METRIC_CALLS = 10
|
|
76
|
+
export const DEFAULT_MAX_PROPOSER_COST_USD = 10
|
|
77
|
+
/** Omni = 3 bounded explore runs + 1 continuation (GEPA's published shape). */
|
|
78
|
+
export const OMNI_RUN_COUNT = 4
|
|
79
|
+
|
|
80
|
+
/** A `ProposerSpec` whose `engine` marks it as a GEPA seat. */
|
|
81
|
+
export type GepaSeatSpec = ProposerSpec & { engine: GepaEngineName; surface: string }
|
|
82
|
+
|
|
83
|
+
export function isGepaSeat(spec: ProposerSpec): spec is GepaSeatSpec {
|
|
84
|
+
return spec.engine !== undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Fail-closed spec validation, run at generator construction. A GEPA seat is
|
|
88
|
+
* an ENGINE invocation: harness/profile/model/merge belong to CLI-authored
|
|
89
|
+
* seats and are rejected here rather than silently ignored. */
|
|
90
|
+
export function validateGepaSeat(spec: ProposerSpec): asserts spec is GepaSeatSpec {
|
|
91
|
+
const label = `gepa seat '${spec.name}'`
|
|
92
|
+
if (spec.engine === undefined || !GEPA_ENGINES.includes(spec.engine)) {
|
|
93
|
+
throw new Error(`${label}: engine must be one of ${GEPA_ENGINES.join('|')}, got ${JSON.stringify(spec.engine)}`)
|
|
94
|
+
}
|
|
95
|
+
if (typeof spec.surface !== 'string' || spec.surface.length === 0) {
|
|
96
|
+
throw new Error(`${label}: surface is required — the ONE repo-relative file GEPA optimizes as a string`)
|
|
97
|
+
}
|
|
98
|
+
const violations = changeSpaceViolations([spec.surface])
|
|
99
|
+
if (violations.length > 0) {
|
|
100
|
+
throw new Error(`${label}: surface ${JSON.stringify(spec.surface)} is outside the declared change-space`)
|
|
101
|
+
}
|
|
102
|
+
for (const field of ['harness', 'profile', 'model', 'merge', 'lens', 'diagnosisSlice'] as const) {
|
|
103
|
+
if (spec[field] !== undefined) {
|
|
104
|
+
throw new Error(`${label}: field '${field}' belongs to harness-authored seats and must be unset on an engine seat`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
108
|
+
if (!Number.isSafeInteger(calls) || calls <= 0) {
|
|
109
|
+
throw new Error(`${label}: maxMetricCalls must be a positive integer, got ${JSON.stringify(spec.maxMetricCalls)}`)
|
|
110
|
+
}
|
|
111
|
+
if (spec.engine === 'omni' && calls < OMNI_RUN_COUNT) {
|
|
112
|
+
throw new Error(`${label}: engine 'omni' runs ${OMNI_RUN_COUNT} bounded engine runs and needs maxMetricCalls >= ${OMNI_RUN_COUNT}, got ${calls}`)
|
|
113
|
+
}
|
|
114
|
+
const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
|
|
115
|
+
if (!Number.isFinite(cost) || cost <= 0) {
|
|
116
|
+
throw new Error(`${label}: maxProposerCostUsd must be a positive finite number, got ${JSON.stringify(spec.maxProposerCostUsd)}`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Recipe.
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
export type GepaSeatRecipe = Extract<
|
|
125
|
+
GepaOptimizationRecipe,
|
|
126
|
+
{ kind: 'engine' | 'omni' }
|
|
127
|
+
>
|
|
128
|
+
|
|
129
|
+
/** Build the bounded recipe for a seat. The TOTAL inner-evaluation budget is
|
|
130
|
+
* exactly `maxMetricCalls`. The adapter's local callback enforces the sum
|
|
131
|
+
* of per-run limits, and the seat's own dispatch wrapper re-enforces it. */
|
|
132
|
+
export function recipeForSeat(spec: GepaSeatSpec): GepaSeatRecipe {
|
|
133
|
+
const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
134
|
+
const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
|
|
135
|
+
if (spec.engine === 'gepa') {
|
|
136
|
+
return { kind: 'engine', run: { engine: 'gepa', maxEvaluations: calls, maxProposerCostUsd: cost } }
|
|
137
|
+
}
|
|
138
|
+
// Omni: explore {gepa, autoresearch, meta_harness} then continue with gepa,
|
|
139
|
+
// splitting the call budget so the four bounded runs sum to `calls`.
|
|
140
|
+
const perExplore = Math.max(1, Math.floor(calls / OMNI_RUN_COUNT))
|
|
141
|
+
const continueCalls = calls - 3 * perExplore
|
|
142
|
+
const perRunCost = cost / OMNI_RUN_COUNT
|
|
143
|
+
const explore = ['gepa', 'autoresearch', 'meta_harness'].map((engine) => ({
|
|
144
|
+
engine,
|
|
145
|
+
maxEvaluations: perExplore,
|
|
146
|
+
maxProposerCostUsd: perRunCost,
|
|
147
|
+
}))
|
|
148
|
+
return {
|
|
149
|
+
kind: 'omni',
|
|
150
|
+
explore,
|
|
151
|
+
continueWith: { engine: 'gepa', maxEvaluations: continueCalls, maxProposerCostUsd: perRunCost },
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function recipeEvaluationBudget(recipe: GepaSeatRecipe): number {
|
|
156
|
+
const runs = recipe.kind === 'engine' ? [recipe.run] : [...recipe.explore, recipe.continueWith]
|
|
157
|
+
return runs.reduce((sum, run) => sum + run.maxEvaluations, 0)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Public-only bridge examples.
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
export interface GepaSeatScenario extends Scenario {
|
|
165
|
+
/** The PUBLIC smoke instance this scenario dispatches to. */
|
|
166
|
+
smokeIid: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Throws when any private instance id appears in text headed to the bridge. */
|
|
170
|
+
export function assertNoPrivateLeak(
|
|
171
|
+
text: string,
|
|
172
|
+
split: Pick<ScoreSplit, 'privateInstances'> | null,
|
|
173
|
+
what: string,
|
|
174
|
+
): void {
|
|
175
|
+
if (split === null) return
|
|
176
|
+
const leaked = split.privateInstances.filter((iid) => text.includes(iid))
|
|
177
|
+
if (leaked.length > 0) {
|
|
178
|
+
throw new Error(`gepa seat: ${what} would leak private instance id(s) [${leaked.join(', ')}] to the GEPA bridge`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The ONLY scenarios the bridge ever sees: the public smoke instance as the
|
|
183
|
+
* train example plus a distinct-id alias as the selection example (the
|
|
184
|
+
* adapter requires disjoint train/selection ids; both dispatch to the same
|
|
185
|
+
* smoke cell). Fails loud when the smoke instance is private. */
|
|
186
|
+
export function gepaBridgeScenarios(
|
|
187
|
+
smokeIid: string,
|
|
188
|
+
split: Pick<ScoreSplit, 'privateInstances'> | null,
|
|
189
|
+
): { train: GepaSeatScenario[]; selection: GepaSeatScenario[] } {
|
|
190
|
+
if (split !== null && split.privateInstances.includes(smokeIid)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`gepa seat: smoke instance ${smokeIid} is PRIVATE under the score split — private ids never cross the bridge`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
assertNoPrivateLeak(smokeIid, split, `smoke instance id '${smokeIid}'`)
|
|
196
|
+
return {
|
|
197
|
+
train: [{ id: smokeIid, kind: 'swe-smoke', smokeIid }],
|
|
198
|
+
selection: [{ id: `${smokeIid}::selection`, kind: 'swe-smoke', smokeIid }],
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Inner score.
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/** Resolve dominates; verify-pass is a bounded tiebreak that can never beat a
|
|
207
|
+
* resolve (0.25 < 1). Range {0, 0.25, 1, 1.25}. */
|
|
208
|
+
export function innerSmokeComposite(verdict: Pick<SmokeVerdict, 'resolved' | 'verifyPass'>): number {
|
|
209
|
+
return (verdict.resolved === true ? 1 : 0) + (verdict.verifyPass === true ? 0.25 : 0)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function innerSmokeJudge(): JudgeConfig<SmokeVerdict, GepaSeatScenario> {
|
|
213
|
+
return {
|
|
214
|
+
name: 'gepa-inner-smoke',
|
|
215
|
+
judgeVersion: 'gepa-inner-smoke',
|
|
216
|
+
dimensions: [
|
|
217
|
+
{ key: 'resolved', description: 'Official SWE-bench judge verdict for the smoke cell (1 resolved / 0 not).' },
|
|
218
|
+
{ key: 'verifyPass', description: 'Committed verify fixture passed for the smoke cell (tiebreak).' },
|
|
219
|
+
],
|
|
220
|
+
score: ({ artifact }) => ({
|
|
221
|
+
dimensions: {
|
|
222
|
+
resolved: artifact.resolved === true ? 1 : 0,
|
|
223
|
+
verifyPass: artifact.verifyPass === true ? 1 : 0,
|
|
224
|
+
},
|
|
225
|
+
composite: innerSmokeComposite(artifact),
|
|
226
|
+
notes: artifact.reason,
|
|
227
|
+
}),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Optional Python runtime.
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
export const GEPA_PYTHON_INSTALL_HINT =
|
|
236
|
+
'install `agent-eval-rpc==0.126.6`, then install ' +
|
|
237
|
+
'`gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f`'
|
|
238
|
+
|
|
239
|
+
export type GepaMethodFactory = (
|
|
240
|
+
config: GepaOptimizationMethodConfig<GepaSeatScenario, SmokeVerdict>,
|
|
241
|
+
) => OptimizationMethod<GepaSeatScenario, SmokeVerdict>
|
|
242
|
+
|
|
243
|
+
export type ProbeExec = (
|
|
244
|
+
command: string,
|
|
245
|
+
args: string[],
|
|
246
|
+
) => Promise<{ code: number | null; stdout: string; stderr: string }>
|
|
247
|
+
|
|
248
|
+
export interface GepaRuntimeProbe {
|
|
249
|
+
pythonVersion: string
|
|
250
|
+
gepaVersion: string
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export const DEFAULT_GEPA_PYTHON = 'python3'
|
|
254
|
+
|
|
255
|
+
/** Prove the Python side of the bridge can run, or throw install
|
|
256
|
+
* instructions. Mirrors the codex seat's login-status gate: run at t=0 so a
|
|
257
|
+
* dead seat fails the launch, never a mid-run candidate slot. */
|
|
258
|
+
export async function probeGepaRuntime(python: string, exec: ProbeExec, seatName: string): Promise<GepaRuntimeProbe> {
|
|
259
|
+
const version = await exec(python, ['--version'])
|
|
260
|
+
if (version.code !== 0) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`gepa seat '${seatName}': '${python} --version' failed (rc=${version.code}) — ${GEPA_PYTHON_INSTALL_HINT}`,
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
const bridge = await exec(python, ['-c', 'import agent_eval_rpc.gepa_bridge'])
|
|
266
|
+
if (bridge.code !== 0) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`gepa seat '${seatName}': GEPA Python runtime is not installed ` +
|
|
269
|
+
`(python=${python}; 'import agent_eval_rpc.gepa_bridge' failed: ${bridge.stderr.trim().slice(0, 300)}). ` +
|
|
270
|
+
GEPA_PYTHON_INSTALL_HINT,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
const gepa = await exec(python, [
|
|
274
|
+
'-c',
|
|
275
|
+
"from gepa.optimize_anything import optimize_anything, OptimizeAnythingConfig; " +
|
|
276
|
+
"import gepa; print(getattr(gepa, '__version__', 'source'))",
|
|
277
|
+
])
|
|
278
|
+
if (gepa.code !== 0) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`gepa seat '${seatName}': installed gepa lacks the multi-engine optimize_anything API ` +
|
|
281
|
+
`(${gepa.stderr.trim().slice(0, 300)}). ` +
|
|
282
|
+
GEPA_PYTHON_INSTALL_HINT,
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
return { pythonVersion: (version.stdout + version.stderr).trim(), gepaVersion: gepa.stdout.trim() }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Inner-run provenance.
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
export interface GepaInnerCall {
|
|
293
|
+
call: number
|
|
294
|
+
scenarioId: string
|
|
295
|
+
smokeIid: string
|
|
296
|
+
candidateSha256: string
|
|
297
|
+
composite: number
|
|
298
|
+
resolved: boolean | null
|
|
299
|
+
verifyPass: boolean | null
|
|
300
|
+
pass: boolean
|
|
301
|
+
wallS: number
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
type OptimizationPackageSource = OptimizationMethodProvenance['source']
|
|
305
|
+
type OptimizationModuleSource = NonNullable<OptimizationMethodProvenance['modules']>[number]
|
|
306
|
+
type OptimizationPythonRuntime = NonNullable<OptimizationMethodProvenance['python']>
|
|
307
|
+
type OptimizationTokenUsage = NonNullable<OptimizationMethodProvenance['tokenUsage']>
|
|
308
|
+
|
|
309
|
+
export interface GepaSeatInnerRun {
|
|
310
|
+
seat: string
|
|
311
|
+
engine: GepaEngineName
|
|
312
|
+
surface: string
|
|
313
|
+
generation: number
|
|
314
|
+
budget: number
|
|
315
|
+
innerCallCount: number
|
|
316
|
+
innerScores: GepaInnerCall[]
|
|
317
|
+
bestComposite: number | null
|
|
318
|
+
source: OptimizationPackageSource & { revision: string; sourceSha256: string }
|
|
319
|
+
bridge: OptimizationPackageSource & { sourceSha256: string }
|
|
320
|
+
modules: OptimizationModuleSource[]
|
|
321
|
+
python: OptimizationPythonRuntime
|
|
322
|
+
runId: string
|
|
323
|
+
compatibleRunId: string
|
|
324
|
+
resumed: boolean
|
|
325
|
+
evaluationCount: number
|
|
326
|
+
tokenUsage: OptimizationTokenUsage
|
|
327
|
+
artifactDir: string
|
|
328
|
+
totalCostUsd: number
|
|
329
|
+
accountingComplete: boolean
|
|
330
|
+
incompleteReasons: string[]
|
|
331
|
+
durationMs: number
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const PROPOSER_PROVENANCE_FILENAME = 'proposer-provenance.json'
|
|
335
|
+
export const GEPA_INNER_RUNS_DIRNAME = 'gepa-inner-runs'
|
|
336
|
+
|
|
337
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
338
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function errorCode(error: unknown): string | undefined {
|
|
342
|
+
return isRecord(error) && typeof error.code === 'string' ? error.code : undefined
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function readJsonObject(path: string): Promise<Record<string, unknown>> {
|
|
346
|
+
const raw = await readFile(path, 'utf8')
|
|
347
|
+
let value: unknown
|
|
348
|
+
try {
|
|
349
|
+
value = JSON.parse(raw)
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw new Error(`gepa seat: malformed JSON in existing provenance file ${path}`, { cause: error })
|
|
352
|
+
}
|
|
353
|
+
if (!isRecord(value)) {
|
|
354
|
+
throw new Error(`gepa seat: existing provenance file ${path} must contain a JSON object`)
|
|
355
|
+
}
|
|
356
|
+
return value
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function validateExistingRunRecords(outDir: string): Promise<void> {
|
|
360
|
+
const launchRecordPath = join(outDir, PROPOSER_PROVENANCE_FILENAME)
|
|
361
|
+
try {
|
|
362
|
+
await readJsonObject(launchRecordPath)
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (errorCode(error) !== 'ENOENT') throw error
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const recordsDir = join(outDir, GEPA_INNER_RUNS_DIRNAME)
|
|
368
|
+
let entries
|
|
369
|
+
try {
|
|
370
|
+
entries = await readdir(recordsDir, { withFileTypes: true })
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (errorCode(error) === 'ENOENT') return
|
|
373
|
+
throw error
|
|
374
|
+
}
|
|
375
|
+
for (const entry of entries) {
|
|
376
|
+
if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
377
|
+
await readJsonObject(join(recordsDir, entry.name))
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Persist one immutable record without mutating the shared launch record. */
|
|
383
|
+
export async function recordGepaSeatInnerRun(outDir: string, run: GepaSeatInnerRun): Promise<string> {
|
|
384
|
+
await validateExistingRunRecords(outDir)
|
|
385
|
+
const recordsDir = join(outDir, GEPA_INNER_RUNS_DIRNAME)
|
|
386
|
+
await mkdir(recordsDir, { recursive: true })
|
|
387
|
+
const identity = createHash('sha256')
|
|
388
|
+
.update(JSON.stringify({ seat: run.seat, generation: run.generation, runId: run.runId }))
|
|
389
|
+
.digest('hex')
|
|
390
|
+
.slice(0, 16)
|
|
391
|
+
const nonce = randomUUID()
|
|
392
|
+
const filename = `${identity}-${nonce}.json`
|
|
393
|
+
const finalPath = join(recordsDir, filename)
|
|
394
|
+
const temporaryPath = join(recordsDir, `.${filename}.tmp`)
|
|
395
|
+
try {
|
|
396
|
+
await writeFile(temporaryPath, JSON.stringify(run, null, 2), { flag: 'wx' })
|
|
397
|
+
await rename(temporaryPath, finalPath)
|
|
398
|
+
} finally {
|
|
399
|
+
await rm(temporaryPath, { force: true })
|
|
400
|
+
}
|
|
401
|
+
return finalPath
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function requiredText(value: unknown, label: string): string {
|
|
405
|
+
if (typeof value !== 'string' || value.length === 0 || value !== value.trim()) {
|
|
406
|
+
throw new Error(`gepa seat: optimizer result omitted ${label}`)
|
|
407
|
+
}
|
|
408
|
+
return value
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function requiredSha256(value: unknown, label: string): string {
|
|
412
|
+
const hash = requiredText(value, label)
|
|
413
|
+
if (!/^[0-9a-f]{64}$/.test(hash)) {
|
|
414
|
+
throw new Error(`gepa seat: optimizer result returned invalid ${label}`)
|
|
415
|
+
}
|
|
416
|
+
return hash
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function requiredCount(value: unknown, label: string): number {
|
|
420
|
+
if (!Number.isSafeInteger(value) || (value as number) < 0) {
|
|
421
|
+
throw new Error(`gepa seat: optimizer result returned invalid ${label}`)
|
|
422
|
+
}
|
|
423
|
+
return value as number
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function completeProvenance(
|
|
427
|
+
provenance: OptimizationMethodProvenance | undefined,
|
|
428
|
+
seatName: string,
|
|
429
|
+
): Pick<
|
|
430
|
+
GepaSeatInnerRun,
|
|
431
|
+
| 'source'
|
|
432
|
+
| 'bridge'
|
|
433
|
+
| 'modules'
|
|
434
|
+
| 'python'
|
|
435
|
+
| 'runId'
|
|
436
|
+
| 'compatibleRunId'
|
|
437
|
+
| 'resumed'
|
|
438
|
+
| 'evaluationCount'
|
|
439
|
+
| 'tokenUsage'
|
|
440
|
+
| 'artifactDir'
|
|
441
|
+
> {
|
|
442
|
+
const label = `gepa seat '${seatName}'`
|
|
443
|
+
if (provenance === undefined) {
|
|
444
|
+
throw new Error(`${label}: optimizer result omitted provenance`)
|
|
445
|
+
}
|
|
446
|
+
if (
|
|
447
|
+
provenance.source.kind !== 'package' ||
|
|
448
|
+
provenance.source.evidence !== 'observed' ||
|
|
449
|
+
requiredText(provenance.source.package, 'source.package') !== 'gepa'
|
|
450
|
+
) {
|
|
451
|
+
throw new Error(`${label}: optimizer result returned invalid source package`)
|
|
452
|
+
}
|
|
453
|
+
requiredText(provenance.source.version, 'source.version')
|
|
454
|
+
const sourceRevision = requiredText(provenance.source.revision, 'source.revision')
|
|
455
|
+
const sourceSha256 = requiredSha256(provenance.source.sourceSha256, 'source.sourceSha256')
|
|
456
|
+
if (provenance.bridge === undefined) {
|
|
457
|
+
throw new Error(`${label}: optimizer result omitted bridge provenance`)
|
|
458
|
+
}
|
|
459
|
+
if (
|
|
460
|
+
provenance.bridge.kind !== 'package' ||
|
|
461
|
+
provenance.bridge.evidence !== 'observed' ||
|
|
462
|
+
requiredText(provenance.bridge.package, 'bridge.package') !== 'agent-eval-rpc'
|
|
463
|
+
) {
|
|
464
|
+
throw new Error(`${label}: optimizer result returned invalid bridge package`)
|
|
465
|
+
}
|
|
466
|
+
requiredText(provenance.bridge.version, 'bridge.version')
|
|
467
|
+
const bridgeSha256 = requiredSha256(provenance.bridge.sourceSha256, 'bridge.sourceSha256')
|
|
468
|
+
if (provenance.modules === undefined) {
|
|
469
|
+
throw new Error(`${label}: optimizer result omitted module provenance`)
|
|
470
|
+
}
|
|
471
|
+
const modules = provenance.modules.map((module, index) => ({
|
|
472
|
+
module: requiredText(module.module, `modules[${index}].module`),
|
|
473
|
+
sourceSha256: requiredSha256(module.sourceSha256, `modules[${index}].sourceSha256`),
|
|
474
|
+
}))
|
|
475
|
+
if (provenance.python === undefined) {
|
|
476
|
+
throw new Error(`${label}: optimizer result omitted Python provenance`)
|
|
477
|
+
}
|
|
478
|
+
const python = {
|
|
479
|
+
implementation: requiredText(provenance.python.implementation, 'python.implementation'),
|
|
480
|
+
version: requiredText(provenance.python.version, 'python.version'),
|
|
481
|
+
}
|
|
482
|
+
if (provenance.compatibleRunId === undefined) {
|
|
483
|
+
throw new Error(`${label}: optimizer result omitted compatibleRunId`)
|
|
484
|
+
}
|
|
485
|
+
if (provenance.tokenUsage === undefined) {
|
|
486
|
+
throw new Error(`${label}: optimizer result omitted token usage`)
|
|
487
|
+
}
|
|
488
|
+
const tokenUsage = {
|
|
489
|
+
inputTokens: requiredCount(provenance.tokenUsage.inputTokens, 'tokenUsage.inputTokens'),
|
|
490
|
+
...(provenance.tokenUsage.cachedInputTokens === undefined
|
|
491
|
+
? {}
|
|
492
|
+
: {
|
|
493
|
+
cachedInputTokens: requiredCount(
|
|
494
|
+
provenance.tokenUsage.cachedInputTokens,
|
|
495
|
+
'tokenUsage.cachedInputTokens',
|
|
496
|
+
),
|
|
497
|
+
}),
|
|
498
|
+
...(provenance.tokenUsage.cacheWriteInputTokens === undefined
|
|
499
|
+
? {}
|
|
500
|
+
: {
|
|
501
|
+
cacheWriteInputTokens: requiredCount(
|
|
502
|
+
provenance.tokenUsage.cacheWriteInputTokens,
|
|
503
|
+
'tokenUsage.cacheWriteInputTokens',
|
|
504
|
+
),
|
|
505
|
+
}),
|
|
506
|
+
outputTokens: requiredCount(provenance.tokenUsage.outputTokens, 'tokenUsage.outputTokens'),
|
|
507
|
+
...(provenance.tokenUsage.reasoningTokens === undefined
|
|
508
|
+
? {}
|
|
509
|
+
: {
|
|
510
|
+
reasoningTokens: requiredCount(
|
|
511
|
+
provenance.tokenUsage.reasoningTokens,
|
|
512
|
+
'tokenUsage.reasoningTokens',
|
|
513
|
+
),
|
|
514
|
+
}),
|
|
515
|
+
totalTokens: requiredCount(provenance.tokenUsage.totalTokens, 'tokenUsage.totalTokens'),
|
|
516
|
+
calls: requiredCount(provenance.tokenUsage.calls, 'tokenUsage.calls'),
|
|
517
|
+
}
|
|
518
|
+
if (tokenUsage.totalTokens !== tokenUsage.inputTokens + tokenUsage.outputTokens) {
|
|
519
|
+
throw new Error(`${label}: optimizer result returned inconsistent token usage`)
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
source: { ...provenance.source, revision: sourceRevision, sourceSha256 },
|
|
523
|
+
bridge: { ...provenance.bridge, sourceSha256: bridgeSha256 },
|
|
524
|
+
modules,
|
|
525
|
+
python,
|
|
526
|
+
runId: requiredText(provenance.runId, 'runId'),
|
|
527
|
+
compatibleRunId: requiredText(provenance.compatibleRunId, 'compatibleRunId'),
|
|
528
|
+
resumed: provenance.resumed,
|
|
529
|
+
evaluationCount: requiredCount(provenance.evaluationCount, 'evaluationCount'),
|
|
530
|
+
tokenUsage,
|
|
531
|
+
artifactDir: requiredText(provenance.artifactDir, 'artifactDir'),
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function assertCompleteCost(
|
|
536
|
+
cost: { totalCostUsd: number; accountingComplete: boolean; incompleteReasons: string[] },
|
|
537
|
+
seatName: string,
|
|
538
|
+
): void {
|
|
539
|
+
if (!Number.isFinite(cost.totalCostUsd) || cost.totalCostUsd < 0) {
|
|
540
|
+
throw new Error(`gepa seat '${seatName}': optimizer returned invalid total cost`)
|
|
541
|
+
}
|
|
542
|
+
if (
|
|
543
|
+
!Array.isArray(cost.incompleteReasons) ||
|
|
544
|
+
cost.incompleteReasons.some(
|
|
545
|
+
(reason) => typeof reason !== 'string' || reason.length === 0 || reason !== reason.trim(),
|
|
546
|
+
)
|
|
547
|
+
) {
|
|
548
|
+
throw new Error(`gepa seat '${seatName}': optimizer returned invalid incomplete reasons`)
|
|
549
|
+
}
|
|
550
|
+
if (cost.accountingComplete !== (cost.incompleteReasons.length === 0)) {
|
|
551
|
+
throw new Error(`gepa seat '${seatName}': optimizer returned inconsistent cost accounting`)
|
|
552
|
+
}
|
|
553
|
+
if (!cost.accountingComplete) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`gepa seat '${seatName}': cost accounting is incomplete: ${cost.incompleteReasons.join('; ') || 'no reason provided'}`,
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
// Mechanical activation predicate.
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
565
|
+
|
|
566
|
+
export const MIN_PREDICATE_LINE_CHARS = 12
|
|
567
|
+
|
|
568
|
+
/** Derive a machine-checkable predicate from the surface change: the longest
|
|
569
|
+
* ADDED line must render in the candidate's own run artifacts (for a prompt
|
|
570
|
+
* surface, the changed text appearing in composed prompts IS the mechanism
|
|
571
|
+
* firing). Returns null when no added line is distinctive enough — the
|
|
572
|
+
* caller fails the candidate loud instead of shipping an unverifiable one. */
|
|
573
|
+
export function mechanicalActivationPredicate(
|
|
574
|
+
seed: string,
|
|
575
|
+
winner: string,
|
|
576
|
+
surface: string,
|
|
577
|
+
): ActivationPredicate | null {
|
|
578
|
+
const seedLines = new Set(seed.split('\n').map((l) => l.trim()))
|
|
579
|
+
const added = winner
|
|
580
|
+
.split('\n')
|
|
581
|
+
.map((l) => l.trim())
|
|
582
|
+
.filter((l) => l.length >= MIN_PREDICATE_LINE_CHARS && !seedLines.has(l))
|
|
583
|
+
if (added.length === 0) return null
|
|
584
|
+
const line = added.reduce((a, b) => (b.length > a.length ? b : a))
|
|
585
|
+
return {
|
|
586
|
+
description: `gepa-author surface change fired: candidate text from ${surface} appears in run artifacts`,
|
|
587
|
+
kind: 'grep',
|
|
588
|
+
pattern: escapeRegExp(line),
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// The seat author.
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
export interface GepaSeatDeps {
|
|
597
|
+
smokeRunner: SmokeRunner
|
|
598
|
+
runnerImplementationRef: string
|
|
599
|
+
judgeImplementationRef: string
|
|
600
|
+
/** Resolved PUBLIC smoke instance (outer-loop restricts the choice to the
|
|
601
|
+
* split's public set; re-asserted here fail-closed). */
|
|
602
|
+
smokeInstanceId: string
|
|
603
|
+
scoreSplit: Pick<ScoreSplit, 'privateInstances'> | null
|
|
604
|
+
/** Test override. Default: agent-eval's official GEPA method. */
|
|
605
|
+
methodFactory?: GepaMethodFactory
|
|
606
|
+
/** Explicit model override. Production otherwise resolves the metered model from env. */
|
|
607
|
+
optimizer?: NonNullable<
|
|
608
|
+
GepaOptimizationMethodConfig<GepaSeatScenario, SmokeVerdict>['optimizer']
|
|
609
|
+
>
|
|
610
|
+
log?: (msg: string) => void
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const sha256 = (s: string): string => `sha256:${createHash('sha256').update(s).digest('hex')}`
|
|
614
|
+
|
|
615
|
+
export function gepaSeatEvaluationId(input: {
|
|
616
|
+
smokeInstanceId: string
|
|
617
|
+
dispatchTimeoutMs: number
|
|
618
|
+
incumbentCommit: string
|
|
619
|
+
runnerImplementationRef: string
|
|
620
|
+
judgeImplementationRef: string
|
|
621
|
+
}): string {
|
|
622
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(input.incumbentCommit)) {
|
|
623
|
+
throw new Error('gepa seat: incumbentCommit must be an immutable git object id')
|
|
624
|
+
}
|
|
625
|
+
const requireImplementationRef = (value: string, label: string): string => {
|
|
626
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
|
|
627
|
+
throw new Error(`gepa seat: ${label} must be an immutable sha256 reference`)
|
|
628
|
+
}
|
|
629
|
+
return value
|
|
630
|
+
}
|
|
631
|
+
const runnerRef = requireImplementationRef(
|
|
632
|
+
input.runnerImplementationRef,
|
|
633
|
+
'runnerImplementationRef',
|
|
634
|
+
)
|
|
635
|
+
const judgeRef = requireImplementationRef(
|
|
636
|
+
input.judgeImplementationRef,
|
|
637
|
+
'judgeImplementationRef',
|
|
638
|
+
)
|
|
639
|
+
return [
|
|
640
|
+
'swe-arena-gepa-seat',
|
|
641
|
+
`smoke=${input.smokeInstanceId}`,
|
|
642
|
+
`incumbent=${input.incumbentCommit}`,
|
|
643
|
+
`runner=${runnerRef}`,
|
|
644
|
+
`judge=${judgeRef}`,
|
|
645
|
+
`dispatchTimeoutMs=${input.dispatchTimeoutMs}`,
|
|
646
|
+
].join('|')
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Build the seat's `AuthorFn`. The fan-out calls it with the seat's scratch
|
|
650
|
+
* worktree (checked out at the incumbent commit); everything this function
|
|
651
|
+
* leaves in that worktree becomes the candidate diff. */
|
|
652
|
+
export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): AuthorFn {
|
|
653
|
+
const log = deps.log ?? (() => {})
|
|
654
|
+
return async (proposer, args) => {
|
|
655
|
+
validateGepaSeat(proposer)
|
|
656
|
+
const spec: GepaSeatSpec = proposer
|
|
657
|
+
const generation = args.generation ?? 0
|
|
658
|
+
const budget = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
659
|
+
const recipe = recipeForSeat(spec)
|
|
660
|
+
const scenarios = gepaBridgeScenarios(deps.smokeInstanceId, deps.scoreSplit)
|
|
661
|
+
const surfacePath = join(args.worktreePath, spec.surface)
|
|
662
|
+
const seed = await readFile(surfacePath, 'utf8').catch(() => {
|
|
663
|
+
throw new Error(`gepa seat '${spec.name}': surface ${spec.surface} does not exist at the incumbent commit`)
|
|
664
|
+
})
|
|
665
|
+
const incumbentCommit = (
|
|
666
|
+
await runOk('git', ['-C', args.worktreePath, 'rev-parse', 'HEAD'])
|
|
667
|
+
).stdout.trim()
|
|
668
|
+
const runDir = join(config.outDir, 'gepa-seat', `gen${generation}-${spec.name.replace(/[^a-zA-Z0-9_-]/g, '_')}`)
|
|
669
|
+
await mkdir(runDir, { recursive: true })
|
|
670
|
+
await pruneDetachedWorktrees(args.worktreePath)
|
|
671
|
+
|
|
672
|
+
const objective =
|
|
673
|
+
`Improve the supervisor-loop file '${spec.surface}' (returned as the COMPLETE new file content) so the ` +
|
|
674
|
+
'SWE-bench smoke evaluation scores higher. Score = 1 for an officially resolved instance plus 0.25 when ' +
|
|
675
|
+
'the verify fixture passes. Keep the file coherent and self-contained; only its content is applied.'
|
|
676
|
+
const background =
|
|
677
|
+
`The candidate string replaces ${spec.surface} in a checkout of the loops supervisor repo; every other ` +
|
|
678
|
+
'file stays at the incumbent commit. Each evaluation runs one real SWE-bench instance end-to-end and ' +
|
|
679
|
+
'takes minutes — spend evaluations deliberately.'
|
|
680
|
+
assertNoPrivateLeak(objective + background + JSON.stringify([...scenarios.train, ...scenarios.selection]),
|
|
681
|
+
deps.scoreSplit, 'bridge payload')
|
|
682
|
+
|
|
683
|
+
const storage = fsCampaignStorage()
|
|
684
|
+
const costLedger =
|
|
685
|
+
args.costLedger ??
|
|
686
|
+
createRunCostLedger({
|
|
687
|
+
storage,
|
|
688
|
+
runDir: `${runDir}/cost`,
|
|
689
|
+
})
|
|
690
|
+
const innerScores: GepaInnerCall[] = []
|
|
691
|
+
let dispatchedCalls = 0
|
|
692
|
+
const dispatchWithSurface = async (
|
|
693
|
+
surface: MutableSurface,
|
|
694
|
+
scenario: GepaSeatScenario,
|
|
695
|
+
_ctx: DispatchContext,
|
|
696
|
+
): Promise<SmokeVerdict> => {
|
|
697
|
+
if (typeof surface !== 'string') {
|
|
698
|
+
throw new Error(`gepa seat '${spec.name}': candidate surface must be a string`)
|
|
699
|
+
}
|
|
700
|
+
const call = ++dispatchedCalls
|
|
701
|
+
if (call > budget) {
|
|
702
|
+
// Defense-in-depth: the adapter's callback enforces the same cap.
|
|
703
|
+
throw new Error(`gepa seat '${spec.name}': inner-call budget ${budget} exhausted`)
|
|
704
|
+
}
|
|
705
|
+
const candidateSha256 = sha256(surface)
|
|
706
|
+
const evaluationKey = `inner-${call}-${candidateSha256.slice(7, 19)}`
|
|
707
|
+
const candidateWorktree = join(runDir, 'candidate-worktrees', evaluationKey)
|
|
708
|
+
await createDetachedWorktree(args.worktreePath, incumbentCommit, candidateWorktree)
|
|
709
|
+
let verdict: SmokeVerdict
|
|
710
|
+
try {
|
|
711
|
+
await writeFile(join(candidateWorktree, spec.surface), surface)
|
|
712
|
+
verdict = await deps.smokeRunner({
|
|
713
|
+
scratchPath: candidateWorktree,
|
|
714
|
+
generation,
|
|
715
|
+
proposer: spec,
|
|
716
|
+
evaluationKey,
|
|
717
|
+
costLedger,
|
|
718
|
+
})
|
|
719
|
+
} finally {
|
|
720
|
+
await removeDetachedWorktree(args.worktreePath, candidateWorktree)
|
|
721
|
+
}
|
|
722
|
+
if (deps.scoreSplit !== null && deps.scoreSplit.privateInstances.includes(verdict.iid)) {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`gepa seat '${spec.name}': smoke ran PRIVATE instance ${verdict.iid} — refusing to feed its score to the bridge`,
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
innerScores.push({
|
|
728
|
+
call,
|
|
729
|
+
scenarioId: scenario.id,
|
|
730
|
+
smokeIid: verdict.iid,
|
|
731
|
+
candidateSha256,
|
|
732
|
+
composite: innerSmokeComposite(verdict),
|
|
733
|
+
resolved: verdict.resolved,
|
|
734
|
+
verifyPass: verdict.verifyPass ?? null,
|
|
735
|
+
pass: verdict.pass,
|
|
736
|
+
wallS: verdict.wallS,
|
|
737
|
+
})
|
|
738
|
+
log(
|
|
739
|
+
`gepa seat ${spec.name} inner call ${call}/${budget}: ` +
|
|
740
|
+
`composite=${innerSmokeComposite(verdict)} (${verdict.reason})`,
|
|
741
|
+
)
|
|
742
|
+
return verdict
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const factory: GepaMethodFactory =
|
|
746
|
+
deps.methodFactory ?? gepaOptimizationMethod<GepaSeatScenario, SmokeVerdict>
|
|
747
|
+
const innerJudge = innerSmokeJudge()
|
|
748
|
+
const optimizer =
|
|
749
|
+
deps.optimizer ??
|
|
750
|
+
(deps.methodFactory
|
|
751
|
+
? undefined
|
|
752
|
+
: officialOptimizerModel({
|
|
753
|
+
env: process.env,
|
|
754
|
+
envPrefix: 'GEPA_OPTIMIZER',
|
|
755
|
+
model: process.env.GEPA_OPTIMIZER_MODEL ?? config.arm.driverModel,
|
|
756
|
+
baseUrl:
|
|
757
|
+
process.env.GEPA_OPTIMIZER_BASE_URL ??
|
|
758
|
+
process.env.ROUTER_BASE ??
|
|
759
|
+
'https://router.tangle.tools/v1',
|
|
760
|
+
apiKey: process.env.GEPA_OPTIMIZER_API_KEY ?? process.env.TANGLE_API_KEY ?? '',
|
|
761
|
+
maxCostUsd: spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD,
|
|
762
|
+
maxOutputTokensPerRequest: Number(
|
|
763
|
+
process.env.GEPA_OPTIMIZER_MAX_OUTPUT_TOKENS ?? 16_384,
|
|
764
|
+
),
|
|
765
|
+
}))
|
|
766
|
+
const method = factory({
|
|
767
|
+
name: `gepa-seat:${spec.name}`,
|
|
768
|
+
recipe,
|
|
769
|
+
objective,
|
|
770
|
+
evaluationId: gepaSeatEvaluationId({
|
|
771
|
+
smokeInstanceId: deps.smokeInstanceId,
|
|
772
|
+
dispatchTimeoutMs: config.dispatchTimeoutMs,
|
|
773
|
+
incumbentCommit,
|
|
774
|
+
runnerImplementationRef: deps.runnerImplementationRef,
|
|
775
|
+
judgeImplementationRef: deps.judgeImplementationRef,
|
|
776
|
+
}),
|
|
777
|
+
background,
|
|
778
|
+
describeScenario: (scenario) => ({ id: scenario.id }),
|
|
779
|
+
...(optimizer ? { optimizer } : {}),
|
|
780
|
+
// Upper bound, not expectation: every inner call is a real arm cell.
|
|
781
|
+
timeoutMs: budget * config.dispatchTimeoutMs,
|
|
782
|
+
resume: 'if-compatible',
|
|
783
|
+
trustResumeState: true,
|
|
784
|
+
runner: { command: spec.python ?? DEFAULT_GEPA_PYTHON },
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
const input: OptimizationMethodInput<GepaSeatScenario, SmokeVerdict> = {
|
|
788
|
+
baselineSurface: seed,
|
|
789
|
+
trainScenarios: scenarios.train,
|
|
790
|
+
selectionScenarios: scenarios.selection,
|
|
791
|
+
dispatchWithSurface,
|
|
792
|
+
judges: [innerJudge],
|
|
793
|
+
runDir,
|
|
794
|
+
seed: config.round * 1000 + generation,
|
|
795
|
+
runOptions: {
|
|
796
|
+
storage,
|
|
797
|
+
maxConcurrency: 1,
|
|
798
|
+
dispatchTimeoutMs: config.dispatchTimeoutMs,
|
|
799
|
+
labeledStore: 'off',
|
|
800
|
+
tracing: 'off',
|
|
801
|
+
expectUsage: 'off',
|
|
802
|
+
resumable: false,
|
|
803
|
+
},
|
|
804
|
+
costLedger,
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const started = Date.now()
|
|
808
|
+
const result = await method.optimize(input)
|
|
809
|
+
let innerRun: GepaSeatInnerRun
|
|
810
|
+
try {
|
|
811
|
+
const orderedInnerScores = [...innerScores].sort((a, b) => a.call - b.call)
|
|
812
|
+
innerRun = {
|
|
813
|
+
seat: spec.name,
|
|
814
|
+
engine: spec.engine,
|
|
815
|
+
surface: spec.surface,
|
|
816
|
+
generation,
|
|
817
|
+
budget,
|
|
818
|
+
innerCallCount: orderedInnerScores.length,
|
|
819
|
+
innerScores: orderedInnerScores,
|
|
820
|
+
bestComposite:
|
|
821
|
+
orderedInnerScores.length > 0
|
|
822
|
+
? Math.max(...orderedInnerScores.map((score) => score.composite))
|
|
823
|
+
: null,
|
|
824
|
+
...completeProvenance(result.provenance, spec.name),
|
|
825
|
+
totalCostUsd: result.cost.totalCostUsd,
|
|
826
|
+
accountingComplete: result.cost.accountingComplete,
|
|
827
|
+
incompleteReasons: [...result.cost.incompleteReasons],
|
|
828
|
+
durationMs: Date.now() - started,
|
|
829
|
+
}
|
|
830
|
+
await writeFile(join(runDir, 'inner-provenance.json'), JSON.stringify(innerRun, null, 2))
|
|
831
|
+
await recordGepaSeatInnerRun(config.outDir, innerRun)
|
|
832
|
+
assertCompleteCost(result.cost, spec.name)
|
|
833
|
+
} catch (error) {
|
|
834
|
+
await writeFile(surfacePath, seed)
|
|
835
|
+
throw error
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const winner = result.winnerSurface
|
|
839
|
+
if (typeof winner !== 'string' || winner.trim().length === 0) {
|
|
840
|
+
await writeFile(surfacePath, seed)
|
|
841
|
+
throw new Error(`gepa seat '${spec.name}': adapter returned a non-string winner surface`)
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (winner === seed) {
|
|
845
|
+
// Restore the seed (the last inner call may have left another candidate)
|
|
846
|
+
// and decline the slot — an unchanged surface has no candidate diff.
|
|
847
|
+
await writeFile(surfacePath, seed)
|
|
848
|
+
return {
|
|
849
|
+
applied: false,
|
|
850
|
+
summary: `gepa ${spec.engine}: best candidate equals the seed after ${innerScores.length} inner call(s)`,
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
await writeFile(surfacePath, winner)
|
|
855
|
+
if (config.activationGate === true) {
|
|
856
|
+
const predicate = mechanicalActivationPredicate(seed, winner, spec.surface)
|
|
857
|
+
if (predicate === null) {
|
|
858
|
+
await writeFile(surfacePath, seed)
|
|
859
|
+
return {
|
|
860
|
+
applied: false,
|
|
861
|
+
summary:
|
|
862
|
+
`gepa ${spec.engine}: winner adds no line of >=${MIN_PREDICATE_LINE_CHARS} chars — ` +
|
|
863
|
+
'cannot derive a machine-checkable activation predicate; candidate declined',
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const predicatePath = join(args.worktreePath, ACTIVATION_PREDICATE_RELPATH)
|
|
867
|
+
await mkdir(dirname(predicatePath), { recursive: true })
|
|
868
|
+
await writeFile(predicatePath, JSON.stringify(predicate, null, 2))
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
applied: true,
|
|
872
|
+
summary:
|
|
873
|
+
`gepa ${spec.engine} optimized ${spec.surface} over ${innerScores.length}/${budget} inner smoke call(s); ` +
|
|
874
|
+
`best inner composite ${innerRun.bestComposite}`,
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|