@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,20 +1,25 @@
1
1
  import { spawnSync } from 'node:child_process'
2
2
  import { existsSync } from 'node:fs'
3
- import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
+ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
4
+ import { createServer } from 'node:http'
4
5
  import { tmpdir } from 'node:os'
5
6
  import { join } from 'node:path'
6
- import type { DispatchContext } from '@tangle-network/agent-eval/campaign'
7
+ import type {
8
+ DispatchContext,
9
+ OptimizationMethodProvenance,
10
+ } from '@tangle-network/agent-eval/campaign'
7
11
  import { afterEach, beforeEach, describe, expect, it } from 'vitest'
8
12
  import { ACTIVATION_PREDICATE_RELPATH, parseActivationPredicate } from './activation.mts'
9
13
  import {
10
14
  DEFAULT_GEPA_PYTHON,
11
15
  DEFAULT_MAX_METRIC_CALLS,
12
- GEPA_ADAPTER_UPGRADE_HINT,
16
+ GEPA_INNER_RUNS_DIRNAME,
17
+ GEPA_PYTHON_INSTALL_HINT,
13
18
  gepaBridgeScenarios,
19
+ gepaSeatEvaluationId,
14
20
  innerSmokeComposite,
15
21
  innerSmokeJudge,
16
22
  isGepaSeat,
17
- loadGepaMethodFactory,
18
23
  mechanicalActivationPredicate,
19
24
  probeGepaRuntime,
20
25
  recipeEvaluationBudget,
@@ -32,6 +37,10 @@ import { captureProposerProvenance } from './proposer-provenance.mts'
32
37
  import { runOk } from './proc.ts'
33
38
 
34
39
  const SURFACE = 'extensions/pi/prompts/worker-coding-system.md'
40
+ const IMPLEMENTATION_REFS = {
41
+ runnerImplementationRef: `sha256:${'a'.repeat(64)}`,
42
+ judgeImplementationRef: `sha256:${'b'.repeat(64)}`,
43
+ } as const
35
44
 
36
45
  const seat = (over: Partial<ProposerSpec> = {}): ProposerSpec => ({
37
46
  name: 'gepa-author',
@@ -45,7 +54,7 @@ const seat = (over: Partial<ProposerSpec> = {}): ProposerSpec => ({
45
54
  // ---------------------------------------------------------------------------
46
55
 
47
56
  describe('validateGepaSeat', () => {
48
- it('accepts the gen-6 draft seat shape and isGepaSeat discriminates on engine', () => {
57
+ it('accepts an engine seat and isGepaSeat discriminates on engine', () => {
49
58
  expect(() => validateGepaSeat(seat())).not.toThrow()
50
59
  expect(() => validateGepaSeat(seat({ engine: 'omni', maxMetricCalls: 8 }))).not.toThrow()
51
60
  expect(isGepaSeat(seat())).toBe(true)
@@ -85,10 +94,10 @@ describe('recipeForSeat', () => {
85
94
  expect(recipeEvaluationBudget(recipe)).toBe(DEFAULT_MAX_METRIC_CALLS)
86
95
  })
87
96
 
88
- it("'omni' is GEPA's best-of-then-continue shape and the four bounded runs sum EXACTLY to the budget", () => {
97
+ it("'omni' uses the official recipe and its four bounded runs preserve the budget", () => {
89
98
  const recipe = recipeForSeat(seat({ engine: 'omni', maxMetricCalls: 10 }) as GepaSeatSpec)
90
- expect(recipe.kind).toBe('best-of-then-continue')
91
- if (recipe.kind !== 'best-of-then-continue') throw new Error('unreachable')
99
+ expect(recipe.kind).toBe('omni')
100
+ if (recipe.kind !== 'omni') throw new Error('unreachable')
92
101
  expect(recipe.explore.map((r) => r.engine)).toEqual(['gepa', 'autoresearch', 'meta_harness'])
93
102
  expect(recipe.continueWith.engine).toBe('gepa')
94
103
  expect(recipeEvaluationBudget(recipe)).toBe(10)
@@ -153,7 +162,7 @@ describe('inner smoke score', () => {
153
162
  expect(innerSmokeComposite(verdict({ resolved: false, verifyPass: true }))).toBe(0.25)
154
163
  expect(innerSmokeComposite(verdict({ resolved: true, verifyPass: false }))).toBe(1)
155
164
  expect(innerSmokeComposite(verdict({ resolved: true, verifyPass: true }))).toBe(1.25)
156
- // Pre-gen-6 verdicts without the field score as no verify signal.
165
+ // Older verdicts without the field score as no verify signal.
157
166
  expect(innerSmokeComposite(verdict({ resolved: true }))).toBe(1)
158
167
  })
159
168
 
@@ -169,21 +178,9 @@ describe('inner smoke score', () => {
169
178
  })
170
179
 
171
180
  // ---------------------------------------------------------------------------
172
- // Runtime seams: loud fails with exact instructions.
181
+ // Optional Python runtime: loud failures with exact instructions.
173
182
  // ---------------------------------------------------------------------------
174
183
 
175
- describe('loadGepaMethodFactory', () => {
176
- it('throws the upgrade instruction when the installed agent-eval predates the adapter', async () => {
177
- await expect(loadGepaMethodFactory(async () => ({}))).rejects.toThrow(/#408/)
178
- await expect(loadGepaMethodFactory(async () => ({}))).rejects.toThrow(/gepaOptimizationMethod/)
179
- })
180
-
181
- it('returns the export when present', async () => {
182
- const factory = (() => ({})) as unknown as GepaMethodFactory
183
- await expect(loadGepaMethodFactory(async () => ({ gepaOptimizationMethod: factory }))).resolves.toBe(factory)
184
- })
185
- })
186
-
187
184
  describe('probeGepaRuntime', () => {
188
185
  const execFailingOn =
189
186
  (failFragment: string, stderr: string): ProbeExec =>
@@ -196,7 +193,9 @@ describe('probeGepaRuntime', () => {
196
193
 
197
194
  it('fails loud with pip install instructions when the bridge module is missing', async () => {
198
195
  const exec = execFailingOn('agent_eval_rpc.gepa_bridge', "ModuleNotFoundError: No module named 'agent_eval_rpc'")
199
- await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(/pip install 'agent-eval-rpc\[gepa\]'/)
196
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(
197
+ GEPA_PYTHON_INSTALL_HINT,
198
+ )
200
199
  await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(/not installed/)
201
200
  })
202
201
 
@@ -234,15 +233,10 @@ describe('captureProposerProvenance with a gepa seat', () => {
234
233
  }
235
234
  return { code: 0, stdout: 'source', stderr: '' }
236
235
  }
237
- const withAdapter = async (): Promise<Record<string, unknown>> => ({
238
- gepaOptimizationMethod: () => ({}),
239
- })
240
-
241
236
  it('records engine, surface, gepa version, bridge module, and the python runtime as harnessVersion', async () => {
242
237
  const record = await captureProposerProvenance([{ name: 'claude-author', harness: 'claude' }, seat()], {
243
238
  exec: okExec,
244
239
  readSettingsModel: () => 'settings-model',
245
- importCampaign: withAdapter,
246
240
  })
247
241
  const gepa = record.proposers.find((p) => p.name === 'gepa-author')!
248
242
  expect(gepa).toMatchObject({
@@ -267,15 +261,9 @@ describe('captureProposerProvenance with a gepa seat', () => {
267
261
  args.join(' ').includes('gepa_bridge')
268
262
  ? { code: 1, stdout: '', stderr: 'ModuleNotFoundError' }
269
263
  : { code: 0, stdout: 'Python 3.12.3', stderr: '' }
270
- await expect(
271
- captureProposerProvenance([seat()], { exec, importCampaign: withAdapter }),
272
- ).rejects.toThrow(/pip install 'agent-eval-rpc\[gepa\]'/)
273
- })
274
-
275
- it('fails LOUD at t=0 when the installed agent-eval predates the adapter export', async () => {
276
- await expect(
277
- captureProposerProvenance([seat()], { exec: okExec, importCampaign: async () => ({}) }),
278
- ).rejects.toThrow(GEPA_ADAPTER_UPGRADE_HINT.slice(0, 40))
264
+ await expect(captureProposerProvenance([seat()], { exec })).rejects.toThrow(
265
+ GEPA_PYTHON_INSTALL_HINT,
266
+ )
279
267
  })
280
268
  })
281
269
 
@@ -284,7 +272,7 @@ describe('captureProposerProvenance with a gepa seat', () => {
284
272
  // ---------------------------------------------------------------------------
285
273
 
286
274
  describe('mechanicalActivationPredicate', () => {
287
- it('targets the longest added line and produces a parseable v1 grep predicate', () => {
275
+ it('targets the longest added line and produces a parseable grep predicate', () => {
288
276
  const seed = 'alpha\nshared line stays here\n'
289
277
  const winner = 'alpha\nshared line stays here\nAlways run the neighboring test file before finalizing.\nshort\n'
290
278
  const predicate = mechanicalActivationPredicate(seed, winner, SURFACE)!
@@ -309,34 +297,128 @@ describe('mechanicalActivationPredicate', () => {
309
297
  })
310
298
 
311
299
  describe('recordGepaSeatInnerRun', () => {
312
- it('appends to gepaInnerRuns while preserving the t=0 capture record', async () => {
300
+ const sourceHash = 'a'.repeat(64)
301
+ const bridgeHash = 'b'.repeat(64)
302
+ const moduleHash = 'c'.repeat(64)
303
+ const provenance = (runId: string): OptimizationMethodProvenance => ({
304
+ source: {
305
+ kind: 'package',
306
+ evidence: 'observed',
307
+ package: 'gepa',
308
+ version: '0.1.4',
309
+ sourceUrl: 'https://github.com/gepa-ai/gepa.git',
310
+ revision: 'f919db0a622e2e9f9204779b81fe00cc1b2d808f',
311
+ sourceSha256: sourceHash,
312
+ },
313
+ bridge: {
314
+ kind: 'package',
315
+ evidence: 'observed',
316
+ package: 'agent-eval-rpc',
317
+ version: '0.126.1',
318
+ sourceSha256: bridgeHash,
319
+ },
320
+ modules: [{ module: 'example.engine', sourceSha256: moduleHash }],
321
+ python: { implementation: 'CPython', version: '3.12.3' },
322
+ runId,
323
+ compatibleRunId: 'compatible-run',
324
+ resumed: false,
325
+ evaluationCount: 3,
326
+ tokenUsage: {
327
+ inputTokens: 120,
328
+ cachedInputTokens: 20,
329
+ outputTokens: 30,
330
+ reasoningTokens: 10,
331
+ totalTokens: 150,
332
+ calls: 2,
333
+ },
334
+ artifactDir: `/tmp/${runId}`,
335
+ })
336
+ const run = (runId: string, over: Partial<GepaSeatInnerRun> = {}): GepaSeatInnerRun => {
337
+ const result = provenance(runId)
338
+ if (
339
+ result.source.revision === undefined ||
340
+ result.source.sourceSha256 === undefined ||
341
+ result.bridge?.sourceSha256 === undefined ||
342
+ result.modules === undefined ||
343
+ result.python === undefined ||
344
+ result.compatibleRunId === undefined ||
345
+ result.tokenUsage === undefined
346
+ ) {
347
+ throw new Error('invalid test provenance')
348
+ }
349
+ return {
350
+ seat: 'gepa-author',
351
+ engine: 'gepa',
352
+ surface: SURFACE,
353
+ generation: 0,
354
+ budget: 10,
355
+ innerCallCount: 2,
356
+ innerScores: [],
357
+ bestComposite: 1,
358
+ source: {
359
+ ...result.source,
360
+ revision: result.source.revision,
361
+ sourceSha256: result.source.sourceSha256,
362
+ },
363
+ bridge: { ...result.bridge, sourceSha256: result.bridge.sourceSha256 },
364
+ modules: result.modules,
365
+ python: result.python,
366
+ runId: result.runId,
367
+ compatibleRunId: result.compatibleRunId,
368
+ resumed: result.resumed,
369
+ evaluationCount: result.evaluationCount,
370
+ tokenUsage: result.tokenUsage,
371
+ artifactDir: result.artifactDir,
372
+ totalCostUsd: 0.25,
373
+ accountingComplete: true,
374
+ incompleteReasons: [],
375
+ durationMs: 5,
376
+ ...over,
377
+ }
378
+ }
379
+
380
+ it('writes collision-free immutable records in parallel and preserves the launch record', async () => {
313
381
  const dir = await mkdtemp(join(tmpdir(), 'gepa-prov-'))
314
382
  try {
315
- await writeFile(join(dir, 'proposer-provenance.json'), JSON.stringify({ schema: 'swe-arena.proposer-provenance.v1', proposers: [] }))
316
- const run: GepaSeatInnerRun = {
317
- seat: 'gepa-author',
318
- engine: 'gepa',
319
- surface: SURFACE,
320
- generation: 0,
321
- budget: 10,
322
- innerCallCount: 2,
323
- innerScores: [],
324
- bestComposite: 1,
325
- adapterReportedCostUsd: 0,
326
- adapterCostAccountingComplete: false,
327
- durationMs: 5,
328
- }
329
- await recordGepaSeatInnerRun(dir, run)
330
- await recordGepaSeatInnerRun(dir, { ...run, generation: 1 })
331
- const record = JSON.parse(await readFile(join(dir, 'proposer-provenance.json'), 'utf8'))
332
- expect(record.schema).toBe('swe-arena.proposer-provenance.v1')
333
- expect(record.gepaInnerRuns).toHaveLength(2)
334
- expect(record.gepaInnerRuns[0]).toMatchObject({ seat: 'gepa-author', innerCallCount: 2 })
335
- expect(record.gepaInnerRuns[1]).toMatchObject({ generation: 1 })
383
+ const launchRecord = JSON.stringify({ capturedAt: '2026-07-24T00:00:00.000Z', proposers: [] }, null, 2)
384
+ await writeFile(join(dir, 'proposer-provenance.json'), launchRecord)
385
+ const paths = await Promise.all(
386
+ Array.from({ length: 24 }, (_, index) =>
387
+ recordGepaSeatInnerRun(dir, run(`run-${index}`, { generation: index })),
388
+ ),
389
+ )
390
+ expect(new Set(paths).size).toBe(24)
391
+ expect(await readFile(join(dir, 'proposer-provenance.json'), 'utf8')).toBe(launchRecord)
392
+
393
+ const names = (await readdir(join(dir, GEPA_INNER_RUNS_DIRNAME))).filter((name) => name.endsWith('.json'))
394
+ expect(names).toHaveLength(24)
395
+ const records = await Promise.all(
396
+ names.map(async (name) => JSON.parse(await readFile(join(dir, GEPA_INNER_RUNS_DIRNAME, name), 'utf8'))),
397
+ )
398
+ expect(new Set(records.map((record) => record.runId))).toEqual(
399
+ new Set(Array.from({ length: 24 }, (_, index) => `run-${index}`)),
400
+ )
401
+ expect(records.every((record) => record.source.sourceSha256 === sourceHash)).toBe(true)
336
402
  } finally {
337
403
  await rm(dir, { recursive: true, force: true })
338
404
  }
339
405
  })
406
+
407
+ it('fails on malformed existing launch or run data', async () => {
408
+ const launchDir = await mkdtemp(join(tmpdir(), 'gepa-prov-bad-launch-'))
409
+ const runDir = await mkdtemp(join(tmpdir(), 'gepa-prov-bad-run-'))
410
+ try {
411
+ await writeFile(join(launchDir, 'proposer-provenance.json'), '{not json')
412
+ await expect(recordGepaSeatInnerRun(launchDir, run('new-run'))).rejects.toThrow(/malformed JSON/)
413
+
414
+ await mkdir(join(runDir, GEPA_INNER_RUNS_DIRNAME), { recursive: true })
415
+ await writeFile(join(runDir, GEPA_INNER_RUNS_DIRNAME, 'broken.json'), '[]')
416
+ await expect(recordGepaSeatInnerRun(runDir, run('new-run'))).rejects.toThrow(/must contain a JSON object/)
417
+ } finally {
418
+ await rm(launchDir, { recursive: true, force: true })
419
+ await rm(runDir, { recursive: true, force: true })
420
+ }
421
+ })
340
422
  })
341
423
 
342
424
  // ---------------------------------------------------------------------------
@@ -344,6 +426,63 @@ describe('recordGepaSeatInnerRun', () => {
344
426
  // ---------------------------------------------------------------------------
345
427
 
346
428
  const fakeCtx = {} as unknown as DispatchContext
429
+ const testOptimizer = {
430
+ model: 'optimizer-model',
431
+ baseUrl: 'http://127.0.0.1:1/v1',
432
+ apiKey: 'optimizer-key',
433
+ budget: {
434
+ maxCostUsd: 1,
435
+ maxRequests: 10,
436
+ maxRequestBytes: 100_000,
437
+ maxResponseBytes: 100_000,
438
+ maxOutputTokensPerRequest: 2_000,
439
+ pricing: {
440
+ inputUsdPerMillion: 1,
441
+ cachedInputUsdPerMillion: 0.1,
442
+ cacheWriteUsdPerMillion: 1.25,
443
+ outputUsdPerMillion: 5,
444
+ },
445
+ },
446
+ }
447
+
448
+ const fullProvenance = (
449
+ runId = 'gepa-run',
450
+ over: Partial<OptimizationMethodProvenance> = {},
451
+ ): OptimizationMethodProvenance => ({
452
+ source: {
453
+ kind: 'package',
454
+ evidence: 'observed',
455
+ package: 'gepa',
456
+ version: '0.1.4',
457
+ sourceUrl: 'https://github.com/gepa-ai/gepa.git',
458
+ revision: 'f919db0a622e2e9f9204779b81fe00cc1b2d808f',
459
+ sourceSha256: '1'.repeat(64),
460
+ },
461
+ bridge: {
462
+ kind: 'package',
463
+ evidence: 'observed',
464
+ package: 'agent-eval-rpc',
465
+ version: '0.126.1',
466
+ sourceSha256: '2'.repeat(64),
467
+ },
468
+ modules: [{ module: 'custom_gepa_engines', sourceSha256: '3'.repeat(64) }],
469
+ python: { implementation: 'CPython', version: '3.12.3' },
470
+ runId,
471
+ compatibleRunId: 'compatible-gepa-run',
472
+ resumed: false,
473
+ evaluationCount: 3,
474
+ tokenUsage: {
475
+ inputTokens: 100,
476
+ cachedInputTokens: 10,
477
+ cacheWriteInputTokens: 5,
478
+ outputTokens: 25,
479
+ reasoningTokens: 8,
480
+ totalTokens: 125,
481
+ calls: 2,
482
+ },
483
+ artifactDir: `/tmp/${runId}`,
484
+ ...over,
485
+ })
347
486
 
348
487
  /** Mimics the adapter's loop: score the seed and each provided candidate via
349
488
  * the seat's dispatch + judge, return the best-scoring candidate — exactly
@@ -365,8 +504,9 @@ const fakeGepaFactory =
365
504
  }
366
505
  return {
367
506
  winnerSurface: best.surface,
368
- cost: { totalCostUsd: 0, accountingComplete: false, incompleteReasons: ['fake factory'] },
507
+ cost: { totalCostUsd: 0.125, accountingComplete: true, incompleteReasons: [] },
369
508
  durationMs: 1,
509
+ provenance: fullProvenance(),
370
510
  }
371
511
  },
372
512
  }
@@ -434,16 +574,36 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
434
574
 
435
575
  it('construction fails loud without the smoke runner (the inner evaluator)', () => {
436
576
  expect(() => fanOutLoopsGenerator(baseConfig([seat()]))).toThrow(/inner evaluator/)
577
+ expect(() =>
578
+ fanOutLoopsGenerator(baseConfig([seat()]), {
579
+ smokeRunner: async () => smokeVerdict(),
580
+ smokeInstanceId: 'astropy__astropy-13033',
581
+ }),
582
+ ).toThrow(/immutable runner and judge references/)
437
583
  expect(() => fanOutLoopsGenerator(baseConfig([{ name: 'no-seat-kind' }]))).toThrow(/neither a harness nor an engine/)
438
584
  })
439
585
 
440
586
  it('materializes each candidate into the scratch surface, applies the winner through the normal prefilter path, and records provenance', async () => {
441
587
  const WINNER = `${SEED}Always run the neighboring test file before finalizing.\n`
442
588
  const LOSER = `${SEED}delete all tests\n`
443
- const seen: Array<{ content: string; scratch: string }> = []
444
- const smokeRunner: SmokeRunner = async ({ scratchPath }) => {
589
+ const seen: Array<{
590
+ content: string
591
+ scratch: string
592
+ evaluationKey: string
593
+ hasCostLedger: boolean
594
+ }> = []
595
+ const smokeRunner: SmokeRunner = async ({
596
+ scratchPath,
597
+ evaluationKey,
598
+ costLedger,
599
+ }) => {
445
600
  const content = await readFile(join(scratchPath, SURFACE), 'utf8')
446
- seen.push({ content, scratch: scratchPath })
601
+ seen.push({
602
+ content,
603
+ scratch: scratchPath,
604
+ evaluationKey,
605
+ hasCostLedger: costLedger !== undefined,
606
+ })
447
607
  // The winner candidate resolves; the seed gets verify-pass only; the
448
608
  // loser gets nothing — exercising resolve-dominates + tiebreak.
449
609
  if (content === WINNER) return smokeVerdict({ resolved: true, verifyPass: true })
@@ -453,21 +613,27 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
453
613
  const observed: { config?: unknown } = {}
454
614
  const config = baseConfig([seat({ maxMetricCalls: 5 })], { activationGate: true })
455
615
  const gen = fanOutLoopsGenerator(config, {
616
+ ...IMPLEMENTATION_REFS,
456
617
  smokeRunner,
457
618
  smokeInstanceId: 'astropy__astropy-13033',
458
619
  scoreSplit: { privateInstances: ['django__django-11532'] },
459
620
  gepaMethodFactory: fakeGepaFactory([LOSER, WINNER], observed),
621
+ gepaOptimizer: testOptimizer,
460
622
  })
461
623
 
462
624
  const result = await gen.generate(generatorArgs(0))
463
625
 
464
626
  expect(result).toMatchObject({ applied: true, label: 'gepa-author' })
465
627
  expect(result.rationale).toContain('engine gepa')
466
- // 3 inner calls (seed, loser, winner) + 1 stage-B prefilter smoke on the
467
- // final candidate all in the seat's scratch worktree, never the driver.
628
+ // 3 isolated inner calls (seed, loser, winner) + 1 stage-B prefilter smoke
629
+ // on the final candidate, all outside the driver worktree.
468
630
  expect(seen).toHaveLength(4)
469
631
  for (const call of seen) expect(call.scratch).not.toBe(driverWt)
470
632
  expect(seen.map((c) => c.content)).toEqual([SEED, LOSER, WINNER, WINNER])
633
+ expect(new Set(seen.slice(0, 3).map((call) => call.scratch)).size).toBe(3)
634
+ expect(seen.slice(0, 3).every((call) => call.evaluationKey.startsWith('inner-'))).toBe(true)
635
+ expect(seen[3]!.evaluationKey).toBe('candidate-0')
636
+ expect(seen.slice(0, 3).every((call) => call.hasCostLedger)).toBe(true)
471
637
  // The winner landed on the driver worktree with the mechanical predicate.
472
638
  expect(await readFile(join(driverWt, SURFACE), 'utf8')).toBe(WINNER)
473
639
  const predicate = parseActivationPredicate(await readFile(join(driverWt, ACTIVATION_PREDICATE_RELPATH), 'utf8'))
@@ -479,31 +645,243 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
479
645
  .filter(Boolean)
480
646
  expect(changed.sort()).toEqual([ACTIVATION_PREDICATE_RELPATH, SURFACE].sort())
481
647
  // Budget threaded into the adapter recipe.
482
- expect(observed.config).toMatchObject({ recipe: { kind: 'engine', run: { engine: 'gepa', maxEvaluations: 5 } } })
483
- // Inner-run provenance: per-seat file + the merged proposer-provenance.json.
648
+ const incumbentCommit = await git(['rev-parse', 'HEAD'], driverWt)
649
+ expect(observed.config).toMatchObject({
650
+ evaluationId: gepaSeatEvaluationId({
651
+ smokeInstanceId: 'astropy__astropy-13033',
652
+ dispatchTimeoutMs: config.dispatchTimeoutMs,
653
+ incumbentCommit,
654
+ ...IMPLEMENTATION_REFS,
655
+ }),
656
+ recipe: { kind: 'engine', run: { engine: 'gepa', maxEvaluations: 5 } },
657
+ optimizer: testOptimizer,
658
+ resume: 'if-compatible',
659
+ trustResumeState: true,
660
+ })
661
+ // Inner-run provenance: the seat-local file plus one immutable shared record.
484
662
  const inner = JSON.parse(await readFile(join(outDir, 'gepa-seat', 'gen0-gepa-author', 'inner-provenance.json'), 'utf8'))
485
- expect(inner).toMatchObject({ seat: 'gepa-author', engine: 'gepa', budget: 5, innerCallCount: 3, bestComposite: 1.25 })
663
+ expect(inner).toMatchObject({
664
+ seat: 'gepa-author',
665
+ engine: 'gepa',
666
+ budget: 5,
667
+ innerCallCount: 3,
668
+ bestComposite: 1.25,
669
+ source: {
670
+ package: 'gepa',
671
+ version: '0.1.4',
672
+ revision: 'f919db0a622e2e9f9204779b81fe00cc1b2d808f',
673
+ sourceSha256: '1'.repeat(64),
674
+ },
675
+ bridge: {
676
+ package: 'agent-eval-rpc',
677
+ version: '0.126.1',
678
+ sourceSha256: '2'.repeat(64),
679
+ },
680
+ modules: [{ module: 'custom_gepa_engines', sourceSha256: '3'.repeat(64) }],
681
+ python: { implementation: 'CPython', version: '3.12.3' },
682
+ runId: 'gepa-run',
683
+ compatibleRunId: 'compatible-gepa-run',
684
+ resumed: false,
685
+ evaluationCount: 3,
686
+ tokenUsage: {
687
+ inputTokens: 100,
688
+ cachedInputTokens: 10,
689
+ cacheWriteInputTokens: 5,
690
+ outputTokens: 25,
691
+ reasoningTokens: 8,
692
+ totalTokens: 125,
693
+ calls: 2,
694
+ },
695
+ artifactDir: '/tmp/gepa-run',
696
+ totalCostUsd: 0.125,
697
+ accountingComplete: true,
698
+ incompleteReasons: [],
699
+ })
486
700
  expect(inner.innerScores.map((s: { composite: number }) => s.composite)).toEqual([0.25, 0, 1.25])
487
- const merged = JSON.parse(await readFile(join(outDir, 'proposer-provenance.json'), 'utf8'))
488
- expect(merged.gepaInnerRuns).toHaveLength(1)
701
+ const records = await readdir(join(outDir, GEPA_INNER_RUNS_DIRNAME))
702
+ expect(records.filter((name) => name.endsWith('.json'))).toHaveLength(1)
703
+ expect(JSON.parse(await readFile(join(outDir, GEPA_INNER_RUNS_DIRNAME, records[0]!), 'utf8'))).toEqual(inner)
489
704
  expect(gen.drainPrefilterKills()).toEqual([])
490
705
  })
491
706
 
707
+ it('isolates concurrent candidate evaluations while preserving parallel execution', async () => {
708
+ const candidateA = `${SEED}candidate A keeps its own workspace\n`
709
+ const candidateB = `${SEED}candidate B keeps its own workspace\n`
710
+ let releaseBoth!: () => void
711
+ const bothStarted = new Promise<void>((resolve) => {
712
+ releaseBoth = resolve
713
+ })
714
+ let started = 0
715
+ const innerSeen: Array<{ content: string; scratchPath: string }> = []
716
+ const smokeRunner: SmokeRunner = async ({
717
+ scratchPath,
718
+ evaluationKey,
719
+ }) => {
720
+ if (evaluationKey.startsWith('inner-')) {
721
+ started += 1
722
+ if (started === 2) releaseBoth()
723
+ await bothStarted
724
+ }
725
+ const content = await readFile(join(scratchPath, SURFACE), 'utf8')
726
+ if (evaluationKey.startsWith('inner-')) {
727
+ innerSeen.push({ content, scratchPath })
728
+ }
729
+ return smokeVerdict({ resolved: content === candidateA })
730
+ }
731
+ const parallelFactory: GepaMethodFactory = () => ({
732
+ name: 'parallel-gepa',
733
+ async optimize(input) {
734
+ const scenario = input.trainScenarios[0]!
735
+ await Promise.all([
736
+ input.dispatchWithSurface(candidateA, scenario, fakeCtx),
737
+ input.dispatchWithSurface(candidateB, scenario, fakeCtx),
738
+ ])
739
+ return {
740
+ winnerSurface: candidateA,
741
+ cost: { totalCostUsd: 0, accountingComplete: true, incompleteReasons: [] },
742
+ durationMs: 1,
743
+ provenance: fullProvenance('parallel-gepa', { evaluationCount: 2 }),
744
+ }
745
+ },
746
+ })
747
+ const gen = fanOutLoopsGenerator(baseConfig([seat({ maxMetricCalls: 2 })]), {
748
+ ...IMPLEMENTATION_REFS,
749
+ smokeRunner,
750
+ smokeInstanceId: 'astropy__astropy-13033',
751
+ scoreSplit: null,
752
+ gepaMethodFactory: parallelFactory,
753
+ })
754
+
755
+ const result = await gen.generate(generatorArgs(0))
756
+
757
+ expect(result.applied).toBe(true)
758
+ expect(innerSeen.map((entry) => entry.content).sort()).toEqual(
759
+ [candidateA, candidateB].sort(),
760
+ )
761
+ expect(new Set(innerSeen.map((entry) => entry.scratchPath)).size).toBe(2)
762
+ })
763
+
764
+ it('uses explicit immutable refs so captured runner or judge behavior cannot share resume state', async () => {
765
+ const config = baseConfig([seat()])
766
+ const incumbentCommit = await git(['rev-parse', 'HEAD'], driverWt)
767
+ const makeRunner = (resolved: boolean): SmokeRunner =>
768
+ async () => smokeVerdict({ resolved })
769
+ const runnerV1 = makeRunner(false)
770
+ const runnerV2 = makeRunner(true)
771
+ const common = {
772
+ smokeInstanceId: 'astropy__astropy-13033',
773
+ dispatchTimeoutMs: config.dispatchTimeoutMs,
774
+ incumbentCommit,
775
+ }
776
+ const original = gepaSeatEvaluationId({ ...common, ...IMPLEMENTATION_REFS })
777
+ const changedRunner = gepaSeatEvaluationId({
778
+ ...common,
779
+ ...IMPLEMENTATION_REFS,
780
+ runnerImplementationRef: `sha256:${'c'.repeat(64)}`,
781
+ })
782
+ const changedJudge = gepaSeatEvaluationId({
783
+ ...common,
784
+ ...IMPLEMENTATION_REFS,
785
+ judgeImplementationRef: `sha256:${'d'.repeat(64)}`,
786
+ })
787
+ const changedIncumbent = gepaSeatEvaluationId({
788
+ ...common,
789
+ ...IMPLEMENTATION_REFS,
790
+ incumbentCommit: 'e'.repeat(40),
791
+ })
792
+ const smokeArgs: Parameters<SmokeRunner>[0] = {
793
+ scratchPath: driverWt,
794
+ generation: 0,
795
+ proposer: seat(),
796
+ evaluationKey: 'identity-test',
797
+ }
798
+
799
+ expect(runnerV1.toString()).toBe(runnerV2.toString())
800
+ expect((await runnerV1(smokeArgs)).resolved).toBe(false)
801
+ expect((await runnerV2(smokeArgs)).resolved).toBe(true)
802
+ expect(original).toMatch(
803
+ /^swe-arena-gepa-seat\|smoke=astropy__astropy-13033\|incumbent=[a-f0-9]{40,64}\|runner=sha256:[a-f0-9]{64}\|judge=sha256:[a-f0-9]{64}\|dispatchTimeoutMs=\d+$/,
804
+ )
805
+ expect(changedRunner).not.toBe(original)
806
+ expect(changedJudge).not.toBe(original)
807
+ expect(changedIncumbent).not.toBe(original)
808
+ expect(() =>
809
+ gepaSeatEvaluationId({
810
+ ...common,
811
+ ...IMPLEMENTATION_REFS,
812
+ runnerImplementationRef: 'runner-v2',
813
+ }),
814
+ ).toThrow(/runnerImplementationRef must be an immutable sha256 reference/)
815
+ })
816
+
817
+ it('records but rejects a winner when cost accounting is incomplete', async () => {
818
+ const WINNER = `${SEED}Always run the neighboring test file before finalizing.\n`
819
+ const incomplete: GepaMethodFactory = () => ({
820
+ name: 'incomplete-cost',
821
+ async optimize(input) {
822
+ await input.dispatchWithSurface(WINNER, input.trainScenarios[0]!, fakeCtx)
823
+ return {
824
+ winnerSurface: WINNER,
825
+ cost: {
826
+ totalCostUsd: 0.25,
827
+ accountingComplete: false,
828
+ incompleteReasons: ['optimizer model receipt missing'],
829
+ },
830
+ durationMs: 1,
831
+ provenance: fullProvenance('incomplete-run', { evaluationCount: 1 }),
832
+ }
833
+ },
834
+ })
835
+ const gen = fanOutLoopsGenerator(baseConfig([seat()]), {
836
+ ...IMPLEMENTATION_REFS,
837
+ smokeRunner: async () => smokeVerdict({ resolved: true }),
838
+ smokeInstanceId: 'astropy__astropy-13033',
839
+ scoreSplit: null,
840
+ gepaMethodFactory: incomplete,
841
+ })
842
+
843
+ await expect(gen.generate(generatorArgs(0))).rejects.toThrow(
844
+ /cost accounting is incomplete: optimizer model receipt missing/,
845
+ )
846
+ expect(await readFile(join(driverWt, SURFACE), 'utf8')).toBe(SEED)
847
+ const inner = JSON.parse(
848
+ await readFile(join(outDir, 'gepa-seat', 'gen0-gepa-author', 'inner-provenance.json'), 'utf8'),
849
+ )
850
+ expect(inner).toMatchObject({
851
+ runId: 'incomplete-run',
852
+ totalCostUsd: 0.25,
853
+ accountingComplete: false,
854
+ incompleteReasons: ['optimizer model receipt missing'],
855
+ })
856
+ })
857
+
492
858
  it('enforces the inner-call budget cap fail-closed', async () => {
493
859
  const runaway: GepaMethodFactory = () => ({
494
860
  name: 'runaway',
495
861
  async optimize(input) {
496
- for (let i = 0; i < 4; i++) {
497
- await input.dispatchWithSurface(`${SEED}candidate ${i}\n`, input.trainScenarios[0]!, fakeCtx)
498
- }
862
+ const results = await Promise.allSettled(
863
+ Array.from({ length: 4 }, (_, index) =>
864
+ input.dispatchWithSurface(
865
+ `${SEED}candidate ${index}\n`,
866
+ input.trainScenarios[0]!,
867
+ fakeCtx,
868
+ ),
869
+ ),
870
+ )
871
+ const rejected = results.find(
872
+ (result): result is PromiseRejectedResult => result.status === 'rejected',
873
+ )
874
+ if (rejected) throw rejected.reason
499
875
  return {
500
876
  winnerSurface: SEED,
501
877
  cost: { totalCostUsd: 0, accountingComplete: false, incompleteReasons: [] },
502
878
  durationMs: 1,
879
+ provenance: fullProvenance('runaway'),
503
880
  }
504
881
  },
505
882
  })
506
883
  const gen = fanOutLoopsGenerator(baseConfig([seat({ maxMetricCalls: 3 })]), {
884
+ ...IMPLEMENTATION_REFS,
507
885
  smokeRunner: async () => smokeVerdict(),
508
886
  smokeInstanceId: 'astropy__astropy-13033',
509
887
  scoreSplit: null,
@@ -514,6 +892,7 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
514
892
 
515
893
  it('refuses to feed a PRIVATE smoke verdict to the bridge', async () => {
516
894
  const gen = fanOutLoopsGenerator(baseConfig([seat()]), {
895
+ ...IMPLEMENTATION_REFS,
517
896
  smokeRunner: async () => smokeVerdict({ iid: 'django__django-11532' }),
518
897
  smokeInstanceId: 'astropy__astropy-13033',
519
898
  scoreSplit: { privateInstances: ['django__django-11532'] },
@@ -524,6 +903,7 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
524
903
 
525
904
  it('declines the slot without a kill when GEPA returns the seed unchanged', async () => {
526
905
  const gen = fanOutLoopsGenerator(baseConfig([seat()]), {
906
+ ...IMPLEMENTATION_REFS,
527
907
  smokeRunner: async () => smokeVerdict(),
528
908
  smokeInstanceId: 'astropy__astropy-13033',
529
909
  scoreSplit: null,
@@ -538,13 +918,12 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
538
918
  })
539
919
 
540
920
  // ---------------------------------------------------------------------------
541
- // Integration: ONE real NodePythonscore roundtrip through the installed
542
- // adapter + bridge. Skips with an exact reason when either runtime is absent
543
- // (this is the same condition the t=0 provenance capture enforces loud).
921
+ // Integration: one real Node-to-Python-to-score roundtrip through the installed
922
+ // bridge. The TypeScript adapter is a compile-time package dependency.
544
923
  // ---------------------------------------------------------------------------
545
924
 
546
- const pythonBridgeReady = (): { ok: boolean; reason: string } => {
547
- const probe = spawnSync(DEFAULT_GEPA_PYTHON, [
925
+ const pythonBridgeReady = (python: string): { ok: boolean; reason: string } => {
926
+ const probe = spawnSync(python, [
548
927
  '-c',
549
928
  'import agent_eval_rpc.gepa_bridge; from gepa.optimize_anything import optimize_anything, OptimizeAnythingConfig',
550
929
  ])
@@ -555,23 +934,44 @@ const pythonBridgeReady = (): { ok: boolean; reason: string } => {
555
934
  }
556
935
 
557
936
  describe('integration: real adapter roundtrip', () => {
558
- it('runs one inner smoke roundtrip through gepaOptimizationMethod when the full runtime is installed', async (ctx) => {
559
- const campaign = (await import('@tangle-network/agent-eval/campaign')) as Record<string, unknown>
560
- const missing: string[] = []
561
- if (typeof campaign.gepaOptimizationMethod !== 'function') {
562
- missing.push(
563
- 'installed @tangle-network/agent-eval lacks gepaOptimizationMethod (needs a release after 0.123.5 containing PRs #408/#409)',
564
- )
565
- }
566
- const python = pythonBridgeReady()
567
- if (!python.ok) missing.push(python.reason)
568
- if (missing.length > 0) {
569
- ctx.skip(`skip-with-reason: ${missing.join('; ')}`)
937
+ it('runs a metered optimizer through the real bridge and applies its winner', async (ctx) => {
938
+ const python = process.env.AGENT_EVAL_TEST_PYTHON ?? DEFAULT_GEPA_PYTHON
939
+ const pythonRuntime = pythonBridgeReady(python)
940
+ if (!pythonRuntime.ok) {
941
+ ctx.skip(`skip-with-reason: ${pythonRuntime.reason}`)
570
942
  return
571
943
  }
572
944
 
573
- // Full runtime present: drive the REAL adapter with a stub smoke runner
574
- // (no arm spend) over a synthetic tiny surface in a temp repo.
945
+ const winner = 'tiny synthetic surface\nAlways run the neighboring test before finalizing.'
946
+ const modelServer = createServer((_request, response) => {
947
+ response.writeHead(200, { 'content-type': 'application/json' })
948
+ response.end(
949
+ JSON.stringify({
950
+ choices: [
951
+ {
952
+ message: {
953
+ role: 'assistant',
954
+ content: `\`\`\`\n${winner}\`\`\``,
955
+ },
956
+ },
957
+ ],
958
+ usage: {
959
+ prompt_tokens: 20,
960
+ completion_tokens: 20,
961
+ total_tokens: 40,
962
+ },
963
+ }),
964
+ )
965
+ })
966
+ await new Promise<void>((resolve, reject) => {
967
+ modelServer.once('error', reject)
968
+ modelServer.listen(0, '127.0.0.1', resolve)
969
+ })
970
+ const address = modelServer.address()
971
+ if (!address || typeof address === 'string') {
972
+ throw new Error('test optimizer server did not bind')
973
+ }
974
+
575
975
  const loopsRepo = await mkdtemp(join(tmpdir(), 'gepa-int-repo-'))
576
976
  const outDir = await mkdtemp(join(tmpdir(), 'gepa-int-out-'))
577
977
  try {
@@ -585,24 +985,24 @@ describe('integration: real adapter roundtrip', () => {
585
985
  const driverWt = join(outDir, 'driver-wt')
586
986
  await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
587
987
 
588
- let innerCalls = 0
589
988
  const gen = fanOutLoopsGenerator(
590
989
  {
591
990
  ...defaultRound4Config(),
592
991
  loopsRepo,
593
992
  outDir,
594
993
  populationSize: 1,
595
- proposers: [seat({ maxMetricCalls: 4 })],
994
+ proposers: [seat({ maxMetricCalls: 10, python })],
596
995
  prefilter: { enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false },
597
996
  },
598
997
  {
599
- smokeRunner: async () => {
600
- innerCalls += 1
998
+ ...IMPLEMENTATION_REFS,
999
+ smokeRunner: async ({ scratchPath }) => {
1000
+ const candidate = await readFile(join(scratchPath, SURFACE), 'utf8')
601
1001
  return {
602
1002
  iid: 'astropy__astropy-13033',
603
1003
  pass: true,
604
1004
  reason: 'stub smoke (integration)',
605
- resolved: innerCalls > 1,
1005
+ resolved: candidate === winner,
606
1006
  patchLines: 1,
607
1007
  wallS: 0,
608
1008
  verifyPass: true,
@@ -610,6 +1010,24 @@ describe('integration: real adapter roundtrip', () => {
610
1010
  },
611
1011
  smokeInstanceId: 'astropy__astropy-13033',
612
1012
  scoreSplit: null,
1013
+ gepaOptimizer: {
1014
+ model: 'test-optimizer',
1015
+ baseUrl: `http://127.0.0.1:${address.port}/v1`,
1016
+ apiKey: 'local-test-key',
1017
+ budget: {
1018
+ maxCostUsd: 1,
1019
+ maxRequests: 10,
1020
+ maxRequestBytes: 100_000,
1021
+ maxResponseBytes: 100_000,
1022
+ maxOutputTokensPerRequest: 2_000,
1023
+ pricing: {
1024
+ inputUsdPerMillion: 1,
1025
+ cachedInputUsdPerMillion: 0.1,
1026
+ cacheWriteUsdPerMillion: 1.25,
1027
+ outputUsdPerMillion: 5,
1028
+ },
1029
+ },
1030
+ },
613
1031
  },
614
1032
  )
615
1033
  const result = await gen.generate({
@@ -621,13 +1039,19 @@ describe('integration: real adapter roundtrip', () => {
621
1039
  generation: 0,
622
1040
  candidateIndex: 0,
623
1041
  })
624
- // The bridge ran: inner provenance must show >= 1 scored callback.
625
1042
  const inner = JSON.parse(
626
1043
  await readFile(join(outDir, 'gepa-seat', 'gen0-gepa-author', 'inner-provenance.json'), 'utf8'),
627
1044
  )
1045
+ expect(result.applied, `${result.summary}\n${JSON.stringify(inner.innerScores, null, 2)}`).toBe(true)
1046
+ expect(await readFile(join(driverWt, SURFACE), 'utf8')).toBe(winner)
628
1047
  expect(inner.innerCallCount).toBeGreaterThanOrEqual(1)
629
- expect(typeof result.applied).toBe('boolean')
1048
+ expect(inner.accountingComplete).toBe(true)
1049
+ expect(inner.incompleteReasons).toEqual([])
1050
+ expect(inner.tokenUsage.calls).toBeGreaterThan(0)
630
1051
  } finally {
1052
+ await new Promise<void>((resolve, reject) =>
1053
+ modelServer.close((error) => (error ? reject(error) : resolve())),
1054
+ )
631
1055
  await rm(outDir, { recursive: true, force: true })
632
1056
  await rm(loopsRepo, { recursive: true, force: true })
633
1057
  }