@tangle-network/agent-bench 0.3.6 → 0.3.8
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/dist/adapters.js +24 -24
- 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/humaneval.d.ts +10 -1
- package/dist/benchmarks/humaneval.js +5 -3
- 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-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
- package/dist/chunk-5H5XV76F.js.map +1 -0
- 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-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
- 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 +24 -24
- package/package.json +6 -5
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- 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 +338 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/activation.mts +228 -0
- package/src/swe-arena/activation.test.mts +303 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +804 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +217 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +437 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- 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/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -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/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -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/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +237 -0
- package/src/swe-arena/gepa-seat.mts +583 -0
- package/src/swe-arena/gepa-seat.test.mts +635 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/lineage-record.mts +164 -0
- package/src/swe-arena/lineage-record.test.mts +115 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2795 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- 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/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +770 -0
- package/src/swe-arena/proposer-fanout.test.mts +619 -0
- package/src/swe-arena/proposer-provenance.mts +177 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +727 -0
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +500 -0
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +218 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
- package/src/swe-structural.mts +245 -837
- package/dist/chunk-LRRD7NAG.js.map +0 -1
- package/dist/chunk-PPYSEKFM.js.map +0 -1
- /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-5SBJCB6W.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,583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GEN-6 GEPA proposer seat — agent-eval's external-GEPA adapter
|
|
3
|
+
* (`gepaOptimizationMethod`, tangle-network/agent-eval PRs #408/#409,
|
|
4
|
+
* main@58a28aa) wired as ONE seat in the swe-arena proposer fan-out.
|
|
5
|
+
*
|
|
6
|
+
* Two-tier evaluator, the critical shape:
|
|
7
|
+
*
|
|
8
|
+
* INNER (what GEPA's own loop calls, many times, budget-capped): the
|
|
9
|
+
* candidate is ONE change-space file's content as a string. Each inner call
|
|
10
|
+
* materializes the candidate string into the seat's scratch loops worktree
|
|
11
|
+
* (the rest of the repo stays at the incumbent commit) and runs the EXISTING
|
|
12
|
+
* pre-filter smoke cell — one PUBLIC instance, the cheap path — through the
|
|
13
|
+
* injected `SmokeRunner`. Score = smoke resolve (1/0) + verify-pass fraction
|
|
14
|
+
* as a bounded tiebreak. Inner calls are capped by `maxMetricCalls`
|
|
15
|
+
* (default 10; each smoke costs minutes of arm time).
|
|
16
|
+
*
|
|
17
|
+
* OUTER: GEPA's best candidate is written back to the surface file in the
|
|
18
|
+
* scratch worktree and the seat returns `applied: true` — from there the
|
|
19
|
+
* fan-out treats it EXACTLY like any other author's work: change-space
|
|
20
|
+
* check, activation-predicate gate, smoke pre-filter, then the full exam,
|
|
21
|
+
* with staircase label = the seat name (`gepa-author`).
|
|
22
|
+
*
|
|
23
|
+
* DATA BOUNDARIES (both fail-closed):
|
|
24
|
+
* - PUBLIC ONLY crosses the bridge: the only scenario ids serialized to the
|
|
25
|
+
* GEPA process name the public smoke instance; `assertNoPrivateLeak`
|
|
26
|
+
* re-checks every string headed to the bridge against the score split.
|
|
27
|
+
* - Holdout/final cases NEVER cross: the adapter's own API has no test-set
|
|
28
|
+
* field (`GepaBridgeInput` in agent-eval src/campaign/gepa-optimization-
|
|
29
|
+
* method.ts — "The final comparison cases are not accepted by this API and
|
|
30
|
+
* cannot be serialized here"), and its Python side hard-rejects one
|
|
31
|
+
* (`gepa_bridge.py` `_validate_input`: `if "testSet" in value ... raise`).
|
|
32
|
+
* This module never mentions holdout instances to begin with.
|
|
33
|
+
*
|
|
34
|
+
* RUNTIME SEAMS (both fail LOUD at provenance time, t=0, mirroring the codex
|
|
35
|
+
* seat's auth check — a dead seat cannot be silently skipped mid-run):
|
|
36
|
+
* - Node: the installed @tangle-network/agent-eval must export
|
|
37
|
+
* `gepaOptimizationMethod` (0.123.x predates it) — `loadGepaMethodFactory`
|
|
38
|
+
* throws with the exact upgrade instruction otherwise.
|
|
39
|
+
* - Python: `agent_eval_rpc.gepa_bridge` + a GEPA build with
|
|
40
|
+
* `optimize_anything`/`OptimizeAnythingConfig` must import —
|
|
41
|
+
* `probeGepaRuntime` throws with the pip install instruction otherwise.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { createHash } from 'node:crypto'
|
|
45
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
46
|
+
import { dirname, join } from 'node:path'
|
|
47
|
+
import type {
|
|
48
|
+
DispatchContext,
|
|
49
|
+
JudgeConfig,
|
|
50
|
+
MutableSurface,
|
|
51
|
+
OptimizationMethod,
|
|
52
|
+
OptimizationMethodInput,
|
|
53
|
+
Scenario,
|
|
54
|
+
} from '@tangle-network/agent-eval/campaign'
|
|
55
|
+
import { ACTIVATION_PREDICATE_RELPATH, type ActivationPredicate } from './activation.mts'
|
|
56
|
+
import { changeSpaceViolations, type OuterLoopConfig } from './outer-loop.mts'
|
|
57
|
+
import type { AuthorFn, ProposerSpec, SmokeRunner, SmokeVerdict } from './proposer-fanout.mts'
|
|
58
|
+
import type { ScoreSplit } from './score-split.mts'
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Spec.
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
export const GEPA_ENGINES = ['gepa', 'omni'] as const
|
|
65
|
+
export type GepaEngineName = (typeof GEPA_ENGINES)[number]
|
|
66
|
+
|
|
67
|
+
export const DEFAULT_MAX_METRIC_CALLS = 10
|
|
68
|
+
export const DEFAULT_MAX_PROPOSER_COST_USD = 10
|
|
69
|
+
/** Omni = 3 bounded explore runs + 1 continuation (GEPA's published shape). */
|
|
70
|
+
export const OMNI_RUN_COUNT = 4
|
|
71
|
+
|
|
72
|
+
/** A `ProposerSpec` whose `engine` marks it as a GEPA seat. */
|
|
73
|
+
export type GepaSeatSpec = ProposerSpec & { engine: GepaEngineName; surface: string }
|
|
74
|
+
|
|
75
|
+
export function isGepaSeat(spec: ProposerSpec): spec is GepaSeatSpec {
|
|
76
|
+
return spec.engine !== undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Fail-closed spec validation, run at generator construction. A GEPA seat is
|
|
80
|
+
* an ENGINE invocation: harness/profile/model/merge belong to CLI-authored
|
|
81
|
+
* seats and are rejected here rather than silently ignored. */
|
|
82
|
+
export function validateGepaSeat(spec: ProposerSpec): asserts spec is GepaSeatSpec {
|
|
83
|
+
const label = `gepa seat '${spec.name}'`
|
|
84
|
+
if (spec.engine === undefined || !GEPA_ENGINES.includes(spec.engine)) {
|
|
85
|
+
throw new Error(`${label}: engine must be one of ${GEPA_ENGINES.join('|')}, got ${JSON.stringify(spec.engine)}`)
|
|
86
|
+
}
|
|
87
|
+
if (typeof spec.surface !== 'string' || spec.surface.length === 0) {
|
|
88
|
+
throw new Error(`${label}: surface is required — the ONE repo-relative file GEPA optimizes as a string`)
|
|
89
|
+
}
|
|
90
|
+
const violations = changeSpaceViolations([spec.surface])
|
|
91
|
+
if (violations.length > 0) {
|
|
92
|
+
throw new Error(`${label}: surface ${JSON.stringify(spec.surface)} is outside the declared change-space`)
|
|
93
|
+
}
|
|
94
|
+
for (const field of ['harness', 'profile', 'model', 'merge', 'lens', 'diagnosisSlice'] as const) {
|
|
95
|
+
if (spec[field] !== undefined) {
|
|
96
|
+
throw new Error(`${label}: field '${field}' belongs to harness-authored seats and must be unset on an engine seat`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
100
|
+
if (!Number.isSafeInteger(calls) || calls <= 0) {
|
|
101
|
+
throw new Error(`${label}: maxMetricCalls must be a positive integer, got ${JSON.stringify(spec.maxMetricCalls)}`)
|
|
102
|
+
}
|
|
103
|
+
if (spec.engine === 'omni' && calls < OMNI_RUN_COUNT) {
|
|
104
|
+
throw new Error(`${label}: engine 'omni' runs ${OMNI_RUN_COUNT} bounded engine runs and needs maxMetricCalls >= ${OMNI_RUN_COUNT}, got ${calls}`)
|
|
105
|
+
}
|
|
106
|
+
const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
|
|
107
|
+
if (!Number.isFinite(cost) || cost <= 0) {
|
|
108
|
+
throw new Error(`${label}: maxProposerCostUsd must be a positive finite number, got ${JSON.stringify(spec.maxProposerCostUsd)}`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Recipe — the adapter's own shape, mirrored structurally (the installed
|
|
114
|
+
// agent-eval may predate the export; see loadGepaMethodFactory).
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
export interface GepaEngineRun {
|
|
118
|
+
engine: string
|
|
119
|
+
maxEvaluations: number
|
|
120
|
+
maxProposerCostUsd: number
|
|
121
|
+
engineConfig?: Record<string, unknown>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type GepaOptimizationRecipe =
|
|
125
|
+
| { kind: 'engine'; run: GepaEngineRun }
|
|
126
|
+
| { kind: 'best-of-then-continue'; explore: readonly GepaEngineRun[]; continueWith: GepaEngineRun }
|
|
127
|
+
|
|
128
|
+
/** Build the bounded recipe for a seat. The TOTAL inner-evaluation budget is
|
|
129
|
+
* exactly `maxMetricCalls` — the adapter's local callback enforces the sum
|
|
130
|
+
* of per-run limits, and the seat's own dispatch wrapper re-enforces it. */
|
|
131
|
+
export function recipeForSeat(spec: GepaSeatSpec): GepaOptimizationRecipe {
|
|
132
|
+
const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
133
|
+
const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
|
|
134
|
+
if (spec.engine === 'gepa') {
|
|
135
|
+
return { kind: 'engine', run: { engine: 'gepa', maxEvaluations: calls, maxProposerCostUsd: cost } }
|
|
136
|
+
}
|
|
137
|
+
// Omni: explore {gepa, autoresearch, meta_harness} then continue with gepa,
|
|
138
|
+
// splitting the call budget so the four bounded runs sum to `calls`.
|
|
139
|
+
const perExplore = Math.max(1, Math.floor(calls / OMNI_RUN_COUNT))
|
|
140
|
+
const continueCalls = calls - 3 * perExplore
|
|
141
|
+
const perRunCost = cost / OMNI_RUN_COUNT
|
|
142
|
+
const explore = ['gepa', 'autoresearch', 'meta_harness'].map((engine) => ({
|
|
143
|
+
engine,
|
|
144
|
+
maxEvaluations: perExplore,
|
|
145
|
+
maxProposerCostUsd: perRunCost,
|
|
146
|
+
}))
|
|
147
|
+
return {
|
|
148
|
+
kind: 'best-of-then-continue',
|
|
149
|
+
explore,
|
|
150
|
+
continueWith: { engine: 'gepa', maxEvaluations: continueCalls, maxProposerCostUsd: perRunCost },
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function recipeEvaluationBudget(recipe: GepaOptimizationRecipe): number {
|
|
155
|
+
const runs = recipe.kind === 'engine' ? [recipe.run] : [...recipe.explore, recipe.continueWith]
|
|
156
|
+
return runs.reduce((sum, run) => sum + run.maxEvaluations, 0)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Public-only bridge examples.
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
export interface GepaSeatScenario extends Scenario {
|
|
164
|
+
/** The PUBLIC smoke instance this scenario dispatches to. */
|
|
165
|
+
smokeIid: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Throws when any private instance id appears in text headed to the bridge. */
|
|
169
|
+
export function assertNoPrivateLeak(
|
|
170
|
+
text: string,
|
|
171
|
+
split: Pick<ScoreSplit, 'privateInstances'> | null,
|
|
172
|
+
what: string,
|
|
173
|
+
): void {
|
|
174
|
+
if (split === null) return
|
|
175
|
+
const leaked = split.privateInstances.filter((iid) => text.includes(iid))
|
|
176
|
+
if (leaked.length > 0) {
|
|
177
|
+
throw new Error(`gepa seat: ${what} would leak private instance id(s) [${leaked.join(', ')}] to the GEPA bridge`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The ONLY scenarios the bridge ever sees: the public smoke instance as the
|
|
182
|
+
* train example plus a distinct-id alias as the selection example (the
|
|
183
|
+
* adapter requires disjoint train/selection ids; both dispatch to the same
|
|
184
|
+
* smoke cell). Fails loud when the smoke instance is private. */
|
|
185
|
+
export function gepaBridgeScenarios(
|
|
186
|
+
smokeIid: string,
|
|
187
|
+
split: Pick<ScoreSplit, 'privateInstances'> | null,
|
|
188
|
+
): { train: GepaSeatScenario[]; selection: GepaSeatScenario[] } {
|
|
189
|
+
if (split !== null && split.privateInstances.includes(smokeIid)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`gepa seat: smoke instance ${smokeIid} is PRIVATE under the score split — private ids never cross the bridge`,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
assertNoPrivateLeak(smokeIid, split, `smoke instance id '${smokeIid}'`)
|
|
195
|
+
return {
|
|
196
|
+
train: [{ id: smokeIid, kind: 'swe-smoke', smokeIid }],
|
|
197
|
+
selection: [{ id: `${smokeIid}::selection`, kind: 'swe-smoke', smokeIid }],
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Inner score.
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
/** Resolve dominates; verify-pass is a bounded tiebreak that can never beat a
|
|
206
|
+
* resolve (0.25 < 1). Range {0, 0.25, 1, 1.25}. */
|
|
207
|
+
export function innerSmokeComposite(verdict: Pick<SmokeVerdict, 'resolved' | 'verifyPass'>): number {
|
|
208
|
+
return (verdict.resolved === true ? 1 : 0) + (verdict.verifyPass === true ? 0.25 : 0)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function innerSmokeJudge(): JudgeConfig<SmokeVerdict, GepaSeatScenario> {
|
|
212
|
+
return {
|
|
213
|
+
name: 'gepa-inner-smoke',
|
|
214
|
+
judgeVersion: 'gepa-inner-smoke.v1',
|
|
215
|
+
dimensions: [
|
|
216
|
+
{ key: 'resolved', description: 'Official SWE-bench judge verdict for the smoke cell (1 resolved / 0 not).' },
|
|
217
|
+
{ key: 'verifyPass', description: 'Committed verify fixture passed for the smoke cell (tiebreak).' },
|
|
218
|
+
],
|
|
219
|
+
score: ({ artifact }) => ({
|
|
220
|
+
dimensions: {
|
|
221
|
+
resolved: artifact.resolved === true ? 1 : 0,
|
|
222
|
+
verifyPass: artifact.verifyPass === true ? 1 : 0,
|
|
223
|
+
},
|
|
224
|
+
composite: innerSmokeComposite(artifact),
|
|
225
|
+
notes: artifact.reason,
|
|
226
|
+
}),
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Runtime seams — Node adapter export + Python bridge, both loud.
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
export const GEPA_ADAPTER_UPGRADE_HINT =
|
|
235
|
+
"the installed @tangle-network/agent-eval does not export gepaOptimizationMethod — " +
|
|
236
|
+
'upgrade to a release containing tangle-network/agent-eval PRs #408/#409 (merged at main@58a28aa; ' +
|
|
237
|
+
'first release after 0.123.5), then reinstall bench deps'
|
|
238
|
+
|
|
239
|
+
export const GEPA_PYTHON_INSTALL_HINT =
|
|
240
|
+
"install the optional Python bridge: pip install 'agent-eval-rpc[gepa]' " +
|
|
241
|
+
'(the extra pins the GEPA source commit providing optimize_anything/OptimizeAnythingConfig; ' +
|
|
242
|
+
'published gepa<=0.1.4 does not contain the multi-engine API — see agent-eval docs/campaign-proposers.md)'
|
|
243
|
+
|
|
244
|
+
/** Adapter config, mirrored structurally from agent-eval's
|
|
245
|
+
* `GepaOptimizationMethodConfig` (src/campaign/gepa-optimization-method.ts). */
|
|
246
|
+
export interface GepaMethodConfig {
|
|
247
|
+
name?: string
|
|
248
|
+
recipe: GepaOptimizationRecipe
|
|
249
|
+
objective: string
|
|
250
|
+
background?: string
|
|
251
|
+
maxCandidateChars?: number
|
|
252
|
+
timeoutMs?: number
|
|
253
|
+
describeScenario?: (scenario: GepaSeatScenario) => unknown
|
|
254
|
+
runner?: { command?: string; args?: readonly string[]; cwd?: string; env?: NodeJS.ProcessEnv }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type GepaMethodFactory = (
|
|
258
|
+
config: GepaMethodConfig,
|
|
259
|
+
) => OptimizationMethod<GepaSeatScenario, SmokeVerdict>
|
|
260
|
+
|
|
261
|
+
export type CampaignModuleImport = () => Promise<Record<string, unknown>>
|
|
262
|
+
|
|
263
|
+
const defaultImportCampaign: CampaignModuleImport = () =>
|
|
264
|
+
import('@tangle-network/agent-eval/campaign') as Promise<Record<string, unknown>>
|
|
265
|
+
|
|
266
|
+
/** Resolve the adapter factory from the installed agent-eval, or throw the
|
|
267
|
+
* exact upgrade instruction. Checked at provenance time (t=0) AND at author
|
|
268
|
+
* time, so a stale install can never silently skip the seat. */
|
|
269
|
+
export async function loadGepaMethodFactory(
|
|
270
|
+
importCampaign: CampaignModuleImport = defaultImportCampaign,
|
|
271
|
+
): Promise<GepaMethodFactory> {
|
|
272
|
+
const mod = await importCampaign()
|
|
273
|
+
const factory = mod['gepaOptimizationMethod']
|
|
274
|
+
if (typeof factory !== 'function') {
|
|
275
|
+
throw new Error(`gepa seat: ${GEPA_ADAPTER_UPGRADE_HINT}`)
|
|
276
|
+
}
|
|
277
|
+
return factory as GepaMethodFactory
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export type ProbeExec = (
|
|
281
|
+
command: string,
|
|
282
|
+
args: string[],
|
|
283
|
+
) => Promise<{ code: number | null; stdout: string; stderr: string }>
|
|
284
|
+
|
|
285
|
+
export interface GepaRuntimeProbe {
|
|
286
|
+
pythonVersion: string
|
|
287
|
+
gepaVersion: string
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const DEFAULT_GEPA_PYTHON = 'python3'
|
|
291
|
+
|
|
292
|
+
/** Prove the Python side of the bridge can run, or throw install
|
|
293
|
+
* instructions. Mirrors the codex seat's login-status gate: run at t=0 so a
|
|
294
|
+
* dead seat fails the launch, never a mid-run candidate slot. */
|
|
295
|
+
export async function probeGepaRuntime(python: string, exec: ProbeExec, seatName: string): Promise<GepaRuntimeProbe> {
|
|
296
|
+
const version = await exec(python, ['--version'])
|
|
297
|
+
if (version.code !== 0) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`gepa seat '${seatName}': '${python} --version' failed (rc=${version.code}) — ${GEPA_PYTHON_INSTALL_HINT}`,
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
const bridge = await exec(python, ['-c', 'import agent_eval_rpc.gepa_bridge'])
|
|
303
|
+
if (bridge.code !== 0) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`gepa seat '${seatName}': GEPA Python runtime is not installed ` +
|
|
306
|
+
`(python=${python}; 'import agent_eval_rpc.gepa_bridge' failed: ${bridge.stderr.trim().slice(0, 300)}). ` +
|
|
307
|
+
GEPA_PYTHON_INSTALL_HINT,
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
const gepa = await exec(python, [
|
|
311
|
+
'-c',
|
|
312
|
+
"from gepa.optimize_anything import optimize_anything, OptimizeAnythingConfig; " +
|
|
313
|
+
"import gepa; print(getattr(gepa, '__version__', 'source'))",
|
|
314
|
+
])
|
|
315
|
+
if (gepa.code !== 0) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`gepa seat '${seatName}': installed gepa lacks the multi-engine optimize_anything API ` +
|
|
318
|
+
`(${gepa.stderr.trim().slice(0, 300)}). ` +
|
|
319
|
+
GEPA_PYTHON_INSTALL_HINT,
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
return { pythonVersion: (version.stdout + version.stderr).trim(), gepaVersion: gepa.stdout.trim() }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Inner-run provenance.
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
export interface GepaInnerCall {
|
|
330
|
+
call: number
|
|
331
|
+
scenarioId: string
|
|
332
|
+
smokeIid: string
|
|
333
|
+
candidateSha256: string
|
|
334
|
+
composite: number
|
|
335
|
+
resolved: boolean | null
|
|
336
|
+
verifyPass: boolean | null
|
|
337
|
+
pass: boolean
|
|
338
|
+
wallS: number
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface GepaSeatInnerRun {
|
|
342
|
+
seat: string
|
|
343
|
+
engine: GepaEngineName
|
|
344
|
+
surface: string
|
|
345
|
+
generation: number
|
|
346
|
+
budget: number
|
|
347
|
+
innerCallCount: number
|
|
348
|
+
innerScores: GepaInnerCall[]
|
|
349
|
+
bestComposite: number | null
|
|
350
|
+
adapterReportedCostUsd: number | null
|
|
351
|
+
adapterCostAccountingComplete: boolean
|
|
352
|
+
durationMs: number
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export const PROPOSER_PROVENANCE_FILENAME = 'proposer-provenance.json'
|
|
356
|
+
|
|
357
|
+
/** Merge one seat run's inner-call record into `proposer-provenance.json`
|
|
358
|
+
* under `gepaInnerRuns` (additive; the t=0 capture record is preserved). */
|
|
359
|
+
export async function recordGepaSeatInnerRun(outDir: string, run: GepaSeatInnerRun): Promise<void> {
|
|
360
|
+
const path = join(outDir, PROPOSER_PROVENANCE_FILENAME)
|
|
361
|
+
let record: Record<string, unknown> = {}
|
|
362
|
+
try {
|
|
363
|
+
record = JSON.parse(await readFile(path, 'utf8')) as Record<string, unknown>
|
|
364
|
+
} catch {
|
|
365
|
+
// No capture record yet (unit-test or crash-before-write): still persist.
|
|
366
|
+
}
|
|
367
|
+
const runs = Array.isArray(record.gepaInnerRuns) ? (record.gepaInnerRuns as unknown[]) : []
|
|
368
|
+
runs.push(run)
|
|
369
|
+
record.gepaInnerRuns = runs
|
|
370
|
+
await mkdir(dirname(path), { recursive: true })
|
|
371
|
+
await writeFile(path, JSON.stringify(record, null, 2))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Mechanical activation predicate (gen-5 activation gate).
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
379
|
+
|
|
380
|
+
export const MIN_PREDICATE_LINE_CHARS = 12
|
|
381
|
+
|
|
382
|
+
/** Derive a machine-checkable predicate from the surface change: the longest
|
|
383
|
+
* ADDED line must render in the candidate's own run artifacts (for a prompt
|
|
384
|
+
* surface, the changed text appearing in composed prompts IS the mechanism
|
|
385
|
+
* firing). Returns null when no added line is distinctive enough — the
|
|
386
|
+
* caller fails the candidate loud instead of shipping an unverifiable one. */
|
|
387
|
+
export function mechanicalActivationPredicate(
|
|
388
|
+
seed: string,
|
|
389
|
+
winner: string,
|
|
390
|
+
surface: string,
|
|
391
|
+
): ActivationPredicate | null {
|
|
392
|
+
const seedLines = new Set(seed.split('\n').map((l) => l.trim()))
|
|
393
|
+
const added = winner
|
|
394
|
+
.split('\n')
|
|
395
|
+
.map((l) => l.trim())
|
|
396
|
+
.filter((l) => l.length >= MIN_PREDICATE_LINE_CHARS && !seedLines.has(l))
|
|
397
|
+
if (added.length === 0) return null
|
|
398
|
+
const line = added.reduce((a, b) => (b.length > a.length ? b : a))
|
|
399
|
+
return {
|
|
400
|
+
version: 'v1',
|
|
401
|
+
description: `gepa-author surface change fired: candidate text from ${surface} appears in run artifacts`,
|
|
402
|
+
kind: 'grep',
|
|
403
|
+
pattern: escapeRegExp(line),
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// The seat author.
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
export interface GepaSeatDeps {
|
|
412
|
+
smokeRunner: SmokeRunner
|
|
413
|
+
/** Resolved PUBLIC smoke instance (outer-loop restricts the choice to the
|
|
414
|
+
* split's public set; re-asserted here fail-closed). */
|
|
415
|
+
smokeInstanceId: string
|
|
416
|
+
scoreSplit: Pick<ScoreSplit, 'privateInstances'> | null
|
|
417
|
+
/** Test seam. Default: checked dynamic import of the installed adapter. */
|
|
418
|
+
methodFactory?: GepaMethodFactory
|
|
419
|
+
log?: (msg: string) => void
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const sha256 = (s: string): string => `sha256:${createHash('sha256').update(s).digest('hex')}`
|
|
423
|
+
|
|
424
|
+
/** Build the seat's `AuthorFn`. The fan-out calls it with the seat's scratch
|
|
425
|
+
* worktree (checked out at the incumbent commit); everything this function
|
|
426
|
+
* leaves in that worktree becomes the candidate diff. */
|
|
427
|
+
export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): AuthorFn {
|
|
428
|
+
const log = deps.log ?? (() => {})
|
|
429
|
+
return async (proposer, args) => {
|
|
430
|
+
validateGepaSeat(proposer)
|
|
431
|
+
const spec: GepaSeatSpec = proposer
|
|
432
|
+
const generation = args.generation ?? 0
|
|
433
|
+
const budget = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
434
|
+
const recipe = recipeForSeat(spec)
|
|
435
|
+
const scenarios = gepaBridgeScenarios(deps.smokeInstanceId, deps.scoreSplit)
|
|
436
|
+
const surfacePath = join(args.worktreePath, spec.surface)
|
|
437
|
+
const seed = await readFile(surfacePath, 'utf8').catch(() => {
|
|
438
|
+
throw new Error(`gepa seat '${spec.name}': surface ${spec.surface} does not exist at the incumbent commit`)
|
|
439
|
+
})
|
|
440
|
+
const runDir = join(config.outDir, 'gepa-seat', `gen${generation}-${spec.name.replace(/[^a-zA-Z0-9_-]/g, '_')}`)
|
|
441
|
+
await mkdir(runDir, { recursive: true })
|
|
442
|
+
|
|
443
|
+
const objective =
|
|
444
|
+
`Improve the supervisor-loop file '${spec.surface}' (returned as the COMPLETE new file content) so the ` +
|
|
445
|
+
'SWE-bench smoke evaluation scores higher. Score = 1 for an officially resolved instance plus 0.25 when ' +
|
|
446
|
+
'the verify fixture passes. Keep the file coherent and self-contained; only its content is applied.'
|
|
447
|
+
const background =
|
|
448
|
+
`The candidate string replaces ${spec.surface} in a checkout of the loops supervisor repo; every other ` +
|
|
449
|
+
'file stays at the incumbent commit. Each evaluation runs one real SWE-bench instance end-to-end and ' +
|
|
450
|
+
'takes minutes — spend evaluations deliberately.'
|
|
451
|
+
assertNoPrivateLeak(objective + background + JSON.stringify([...scenarios.train, ...scenarios.selection]),
|
|
452
|
+
deps.scoreSplit, 'bridge payload')
|
|
453
|
+
|
|
454
|
+
const innerScores: GepaInnerCall[] = []
|
|
455
|
+
const dispatchWithSurface = async (
|
|
456
|
+
surface: MutableSurface,
|
|
457
|
+
scenario: GepaSeatScenario,
|
|
458
|
+
_ctx: DispatchContext,
|
|
459
|
+
): Promise<SmokeVerdict> => {
|
|
460
|
+
if (typeof surface !== 'string') {
|
|
461
|
+
throw new Error(`gepa seat '${spec.name}': candidate surface must be a string`)
|
|
462
|
+
}
|
|
463
|
+
if (innerScores.length >= budget) {
|
|
464
|
+
// Defense-in-depth: the adapter's callback enforces the same cap.
|
|
465
|
+
throw new Error(`gepa seat '${spec.name}': inner-call budget ${budget} exhausted`)
|
|
466
|
+
}
|
|
467
|
+
await writeFile(surfacePath, surface)
|
|
468
|
+
const verdict = await deps.smokeRunner({
|
|
469
|
+
scratchPath: args.worktreePath,
|
|
470
|
+
generation,
|
|
471
|
+
proposer: spec,
|
|
472
|
+
...(args.costLedger ? { costLedger: args.costLedger } : {}),
|
|
473
|
+
})
|
|
474
|
+
if (deps.scoreSplit !== null && deps.scoreSplit.privateInstances.includes(verdict.iid)) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`gepa seat '${spec.name}': smoke ran PRIVATE instance ${verdict.iid} — refusing to feed its score to the bridge`,
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
innerScores.push({
|
|
480
|
+
call: innerScores.length + 1,
|
|
481
|
+
scenarioId: scenario.id,
|
|
482
|
+
smokeIid: verdict.iid,
|
|
483
|
+
candidateSha256: sha256(surface),
|
|
484
|
+
composite: innerSmokeComposite(verdict),
|
|
485
|
+
resolved: verdict.resolved,
|
|
486
|
+
verifyPass: verdict.verifyPass ?? null,
|
|
487
|
+
pass: verdict.pass,
|
|
488
|
+
wallS: verdict.wallS,
|
|
489
|
+
})
|
|
490
|
+
log(
|
|
491
|
+
`gepa seat ${spec.name} inner call ${innerScores.length}/${budget}: ` +
|
|
492
|
+
`composite=${innerSmokeComposite(verdict)} (${verdict.reason})`,
|
|
493
|
+
)
|
|
494
|
+
return verdict
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const factory = deps.methodFactory ?? (await loadGepaMethodFactory())
|
|
498
|
+
const method = factory({
|
|
499
|
+
name: `gepa-seat:${spec.name}`,
|
|
500
|
+
recipe,
|
|
501
|
+
objective,
|
|
502
|
+
background,
|
|
503
|
+
describeScenario: (scenario) => ({ id: scenario.id }),
|
|
504
|
+
// Ceiling, not expectation: every inner call is a real arm cell.
|
|
505
|
+
timeoutMs: budget * config.dispatchTimeoutMs,
|
|
506
|
+
runner: { command: spec.python ?? DEFAULT_GEPA_PYTHON },
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
const input: OptimizationMethodInput<GepaSeatScenario, SmokeVerdict> = {
|
|
510
|
+
baselineSurface: seed,
|
|
511
|
+
trainScenarios: scenarios.train,
|
|
512
|
+
selectionScenarios: scenarios.selection,
|
|
513
|
+
dispatchWithSurface,
|
|
514
|
+
judges: [innerSmokeJudge()],
|
|
515
|
+
runDir,
|
|
516
|
+
seed: config.round * 1000 + generation,
|
|
517
|
+
runOptions: {
|
|
518
|
+
maxConcurrency: 1,
|
|
519
|
+
dispatchTimeoutMs: config.dispatchTimeoutMs,
|
|
520
|
+
labeledStore: 'off',
|
|
521
|
+
tracing: 'off',
|
|
522
|
+
expectUsage: 'off',
|
|
523
|
+
resumable: false,
|
|
524
|
+
},
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const started = Date.now()
|
|
528
|
+
const result = await method.optimize(input)
|
|
529
|
+
const winner = result.winnerSurface
|
|
530
|
+
if (typeof winner !== 'string' || winner.trim().length === 0) {
|
|
531
|
+
throw new Error(`gepa seat '${spec.name}': adapter returned a non-string winner surface`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const innerRun: GepaSeatInnerRun = {
|
|
535
|
+
seat: spec.name,
|
|
536
|
+
engine: spec.engine,
|
|
537
|
+
surface: spec.surface,
|
|
538
|
+
generation,
|
|
539
|
+
budget,
|
|
540
|
+
innerCallCount: innerScores.length,
|
|
541
|
+
innerScores,
|
|
542
|
+
bestComposite: innerScores.length > 0 ? Math.max(...innerScores.map((s) => s.composite)) : null,
|
|
543
|
+
adapterReportedCostUsd: result.cost.totalCostUsd,
|
|
544
|
+
adapterCostAccountingComplete: result.cost.accountingComplete,
|
|
545
|
+
durationMs: Date.now() - started,
|
|
546
|
+
}
|
|
547
|
+
await writeFile(join(runDir, 'inner-provenance.json'), JSON.stringify(innerRun, null, 2))
|
|
548
|
+
await recordGepaSeatInnerRun(config.outDir, innerRun)
|
|
549
|
+
|
|
550
|
+
if (winner === seed) {
|
|
551
|
+
// Restore the seed (the last inner call may have left another candidate)
|
|
552
|
+
// and decline the slot — an unchanged surface has no candidate diff.
|
|
553
|
+
await writeFile(surfacePath, seed)
|
|
554
|
+
return {
|
|
555
|
+
applied: false,
|
|
556
|
+
summary: `gepa ${spec.engine}: best candidate equals the seed after ${innerScores.length} inner call(s)`,
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
await writeFile(surfacePath, winner)
|
|
561
|
+
if (config.activationGate === true) {
|
|
562
|
+
const predicate = mechanicalActivationPredicate(seed, winner, spec.surface)
|
|
563
|
+
if (predicate === null) {
|
|
564
|
+
await writeFile(surfacePath, seed)
|
|
565
|
+
return {
|
|
566
|
+
applied: false,
|
|
567
|
+
summary:
|
|
568
|
+
`gepa ${spec.engine}: winner adds no line of >=${MIN_PREDICATE_LINE_CHARS} chars — ` +
|
|
569
|
+
'cannot derive a machine-checkable activation predicate; candidate declined',
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const predicatePath = join(args.worktreePath, ACTIVATION_PREDICATE_RELPATH)
|
|
573
|
+
await mkdir(dirname(predicatePath), { recursive: true })
|
|
574
|
+
await writeFile(predicatePath, JSON.stringify(predicate, null, 2))
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
applied: true,
|
|
578
|
+
summary:
|
|
579
|
+
`gepa ${spec.engine} optimized ${spec.surface} over ${innerScores.length}/${budget} inner smoke call(s); ` +
|
|
580
|
+
`best inner composite ${innerRun.bestComposite}`,
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|