@tangle-network/agent-bench 0.3.8 → 0.4.1
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 +13 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +4 -1
- package/dist/benchmarks/_harness.js +3 -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-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
- package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
- package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
- package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
- package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
- package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
- package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
- package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
- package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
- package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
- package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
- package/dist/chunk-EIETHPD5.js.map +1 -0
- package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
- package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
- package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
- package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
- package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
- package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
- package/dist/chunk-JSQOUKXS.js.map +1 -0
- package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
- package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
- package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
- package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
- package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
- package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +4 -4
- package/pier_agents/candidate_contract.py +238 -24
- package/pier_agents/tangle_candidate.py +75 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +65 -1
- package/scripts/verify-pier-agent.mts +6 -4
- package/src/benchmarks/_harness.test.mts +16 -1
- package/src/benchmarks/_harness.ts +9 -2
- package/src/benchmarks/terminal-bench.ts +2 -1
- 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/rollout-ledger/backfill-swe-arena.test.mts +28 -24
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +1 -4
- package/src/swe-arena/activation.test.mts +10 -13
- package/src/swe-arena/gepa-seat.mts +425 -131
- package/src/swe-arena/gepa-seat.test.mts +524 -100
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/outer-loop.mts +103 -76
- package/src/swe-arena/proposer-fanout.mts +51 -36
- package/src/swe-arena/proposer-fanout.test.mts +0 -1
- package/src/swe-arena/proposer-provenance.mts +11 -16
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -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/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-WSKWVEQB.js.map +0 -1
- package/dist/chunk-XYA4XSNU.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- package/src/swe-arena/lineage-record.mts +0 -164
- package/src/swe-arena/lineage-record.test.mts +0 -115
- /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
- /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
- /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
- /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
- /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
- /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
- /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
- /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
- /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
- /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
- /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
- /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
- /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
- /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
- /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
- /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
- /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
- /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
- /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
- /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
- /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* main@58a28aa) wired as ONE seat in the swe-arena proposer fan-out.
|
|
2
|
+
* GEPA proposer seat using agent-eval's official
|
|
3
|
+
* `gepaOptimizationMethod` as one author in the swe-arena fan-out.
|
|
5
4
|
*
|
|
6
5
|
* Two-tier evaluator, the critical shape:
|
|
7
6
|
*
|
|
8
7
|
* INNER (what GEPA's own loop calls, many times, budget-capped): the
|
|
9
8
|
* candidate is ONE change-space file's content as a string. Each inner call
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* (default 10; each smoke costs minutes of arm time).
|
|
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).
|
|
16
14
|
*
|
|
17
15
|
* OUTER: GEPA's best candidate is written back to the surface file in the
|
|
18
16
|
* scratch worktree and the seat returns `applied: true` — from there the
|
|
@@ -31,30 +29,40 @@
|
|
|
31
29
|
* (`gepa_bridge.py` `_validate_input`: `if "testSet" in value ... raise`).
|
|
32
30
|
* This module never mentions holdout instances to begin with.
|
|
33
31
|
*
|
|
34
|
-
* RUNTIME
|
|
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.
|
|
32
|
+
* OPTIONAL RUNTIME (fails at provenance time, before a candidate slot is used):
|
|
39
33
|
* - Python: `agent_eval_rpc.gepa_bridge` + a GEPA build with
|
|
40
34
|
* `optimize_anything`/`OptimizeAnythingConfig` must import —
|
|
41
35
|
* `probeGepaRuntime` throws with the pip install instruction otherwise.
|
|
36
|
+
* The TypeScript adapter is a pinned package dependency and imported directly.
|
|
42
37
|
*/
|
|
43
38
|
|
|
44
|
-
import { createHash } from 'node:crypto'
|
|
45
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
39
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
40
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
46
41
|
import { dirname, join } from 'node:path'
|
|
47
|
-
import
|
|
48
|
-
DispatchContext,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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,
|
|
54
55
|
} from '@tangle-network/agent-eval/campaign'
|
|
56
|
+
import { officialOptimizerModel } from '../official-optimizer-config.mts'
|
|
55
57
|
import { ACTIVATION_PREDICATE_RELPATH, type ActivationPredicate } from './activation.mts'
|
|
56
58
|
import { changeSpaceViolations, type OuterLoopConfig } from './outer-loop.mts'
|
|
57
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'
|
|
58
66
|
import type { ScoreSplit } from './score-split.mts'
|
|
59
67
|
|
|
60
68
|
// ---------------------------------------------------------------------------
|
|
@@ -110,25 +118,18 @@ export function validateGepaSeat(spec: ProposerSpec): asserts spec is GepaSeatSp
|
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
// ---------------------------------------------------------------------------
|
|
113
|
-
// Recipe
|
|
114
|
-
// agent-eval may predate the export; see loadGepaMethodFactory).
|
|
121
|
+
// Recipe.
|
|
115
122
|
// ---------------------------------------------------------------------------
|
|
116
123
|
|
|
117
|
-
export
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 }
|
|
124
|
+
export type GepaSeatRecipe = Extract<
|
|
125
|
+
GepaOptimizationRecipe,
|
|
126
|
+
{ kind: 'engine' | 'omni' }
|
|
127
|
+
>
|
|
127
128
|
|
|
128
129
|
/** Build the bounded recipe for a seat. The TOTAL inner-evaluation budget is
|
|
129
|
-
* exactly `maxMetricCalls
|
|
130
|
+
* exactly `maxMetricCalls`. The adapter's local callback enforces the sum
|
|
130
131
|
* of per-run limits, and the seat's own dispatch wrapper re-enforces it. */
|
|
131
|
-
export function recipeForSeat(spec: GepaSeatSpec):
|
|
132
|
+
export function recipeForSeat(spec: GepaSeatSpec): GepaSeatRecipe {
|
|
132
133
|
const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
|
|
133
134
|
const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
|
|
134
135
|
if (spec.engine === 'gepa') {
|
|
@@ -145,13 +146,13 @@ export function recipeForSeat(spec: GepaSeatSpec): GepaOptimizationRecipe {
|
|
|
145
146
|
maxProposerCostUsd: perRunCost,
|
|
146
147
|
}))
|
|
147
148
|
return {
|
|
148
|
-
kind: '
|
|
149
|
+
kind: 'omni',
|
|
149
150
|
explore,
|
|
150
151
|
continueWith: { engine: 'gepa', maxEvaluations: continueCalls, maxProposerCostUsd: perRunCost },
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
export function recipeEvaluationBudget(recipe:
|
|
155
|
+
export function recipeEvaluationBudget(recipe: GepaSeatRecipe): number {
|
|
155
156
|
const runs = recipe.kind === 'engine' ? [recipe.run] : [...recipe.explore, recipe.continueWith]
|
|
156
157
|
return runs.reduce((sum, run) => sum + run.maxEvaluations, 0)
|
|
157
158
|
}
|
|
@@ -211,7 +212,7 @@ export function innerSmokeComposite(verdict: Pick<SmokeVerdict, 'resolved' | 've
|
|
|
211
212
|
export function innerSmokeJudge(): JudgeConfig<SmokeVerdict, GepaSeatScenario> {
|
|
212
213
|
return {
|
|
213
214
|
name: 'gepa-inner-smoke',
|
|
214
|
-
judgeVersion: 'gepa-inner-smoke
|
|
215
|
+
judgeVersion: 'gepa-inner-smoke',
|
|
215
216
|
dimensions: [
|
|
216
217
|
{ key: 'resolved', description: 'Official SWE-bench judge verdict for the smoke cell (1 resolved / 0 not).' },
|
|
217
218
|
{ key: 'verifyPass', description: 'Committed verify fixture passed for the smoke cell (tiebreak).' },
|
|
@@ -228,55 +229,17 @@ export function innerSmokeJudge(): JudgeConfig<SmokeVerdict, GepaSeatScenario> {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
// ---------------------------------------------------------------------------
|
|
231
|
-
//
|
|
232
|
+
// Optional Python runtime.
|
|
232
233
|
// ---------------------------------------------------------------------------
|
|
233
234
|
|
|
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
235
|
export const GEPA_PYTHON_INSTALL_HINT =
|
|
240
|
-
|
|
241
|
-
'
|
|
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
|
-
}
|
|
236
|
+
'install `agent-eval-rpc==0.126.6`, then install ' +
|
|
237
|
+
'`gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f`'
|
|
256
238
|
|
|
257
239
|
export type GepaMethodFactory = (
|
|
258
|
-
config:
|
|
240
|
+
config: GepaOptimizationMethodConfig<GepaSeatScenario, SmokeVerdict>,
|
|
259
241
|
) => OptimizationMethod<GepaSeatScenario, SmokeVerdict>
|
|
260
242
|
|
|
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
243
|
export type ProbeExec = (
|
|
281
244
|
command: string,
|
|
282
245
|
args: string[],
|
|
@@ -338,6 +301,11 @@ export interface GepaInnerCall {
|
|
|
338
301
|
wallS: number
|
|
339
302
|
}
|
|
340
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
|
+
|
|
341
309
|
export interface GepaSeatInnerRun {
|
|
342
310
|
seat: string
|
|
343
311
|
engine: GepaEngineName
|
|
@@ -347,32 +315,250 @@ export interface GepaSeatInnerRun {
|
|
|
347
315
|
innerCallCount: number
|
|
348
316
|
innerScores: GepaInnerCall[]
|
|
349
317
|
bestComposite: number | null
|
|
350
|
-
|
|
351
|
-
|
|
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[]
|
|
352
331
|
durationMs: number
|
|
353
332
|
}
|
|
354
333
|
|
|
355
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
|
+
}
|
|
356
366
|
|
|
357
|
-
|
|
358
|
-
|
|
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> = {}
|
|
367
|
+
const recordsDir = join(outDir, GEPA_INNER_RUNS_DIRNAME)
|
|
368
|
+
let entries
|
|
362
369
|
try {
|
|
363
|
-
|
|
364
|
-
} catch {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
+
}
|
|
372
558
|
}
|
|
373
559
|
|
|
374
560
|
// ---------------------------------------------------------------------------
|
|
375
|
-
// Mechanical activation predicate
|
|
561
|
+
// Mechanical activation predicate.
|
|
376
562
|
// ---------------------------------------------------------------------------
|
|
377
563
|
|
|
378
564
|
const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
@@ -397,7 +583,6 @@ export function mechanicalActivationPredicate(
|
|
|
397
583
|
if (added.length === 0) return null
|
|
398
584
|
const line = added.reduce((a, b) => (b.length > a.length ? b : a))
|
|
399
585
|
return {
|
|
400
|
-
version: 'v1',
|
|
401
586
|
description: `gepa-author surface change fired: candidate text from ${surface} appears in run artifacts`,
|
|
402
587
|
kind: 'grep',
|
|
403
588
|
pattern: escapeRegExp(line),
|
|
@@ -410,17 +595,57 @@ export function mechanicalActivationPredicate(
|
|
|
410
595
|
|
|
411
596
|
export interface GepaSeatDeps {
|
|
412
597
|
smokeRunner: SmokeRunner
|
|
598
|
+
runnerImplementationRef: string
|
|
599
|
+
judgeImplementationRef: string
|
|
413
600
|
/** Resolved PUBLIC smoke instance (outer-loop restricts the choice to the
|
|
414
601
|
* split's public set; re-asserted here fail-closed). */
|
|
415
602
|
smokeInstanceId: string
|
|
416
603
|
scoreSplit: Pick<ScoreSplit, 'privateInstances'> | null
|
|
417
|
-
/** Test
|
|
604
|
+
/** Test override. Default: agent-eval's official GEPA method. */
|
|
418
605
|
methodFactory?: GepaMethodFactory
|
|
606
|
+
/** Explicit model override. Production otherwise resolves the metered model from env. */
|
|
607
|
+
optimizer?: NonNullable<
|
|
608
|
+
GepaOptimizationMethodConfig<GepaSeatScenario, SmokeVerdict>['optimizer']
|
|
609
|
+
>
|
|
419
610
|
log?: (msg: string) => void
|
|
420
611
|
}
|
|
421
612
|
|
|
422
613
|
const sha256 = (s: string): string => `sha256:${createHash('sha256').update(s).digest('hex')}`
|
|
423
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
|
+
|
|
424
649
|
/** Build the seat's `AuthorFn`. The fan-out calls it with the seat's scratch
|
|
425
650
|
* worktree (checked out at the incumbent commit); everything this function
|
|
426
651
|
* leaves in that worktree becomes the candidate diff. */
|
|
@@ -437,8 +662,12 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
437
662
|
const seed = await readFile(surfacePath, 'utf8').catch(() => {
|
|
438
663
|
throw new Error(`gepa seat '${spec.name}': surface ${spec.surface} does not exist at the incumbent commit`)
|
|
439
664
|
})
|
|
665
|
+
const incumbentCommit = (
|
|
666
|
+
await runOk('git', ['-C', args.worktreePath, 'rev-parse', 'HEAD'])
|
|
667
|
+
).stdout.trim()
|
|
440
668
|
const runDir = join(config.outDir, 'gepa-seat', `gen${generation}-${spec.name.replace(/[^a-zA-Z0-9_-]/g, '_')}`)
|
|
441
669
|
await mkdir(runDir, { recursive: true })
|
|
670
|
+
await pruneDetachedWorktrees(args.worktreePath)
|
|
442
671
|
|
|
443
672
|
const objective =
|
|
444
673
|
`Improve the supervisor-loop file '${spec.surface}' (returned as the COMPLETE new file content) so the ` +
|
|
@@ -451,7 +680,15 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
451
680
|
assertNoPrivateLeak(objective + background + JSON.stringify([...scenarios.train, ...scenarios.selection]),
|
|
452
681
|
deps.scoreSplit, 'bridge payload')
|
|
453
682
|
|
|
683
|
+
const storage = fsCampaignStorage()
|
|
684
|
+
const costLedger =
|
|
685
|
+
args.costLedger ??
|
|
686
|
+
createRunCostLedger({
|
|
687
|
+
storage,
|
|
688
|
+
runDir: `${runDir}/cost`,
|
|
689
|
+
})
|
|
454
690
|
const innerScores: GepaInnerCall[] = []
|
|
691
|
+
let dispatchedCalls = 0
|
|
455
692
|
const dispatchWithSurface = async (
|
|
456
693
|
surface: MutableSurface,
|
|
457
694
|
scenario: GepaSeatScenario,
|
|
@@ -460,27 +697,38 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
460
697
|
if (typeof surface !== 'string') {
|
|
461
698
|
throw new Error(`gepa seat '${spec.name}': candidate surface must be a string`)
|
|
462
699
|
}
|
|
463
|
-
|
|
700
|
+
const call = ++dispatchedCalls
|
|
701
|
+
if (call > budget) {
|
|
464
702
|
// Defense-in-depth: the adapter's callback enforces the same cap.
|
|
465
703
|
throw new Error(`gepa seat '${spec.name}': inner-call budget ${budget} exhausted`)
|
|
466
704
|
}
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
}
|
|
474
722
|
if (deps.scoreSplit !== null && deps.scoreSplit.privateInstances.includes(verdict.iid)) {
|
|
475
723
|
throw new Error(
|
|
476
724
|
`gepa seat '${spec.name}': smoke ran PRIVATE instance ${verdict.iid} — refusing to feed its score to the bridge`,
|
|
477
725
|
)
|
|
478
726
|
}
|
|
479
727
|
innerScores.push({
|
|
480
|
-
call
|
|
728
|
+
call,
|
|
481
729
|
scenarioId: scenario.id,
|
|
482
730
|
smokeIid: verdict.iid,
|
|
483
|
-
candidateSha256
|
|
731
|
+
candidateSha256,
|
|
484
732
|
composite: innerSmokeComposite(verdict),
|
|
485
733
|
resolved: verdict.resolved,
|
|
486
734
|
verifyPass: verdict.verifyPass ?? null,
|
|
@@ -488,21 +736,51 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
488
736
|
wallS: verdict.wallS,
|
|
489
737
|
})
|
|
490
738
|
log(
|
|
491
|
-
`gepa seat ${spec.name} inner call ${
|
|
739
|
+
`gepa seat ${spec.name} inner call ${call}/${budget}: ` +
|
|
492
740
|
`composite=${innerSmokeComposite(verdict)} (${verdict.reason})`,
|
|
493
741
|
)
|
|
494
742
|
return verdict
|
|
495
743
|
}
|
|
496
744
|
|
|
497
|
-
const factory =
|
|
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
|
+
}))
|
|
498
766
|
const method = factory({
|
|
499
767
|
name: `gepa-seat:${spec.name}`,
|
|
500
768
|
recipe,
|
|
501
769
|
objective,
|
|
770
|
+
evaluationId: gepaSeatEvaluationId({
|
|
771
|
+
smokeInstanceId: deps.smokeInstanceId,
|
|
772
|
+
dispatchTimeoutMs: config.dispatchTimeoutMs,
|
|
773
|
+
incumbentCommit,
|
|
774
|
+
runnerImplementationRef: deps.runnerImplementationRef,
|
|
775
|
+
judgeImplementationRef: deps.judgeImplementationRef,
|
|
776
|
+
}),
|
|
502
777
|
background,
|
|
503
778
|
describeScenario: (scenario) => ({ id: scenario.id }),
|
|
504
|
-
|
|
779
|
+
...(optimizer ? { optimizer } : {}),
|
|
780
|
+
// Upper bound, not expectation: every inner call is a real arm cell.
|
|
505
781
|
timeoutMs: budget * config.dispatchTimeoutMs,
|
|
782
|
+
resume: 'if-compatible',
|
|
783
|
+
trustResumeState: true,
|
|
506
784
|
runner: { command: spec.python ?? DEFAULT_GEPA_PYTHON },
|
|
507
785
|
})
|
|
508
786
|
|
|
@@ -511,10 +789,11 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
511
789
|
trainScenarios: scenarios.train,
|
|
512
790
|
selectionScenarios: scenarios.selection,
|
|
513
791
|
dispatchWithSurface,
|
|
514
|
-
judges: [
|
|
792
|
+
judges: [innerJudge],
|
|
515
793
|
runDir,
|
|
516
794
|
seed: config.round * 1000 + generation,
|
|
517
795
|
runOptions: {
|
|
796
|
+
storage,
|
|
518
797
|
maxConcurrency: 1,
|
|
519
798
|
dispatchTimeoutMs: config.dispatchTimeoutMs,
|
|
520
799
|
labeledStore: 'off',
|
|
@@ -522,31 +801,46 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
|
|
|
522
801
|
expectUsage: 'off',
|
|
523
802
|
resumable: false,
|
|
524
803
|
},
|
|
804
|
+
costLedger,
|
|
525
805
|
}
|
|
526
806
|
|
|
527
807
|
const started = Date.now()
|
|
528
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
|
+
|
|
529
838
|
const winner = result.winnerSurface
|
|
530
839
|
if (typeof winner !== 'string' || winner.trim().length === 0) {
|
|
840
|
+
await writeFile(surfacePath, seed)
|
|
531
841
|
throw new Error(`gepa seat '${spec.name}': adapter returned a non-string winner surface`)
|
|
532
842
|
}
|
|
533
843
|
|
|
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
844
|
if (winner === seed) {
|
|
551
845
|
// Restore the seed (the last inner call may have left another candidate)
|
|
552
846
|
// and decline the slot — an unchanged surface has no candidate diff.
|