@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.
Files changed (115) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -0
  3. package/dist/adapters.js +23 -23
  4. package/dist/benchmarks/_harness.d.ts +4 -1
  5. package/dist/benchmarks/_harness.js +3 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/nomiracl.js +2 -2
  16. package/dist/benchmarks/open-rag-bench.js +2 -2
  17. package/dist/benchmarks/programbench.js +2 -2
  18. package/dist/benchmarks/ragbench.js +2 -2
  19. package/dist/benchmarks/swe-bench.js +2 -2
  20. package/dist/benchmarks/t2-ragbench.js +2 -2
  21. package/dist/benchmarks/tau-bench-shared.js +2 -2
  22. package/dist/benchmarks/tau2-bench.js +3 -3
  23. package/dist/benchmarks/tau3-banking.js +3 -3
  24. package/dist/benchmarks/terminal-bench.js +2 -2
  25. package/dist/benchmarks/toollm.js +2 -2
  26. package/dist/benchmarks/webarena-verified.js +2 -2
  27. package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
  28. package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
  29. package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
  30. package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
  31. package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
  32. package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
  33. package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
  34. package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
  35. package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
  36. package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
  37. package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
  38. package/dist/chunk-EIETHPD5.js.map +1 -0
  39. package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
  40. package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
  41. package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
  42. package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
  43. package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
  44. package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
  45. package/dist/chunk-JSQOUKXS.js.map +1 -0
  46. package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
  47. package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
  48. package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
  49. package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
  50. package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
  51. package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
  52. package/dist/index.js +23 -23
  53. package/package.json +4 -4
  54. package/pier_agents/candidate_contract.py +238 -24
  55. package/pier_agents/tangle_candidate.py +75 -5
  56. package/scripts/trata-hedge/README.md +6 -5
  57. package/scripts/verify-packed-consumer.mjs +65 -1
  58. package/scripts/verify-pier-agent.mts +6 -4
  59. package/src/benchmarks/_harness.test.mts +16 -1
  60. package/src/benchmarks/_harness.ts +9 -2
  61. package/src/benchmarks/terminal-bench.ts +2 -1
  62. package/src/gate.ts +1 -1
  63. package/src/hev-eval.mts +5 -2
  64. package/src/hev-improve.mts +118 -73
  65. package/src/official-optimizer-config.mts +89 -0
  66. package/src/official-optimizer-config.test.mts +88 -0
  67. package/src/profiles.ts +2 -2
  68. package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
  69. package/src/smoke-structural-rollout.mts +15 -9
  70. package/src/swe-arena/activation.mts +1 -4
  71. package/src/swe-arena/activation.test.mts +10 -13
  72. package/src/swe-arena/gepa-seat.mts +425 -131
  73. package/src/swe-arena/gepa-seat.test.mts +524 -100
  74. package/src/swe-arena/implementation-ref.test.mts +64 -0
  75. package/src/swe-arena/implementation-ref.ts +62 -0
  76. package/src/swe-arena/outer-loop.mts +103 -76
  77. package/src/swe-arena/proposer-fanout.mts +51 -36
  78. package/src/swe-arena/proposer-fanout.test.mts +0 -1
  79. package/src/swe-arena/proposer-provenance.mts +11 -16
  80. package/src/swe-arena/scratch-worktree.test.mts +55 -0
  81. package/src/swe-arena/scratch-worktree.ts +34 -0
  82. package/src/swe-code-improve.mts +24 -25
  83. package/src/swe-improve.mts +129 -96
  84. package/src/swe-local-proof.mts +6 -1
  85. package/src/swe-stream.mts +4 -2
  86. package/src/tb-container-executor.test.mts +30 -6
  87. package/src/tb-supervisor-sidecar.mts +2 -1
  88. package/src/trata-gepa.mts +182 -245
  89. package/dist/chunk-WSKWVEQB.js.map +0 -1
  90. package/dist/chunk-XYA4XSNU.js.map +0 -1
  91. package/src/live-improve-campaign-mbpp.mts +0 -641
  92. package/src/live-improve-campaign.mts +0 -500
  93. package/src/swe-arena/lineage-record.mts +0 -164
  94. package/src/swe-arena/lineage-record.test.mts +0 -115
  95. /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
  96. /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
  97. /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
  98. /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
  99. /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
  100. /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
  101. /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
  102. /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
  103. /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
  104. /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
  105. /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
  106. /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
  107. /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
  108. /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
  109. /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
  110. /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
  111. /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
  112. /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
  113. /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
  114. /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
  115. /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
@@ -1,18 +1,16 @@
1
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.
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
- * 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).
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 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.
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 type {
48
- DispatchContext,
49
- JudgeConfig,
50
- MutableSurface,
51
- OptimizationMethod,
52
- OptimizationMethodInput,
53
- Scenario,
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 — the adapter's own shape, mirrored structurally (the installed
114
- // agent-eval may predate the export; see loadGepaMethodFactory).
121
+ // Recipe.
115
122
  // ---------------------------------------------------------------------------
116
123
 
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 }
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` the adapter's local callback enforces the sum
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): GepaOptimizationRecipe {
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: 'best-of-then-continue',
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: GepaOptimizationRecipe): number {
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.v1',
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
- // Runtime seams — Node adapter export + Python bridge, both loud.
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
- "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
- }
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: GepaMethodConfig,
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
- adapterReportedCostUsd: number | null
351
- adapterCostAccountingComplete: boolean
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
- /** 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> = {}
367
+ const recordsDir = join(outDir, GEPA_INNER_RUNS_DIRNAME)
368
+ let entries
362
369
  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))
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 (gen-5 activation gate).
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 seam. Default: checked dynamic import of the installed adapter. */
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
- if (innerScores.length >= budget) {
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
- 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
- })
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: innerScores.length + 1,
728
+ call,
481
729
  scenarioId: scenario.id,
482
730
  smokeIid: verdict.iid,
483
- candidateSha256: sha256(surface),
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 ${innerScores.length}/${budget}: ` +
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 = deps.methodFactory ?? (await loadGepaMethodFactory())
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
- // Ceiling, not expectation: every inner call is a real arm cell.
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: [innerSmokeJudge()],
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.