@tangle-network/agent-bench 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/README.md +7 -0
  4. package/dist/adapters.js +23 -23
  5. package/dist/benchmarks/_harness.d.ts +1 -1
  6. package/dist/benchmarks/_harness.js +1 -1
  7. package/dist/benchmarks/aec-bench.js +2 -2
  8. package/dist/benchmarks/agentbench.js +2 -2
  9. package/dist/benchmarks/appworld.js +2 -2
  10. package/dist/benchmarks/bfcl.js +2 -2
  11. package/dist/benchmarks/commit0.js +2 -2
  12. package/dist/benchmarks/crag.js +2 -2
  13. package/dist/benchmarks/dabstep.js +2 -2
  14. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  15. package/dist/benchmarks/finresearchbench.js +2 -2
  16. package/dist/benchmarks/nomiracl.js +2 -2
  17. package/dist/benchmarks/open-rag-bench.js +2 -2
  18. package/dist/benchmarks/programbench.js +2 -2
  19. package/dist/benchmarks/ragbench.js +2 -2
  20. package/dist/benchmarks/swe-bench.js +2 -2
  21. package/dist/benchmarks/t2-ragbench.js +2 -2
  22. package/dist/benchmarks/tau-bench-shared.js +2 -2
  23. package/dist/benchmarks/tau2-bench.js +3 -3
  24. package/dist/benchmarks/tau3-banking.js +3 -3
  25. package/dist/benchmarks/terminal-bench.js +2 -2
  26. package/dist/benchmarks/toollm.js +2 -2
  27. package/dist/benchmarks/webarena-verified.js +2 -2
  28. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  29. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  30. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  31. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  32. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  33. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  34. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  35. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  36. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  37. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  38. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  39. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  40. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  41. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  42. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  43. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  44. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  45. package/dist/{chunk-PWQVGAJB.js → chunk-V7AEBY6U.js} +21 -21
  46. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  47. package/dist/chunk-WSKWVEQB.js.map +1 -0
  48. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  49. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  50. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  51. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  52. package/dist/index.js +23 -23
  53. package/package.json +5 -5
  54. package/scripts/trata-hedge/README.md +6 -5
  55. package/scripts/verify-packed-consumer.mjs +1 -1
  56. package/src/benchmarks/_harness.ts +20 -2
  57. package/src/benchmarks/humaneval.test.mts +2 -2
  58. package/src/gate.ts +1 -1
  59. package/src/hev-eval.mts +5 -2
  60. package/src/hev-improve.mts +118 -73
  61. package/src/official-optimizer-config.mts +89 -0
  62. package/src/official-optimizer-config.test.mts +88 -0
  63. package/src/profiles.ts +2 -2
  64. package/src/quant-arena/README.md +144 -0
  65. package/src/quant-arena/backtest.test.mts +135 -0
  66. package/src/quant-arena/backtest.ts +218 -0
  67. package/src/quant-arena/data.test.mts +44 -0
  68. package/src/quant-arena/data.ts +141 -0
  69. package/src/quant-arena/driver.test.mts +253 -0
  70. package/src/quant-arena/driver.ts +219 -0
  71. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  72. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  73. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  74. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  75. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  76. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  77. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  78. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  83. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  84. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  85. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  86. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  87. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  88. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  89. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  94. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  95. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  96. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  97. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  98. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  99. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  100. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  101. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  102. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  103. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  104. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  105. package/src/quant-arena/holdout-certify.mts +206 -0
  106. package/src/quant-arena/holdout-certify.test.mts +82 -0
  107. package/src/quant-arena/leak-audit.test.mts +79 -0
  108. package/src/quant-arena/leak-audit.ts +95 -0
  109. package/src/quant-arena/make-fixtures.mts +161 -0
  110. package/src/quant-arena/multiplicity.test.mts +68 -0
  111. package/src/quant-arena/multiplicity.ts +87 -0
  112. package/src/quant-arena/nautilus-certify.ts +31 -0
  113. package/src/quant-arena/oms.ts +90 -0
  114. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  115. package/src/quant-arena/python/pyproject.toml +8 -0
  116. package/src/quant-arena/python/uv.lock +1297 -0
  117. package/src/quant-arena/python/vbt-worker.py +192 -0
  118. package/src/quant-arena/quant-loop.mts +813 -0
  119. package/src/quant-arena/quant-loop.test.mts +75 -0
  120. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  121. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  122. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  123. package/src/quant-arena/types.ts +133 -0
  124. package/src/quant-arena/vbt-client.ts +321 -0
  125. package/src/quant-arena/vbt-parity.test.mts +183 -0
  126. package/src/quant-arena/windows.test.mts +45 -0
  127. package/src/quant-arena/windows.ts +54 -0
  128. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  129. package/src/rollout-ledger/backfill-swe-arena.test.mts +342 -0
  130. package/src/rollout-ledger/settle-capture.mts +442 -0
  131. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  132. package/src/smoke-structural-rollout.mts +15 -9
  133. package/src/swe-arena/activation.mts +225 -0
  134. package/src/swe-arena/activation.test.mts +300 -0
  135. package/src/swe-arena/arms.ts +30 -14
  136. package/src/swe-arena/briefing.mts +217 -0
  137. package/src/swe-arena/briefing.test.mts +178 -0
  138. package/src/swe-arena/calibrate.ts +102 -1
  139. package/src/swe-arena/cell-evidence.mts +41 -9
  140. package/src/swe-arena/factory-command-container.ts +284 -0
  141. package/src/swe-arena/factory-judge-child.mts +228 -0
  142. package/src/swe-arena/factory.test.mts +643 -0
  143. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  144. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  145. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  146. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  147. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  148. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  149. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  150. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  151. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  152. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  153. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  154. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  155. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  156. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  157. package/src/swe-arena/fixtures.ts +135 -2
  158. package/src/swe-arena/gepa-seat.mts +877 -0
  159. package/src/swe-arena/gepa-seat.test.mts +1059 -0
  160. package/src/swe-arena/implementation-ref.test.mts +64 -0
  161. package/src/swe-arena/implementation-ref.ts +62 -0
  162. package/src/swe-arena/ledger-orphans.mts +77 -0
  163. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  164. package/src/swe-arena/outer-loop.mts +710 -33
  165. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  166. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  167. package/src/swe-arena/proposer-fanout.mts +327 -31
  168. package/src/swe-arena/proposer-fanout.test.mts +246 -0
  169. package/src/swe-arena/proposer-provenance.mts +172 -0
  170. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  171. package/src/swe-arena/run-experiment.mts +367 -1
  172. package/src/swe-arena/run-report.mts +75 -0
  173. package/src/swe-arena/run-supervisor.test.mts +5 -3
  174. package/src/swe-arena/score-split.mts +140 -0
  175. package/src/swe-arena/score-split.test.mts +123 -0
  176. package/src/swe-arena/scratch-worktree.test.mts +55 -0
  177. package/src/swe-arena/scratch-worktree.ts +34 -0
  178. package/src/swe-arena/types.ts +52 -0
  179. package/src/swe-code-improve.mts +24 -25
  180. package/src/swe-improve.mts +129 -96
  181. package/src/swe-local-proof.mts +6 -1
  182. package/src/swe-stream.mts +4 -2
  183. package/src/swe-structural.mts +245 -837
  184. package/src/tb-container-executor.test.mts +30 -6
  185. package/src/tb-supervisor-sidecar.mts +2 -1
  186. package/src/trata-gepa.mts +182 -245
  187. package/dist/chunk-LRRD7NAG.js.map +0 -1
  188. package/src/live-improve-campaign-mbpp.mts +0 -641
  189. package/src/live-improve-campaign.mts +0 -500
  190. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  191. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  192. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  193. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  194. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  195. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  196. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  197. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  198. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  199. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  200. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  201. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  202. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  203. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  204. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  205. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  206. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  207. /package/dist/{chunk-PWQVGAJB.js.map → chunk-V7AEBY6U.js.map} +0 -0
  208. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  209. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  210. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  211. /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
@@ -0,0 +1,1059 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
4
+ import { createServer } from 'node:http'
5
+ import { tmpdir } from 'node:os'
6
+ import { join } from 'node:path'
7
+ import type {
8
+ DispatchContext,
9
+ OptimizationMethodProvenance,
10
+ } from '@tangle-network/agent-eval/campaign'
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
12
+ import { ACTIVATION_PREDICATE_RELPATH, parseActivationPredicate } from './activation.mts'
13
+ import {
14
+ DEFAULT_GEPA_PYTHON,
15
+ DEFAULT_MAX_METRIC_CALLS,
16
+ GEPA_INNER_RUNS_DIRNAME,
17
+ GEPA_PYTHON_INSTALL_HINT,
18
+ gepaBridgeScenarios,
19
+ gepaSeatEvaluationId,
20
+ innerSmokeComposite,
21
+ innerSmokeJudge,
22
+ isGepaSeat,
23
+ mechanicalActivationPredicate,
24
+ probeGepaRuntime,
25
+ recipeEvaluationBudget,
26
+ recipeForSeat,
27
+ recordGepaSeatInnerRun,
28
+ validateGepaSeat,
29
+ type GepaMethodFactory,
30
+ type GepaSeatInnerRun,
31
+ type GepaSeatSpec,
32
+ type ProbeExec,
33
+ } from './gepa-seat.mts'
34
+ import { defaultRound4Config, type OuterLoopConfig } from './outer-loop.mts'
35
+ import { fanOutLoopsGenerator, type ProposerSpec, type SmokeRunner, type SmokeVerdict } from './proposer-fanout.mts'
36
+ import { captureProposerProvenance } from './proposer-provenance.mts'
37
+ import { runOk } from './proc.ts'
38
+
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
44
+
45
+ const seat = (over: Partial<ProposerSpec> = {}): ProposerSpec => ({
46
+ name: 'gepa-author',
47
+ engine: 'gepa',
48
+ surface: SURFACE,
49
+ ...over,
50
+ })
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Spec validation.
54
+ // ---------------------------------------------------------------------------
55
+
56
+ describe('validateGepaSeat', () => {
57
+ it('accepts an engine seat and isGepaSeat discriminates on engine', () => {
58
+ expect(() => validateGepaSeat(seat())).not.toThrow()
59
+ expect(() => validateGepaSeat(seat({ engine: 'omni', maxMetricCalls: 8 }))).not.toThrow()
60
+ expect(isGepaSeat(seat())).toBe(true)
61
+ expect(isGepaSeat({ name: 'x', harness: 'claude' })).toBe(false)
62
+ })
63
+
64
+ it('requires a surface inside the declared change-space', () => {
65
+ expect(() => validateGepaSeat(seat({ surface: undefined }))).toThrow(/surface is required/)
66
+ expect(() => validateGepaSeat(seat({ surface: 'judge.py' }))).toThrow(/outside the declared change-space/)
67
+ expect(() => validateGepaSeat(seat({ surface: '../escape.md' }))).toThrow(/outside the declared change-space/)
68
+ })
69
+
70
+ it('rejects harness-seat fields on an engine seat instead of silently ignoring them', () => {
71
+ expect(() => validateGepaSeat(seat({ harness: 'claude' }))).toThrow(/'harness' belongs to harness-authored/)
72
+ expect(() => validateGepaSeat(seat({ merge: true }))).toThrow(/'merge'/)
73
+ expect(() => validateGepaSeat(seat({ model: 'x' }))).toThrow(/'model'/)
74
+ expect(() => validateGepaSeat(seat({ profile: 'p.json' }))).toThrow(/'profile'/)
75
+ })
76
+
77
+ it('bounds the budget: positive integer calls, omni needs >= 4, positive cost cap', () => {
78
+ expect(() => validateGepaSeat(seat({ maxMetricCalls: 0 }))).toThrow(/maxMetricCalls/)
79
+ expect(() => validateGepaSeat(seat({ maxMetricCalls: 2.5 }))).toThrow(/maxMetricCalls/)
80
+ expect(() => validateGepaSeat(seat({ engine: 'omni', maxMetricCalls: 3 }))).toThrow(/omni.*needs maxMetricCalls >= 4/)
81
+ expect(() => validateGepaSeat(seat({ maxProposerCostUsd: 0 }))).toThrow(/maxProposerCostUsd/)
82
+ expect(() => validateGepaSeat(seat({ engine: 'nope' as never }))).toThrow(/engine must be one of/)
83
+ })
84
+ })
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Recipe / budget cap.
88
+ // ---------------------------------------------------------------------------
89
+
90
+ describe('recipeForSeat', () => {
91
+ it("'gepa' is one bounded engine run carrying the full budget (default 10)", () => {
92
+ const recipe = recipeForSeat(seat() as GepaSeatSpec)
93
+ expect(recipe).toMatchObject({ kind: 'engine', run: { engine: 'gepa', maxEvaluations: DEFAULT_MAX_METRIC_CALLS } })
94
+ expect(recipeEvaluationBudget(recipe)).toBe(DEFAULT_MAX_METRIC_CALLS)
95
+ })
96
+
97
+ it("'omni' uses the official recipe and its four bounded runs preserve the budget", () => {
98
+ const recipe = recipeForSeat(seat({ engine: 'omni', maxMetricCalls: 10 }) as GepaSeatSpec)
99
+ expect(recipe.kind).toBe('omni')
100
+ if (recipe.kind !== 'omni') throw new Error('unreachable')
101
+ expect(recipe.explore.map((r) => r.engine)).toEqual(['gepa', 'autoresearch', 'meta_harness'])
102
+ expect(recipe.continueWith.engine).toBe('gepa')
103
+ expect(recipeEvaluationBudget(recipe)).toBe(10)
104
+ for (const run of [...recipe.explore, recipe.continueWith]) {
105
+ expect(run.maxEvaluations).toBeGreaterThan(0)
106
+ expect(run.maxProposerCostUsd).toBeGreaterThan(0)
107
+ }
108
+ })
109
+
110
+ it('every budget from 4 upward is preserved exactly by the omni split', () => {
111
+ for (const calls of [4, 5, 8, 12, 24]) {
112
+ const recipe = recipeForSeat(seat({ engine: 'omni', maxMetricCalls: calls }) as GepaSeatSpec)
113
+ expect(recipeEvaluationBudget(recipe)).toBe(calls)
114
+ }
115
+ })
116
+ })
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Public-only bridge examples.
120
+ // ---------------------------------------------------------------------------
121
+
122
+ describe('gepaBridgeScenarios (public-only invariant)', () => {
123
+ const split = { privateInstances: ['django__django-11532', 'sphinx-doc__sphinx-9658'] }
124
+
125
+ it('serializes ONLY the public smoke instance (train + a distinct-id selection alias)', () => {
126
+ const { train, selection } = gepaBridgeScenarios('astropy__astropy-13033', split)
127
+ expect(train).toEqual([{ id: 'astropy__astropy-13033', kind: 'swe-smoke', smokeIid: 'astropy__astropy-13033' }])
128
+ expect(selection[0]!.id).toBe('astropy__astropy-13033::selection')
129
+ expect(selection[0]!.smokeIid).toBe('astropy__astropy-13033')
130
+ // Disjoint ids — the adapter's scenario map requires uniqueness.
131
+ expect(train[0]!.id).not.toBe(selection[0]!.id)
132
+ const serialized = JSON.stringify([...train, ...selection])
133
+ for (const iid of split.privateInstances) expect(serialized).not.toContain(iid)
134
+ })
135
+
136
+ it('fails loud when the smoke instance is private — private ids never cross the bridge', () => {
137
+ expect(() => gepaBridgeScenarios('django__django-11532', split)).toThrow(/PRIVATE under the score split/)
138
+ })
139
+
140
+ it('passes through with no split configured (pre-gen-5 behavior)', () => {
141
+ expect(gepaBridgeScenarios('astropy__astropy-13033', null).train).toHaveLength(1)
142
+ })
143
+ })
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Inner score.
147
+ // ---------------------------------------------------------------------------
148
+
149
+ describe('inner smoke score', () => {
150
+ const verdict = (over: Partial<SmokeVerdict>): SmokeVerdict => ({
151
+ iid: 'astropy__astropy-13033',
152
+ pass: true,
153
+ reason: 'ok',
154
+ resolved: false,
155
+ patchLines: 3,
156
+ wallS: 60,
157
+ ...over,
158
+ })
159
+
160
+ it('resolve dominates; verify-pass is a bounded tiebreak that can never beat a resolve', () => {
161
+ expect(innerSmokeComposite(verdict({ resolved: false, verifyPass: false }))).toBe(0)
162
+ expect(innerSmokeComposite(verdict({ resolved: false, verifyPass: true }))).toBe(0.25)
163
+ expect(innerSmokeComposite(verdict({ resolved: true, verifyPass: false }))).toBe(1)
164
+ expect(innerSmokeComposite(verdict({ resolved: true, verifyPass: true }))).toBe(1.25)
165
+ // Older verdicts without the field score as no verify signal.
166
+ expect(innerSmokeComposite(verdict({ resolved: true }))).toBe(1)
167
+ })
168
+
169
+ it('the judge reports both dimensions and the composite', async () => {
170
+ const judge = innerSmokeJudge()
171
+ const score = await judge.score({
172
+ artifact: verdict({ resolved: true, verifyPass: true, reason: 'smoke line' }),
173
+ scenario: { id: 'x', kind: 'swe-smoke', smokeIid: 'x' },
174
+ signal: new AbortController().signal,
175
+ })
176
+ expect(score).toMatchObject({ dimensions: { resolved: 1, verifyPass: 1 }, composite: 1.25, notes: 'smoke line' })
177
+ })
178
+ })
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Optional Python runtime: loud failures with exact instructions.
182
+ // ---------------------------------------------------------------------------
183
+
184
+ describe('probeGepaRuntime', () => {
185
+ const execFailingOn =
186
+ (failFragment: string, stderr: string): ProbeExec =>
187
+ async (_cmd, args) => {
188
+ const line = args.join(' ')
189
+ if (line.includes(failFragment)) return { code: 1, stdout: '', stderr }
190
+ if (line === '--version') return { code: 0, stdout: 'Python 3.12.3', stderr: '' }
191
+ return { code: 0, stdout: '0.2.0', stderr: '' }
192
+ }
193
+
194
+ it('fails loud with pip install instructions when the bridge module is missing', async () => {
195
+ const exec = execFailingOn('agent_eval_rpc.gepa_bridge', "ModuleNotFoundError: No module named 'agent_eval_rpc'")
196
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(
197
+ GEPA_PYTHON_INSTALL_HINT,
198
+ )
199
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(/not installed/)
200
+ })
201
+
202
+ it('fails loud when the installed gepa lacks the multi-engine optimize_anything API', async () => {
203
+ const exec = execFailingOn('OptimizeAnythingConfig', "ImportError: cannot import name 'OptimizeAnythingConfig'")
204
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(/optimize_anything API/)
205
+ })
206
+
207
+ it('fails loud when python itself is missing', async () => {
208
+ const exec: ProbeExec = async () => ({ code: 127, stdout: '', stderr: 'not found' })
209
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).rejects.toThrow(/--version' failed/)
210
+ })
211
+
212
+ it('returns python + gepa versions on a complete runtime', async () => {
213
+ const exec: ProbeExec = async (_cmd, args) =>
214
+ args.join(' ') === '--version'
215
+ ? { code: 0, stdout: 'Python 3.12.3\n', stderr: '' }
216
+ : { code: 0, stdout: 'source\n', stderr: '' }
217
+ await expect(probeGepaRuntime('python3', exec, 'gepa-author')).resolves.toEqual({
218
+ pythonVersion: 'Python 3.12.3',
219
+ gepaVersion: 'source',
220
+ })
221
+ })
222
+ })
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Provenance capture at t=0.
226
+ // ---------------------------------------------------------------------------
227
+
228
+ describe('captureProposerProvenance with a gepa seat', () => {
229
+ const okExec: ProbeExec = async (cmd, args) => {
230
+ const line = args.join(' ')
231
+ if (line === '--version') {
232
+ return { code: 0, stdout: cmd === 'python3' ? 'Python 3.12.3' : `${cmd} 1.0.0`, stderr: '' }
233
+ }
234
+ return { code: 0, stdout: 'source', stderr: '' }
235
+ }
236
+ it('records engine, surface, gepa version, bridge module, and the python runtime as harnessVersion', async () => {
237
+ const record = await captureProposerProvenance([{ name: 'claude-author', harness: 'claude' }, seat()], {
238
+ exec: okExec,
239
+ readSettingsModel: () => 'settings-model',
240
+ })
241
+ const gepa = record.proposers.find((p) => p.name === 'gepa-author')!
242
+ expect(gepa).toMatchObject({
243
+ engine: 'gepa',
244
+ surface: SURFACE,
245
+ gepaVersion: 'source',
246
+ bridge: 'agent_eval_rpc.gepa_bridge',
247
+ harnessVersion: 'Python 3.12.3',
248
+ pinnedModel: null,
249
+ merge: false,
250
+ })
251
+ expect(gepa.harness).toBeUndefined()
252
+ // The claude seat is untouched by the gepa capture path.
253
+ expect(record.proposers.find((p) => p.name === 'claude-author')).toMatchObject({
254
+ harness: 'claude',
255
+ settingsModel: 'settings-model',
256
+ })
257
+ })
258
+
259
+ it('fails LOUD at t=0 when the python runtime is missing — with install instructions', async () => {
260
+ const exec: ProbeExec = async (_cmd, args) =>
261
+ args.join(' ').includes('gepa_bridge')
262
+ ? { code: 1, stdout: '', stderr: 'ModuleNotFoundError' }
263
+ : { code: 0, stdout: 'Python 3.12.3', stderr: '' }
264
+ await expect(captureProposerProvenance([seat()], { exec })).rejects.toThrow(
265
+ GEPA_PYTHON_INSTALL_HINT,
266
+ )
267
+ })
268
+ })
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Mechanical activation predicate.
272
+ // ---------------------------------------------------------------------------
273
+
274
+ describe('mechanicalActivationPredicate', () => {
275
+ it('targets the longest added line and produces a parseable grep predicate', () => {
276
+ const seed = 'alpha\nshared line stays here\n'
277
+ const winner = 'alpha\nshared line stays here\nAlways run the neighboring test file before finalizing.\nshort\n'
278
+ const predicate = mechanicalActivationPredicate(seed, winner, SURFACE)!
279
+ expect(predicate.kind).toBe('grep')
280
+ expect(predicate.pattern).toContain('Always run the neighboring test file')
281
+ const parsed = parseActivationPredicate(JSON.stringify(predicate))
282
+ expect(parsed.ok).toBe(true)
283
+ // The pattern is regex-escaped: it must match its own source line.
284
+ expect(new RegExp(predicate.pattern!).test('Always run the neighboring test file before finalizing.')).toBe(true)
285
+ })
286
+
287
+ it('escapes regex metacharacters in the added line', () => {
288
+ const predicate = mechanicalActivationPredicate('', 'Use pattern (a|b).* with $VAR [strictly].\n', SURFACE)!
289
+ expect(new RegExp(predicate.pattern!).test('Use pattern (a|b).* with $VAR [strictly].')).toBe(true)
290
+ expect(new RegExp(predicate.pattern!).test('Use pattern axb1* with 2VAR strictly.')).toBe(false)
291
+ })
292
+
293
+ it('returns null when no added line is distinctive enough', () => {
294
+ expect(mechanicalActivationPredicate('a\nb\n', 'a\nb\nshort\n', SURFACE)).toBeNull()
295
+ expect(mechanicalActivationPredicate('same\n', 'same\n', SURFACE)).toBeNull()
296
+ })
297
+ })
298
+
299
+ describe('recordGepaSeatInnerRun', () => {
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 () => {
381
+ const dir = await mkdtemp(join(tmpdir(), 'gepa-prov-'))
382
+ try {
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)
402
+ } finally {
403
+ await rm(dir, { recursive: true, force: true })
404
+ }
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
+ })
422
+ })
423
+
424
+ // ---------------------------------------------------------------------------
425
+ // The seat inside the fan-out generator, against a real temp git repo.
426
+ // ---------------------------------------------------------------------------
427
+
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
+ })
486
+
487
+ /** Mimics the adapter's loop: score the seed and each provided candidate via
488
+ * the seat's dispatch + judge, return the best-scoring candidate — exactly
489
+ * the contract gepaOptimizationMethod fulfills through the Python bridge. */
490
+ const fakeGepaFactory =
491
+ (candidates: string[], observed?: { config?: unknown }): GepaMethodFactory =>
492
+ (config) => {
493
+ if (observed) observed.config = config
494
+ return {
495
+ name: config.name ?? 'fake-gepa',
496
+ async optimize(input) {
497
+ const judge = input.judges[0]!
498
+ const scenario = input.trainScenarios[0]!
499
+ let best = { surface: input.baselineSurface as string, composite: -Infinity }
500
+ for (const candidate of [input.baselineSurface as string, ...candidates]) {
501
+ const artifact = await input.dispatchWithSurface(candidate, scenario, fakeCtx)
502
+ const score = await judge.score({ artifact, scenario, signal: new AbortController().signal })
503
+ if (score.composite > best.composite) best = { surface: candidate, composite: score.composite }
504
+ }
505
+ return {
506
+ winnerSurface: best.surface,
507
+ cost: { totalCostUsd: 0.125, accountingComplete: true, incompleteReasons: [] },
508
+ durationMs: 1,
509
+ provenance: fullProvenance(),
510
+ }
511
+ },
512
+ }
513
+ }
514
+
515
+ describe('fanOutLoopsGenerator with the gepa seat', () => {
516
+ let loopsRepo: string
517
+ let outDir: string
518
+ let driverWt: string
519
+
520
+ const git = async (args: string[], cwd: string): Promise<string> =>
521
+ (await runOk('git', ['-C', cwd, ...args])).stdout.trim()
522
+
523
+ const SEED = '# worker coding system\nkeep tests green\n'
524
+
525
+ beforeEach(async () => {
526
+ loopsRepo = await mkdtemp(join(tmpdir(), 'gepa-repo-'))
527
+ outDir = await mkdtemp(join(tmpdir(), 'gepa-out-'))
528
+ await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
529
+ await git(['config', 'user.email', 't@t.dev'], loopsRepo)
530
+ await git(['config', 'user.name', 'T'], loopsRepo)
531
+ await mkdir(join(loopsRepo, 'extensions', 'pi', 'prompts'), { recursive: true })
532
+ await writeFile(join(loopsRepo, SURFACE), SEED)
533
+ await git(['add', '-A'], loopsRepo)
534
+ await git(['commit', '-q', '-m', 'init'], loopsRepo)
535
+ driverWt = join(outDir, 'driver-wt')
536
+ await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
537
+ })
538
+
539
+ afterEach(async () => {
540
+ await rm(outDir, { recursive: true, force: true })
541
+ await rm(loopsRepo, { recursive: true, force: true })
542
+ })
543
+
544
+ const baseConfig = (proposers: ProposerSpec[], over: Partial<OuterLoopConfig> = {}): OuterLoopConfig => ({
545
+ ...defaultRound4Config(),
546
+ loopsRepo,
547
+ outDir,
548
+ populationSize: proposers.length,
549
+ proposers,
550
+ prefilter: { enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false },
551
+ ...over,
552
+ })
553
+
554
+ const generatorArgs = (candidateIndex: number) => ({
555
+ worktreePath: driverWt,
556
+ report: undefined,
557
+ findings: [],
558
+ maxShots: 1,
559
+ signal: new AbortController().signal,
560
+ generation: 0,
561
+ candidateIndex,
562
+ })
563
+
564
+ const smokeVerdict = (over: Partial<SmokeVerdict> = {}): SmokeVerdict => ({
565
+ iid: 'astropy__astropy-13033',
566
+ pass: true,
567
+ reason: 'smoke ok',
568
+ resolved: false,
569
+ patchLines: 3,
570
+ wallS: 5,
571
+ verifyPass: false,
572
+ ...over,
573
+ })
574
+
575
+ it('construction fails loud without the smoke runner (the inner evaluator)', () => {
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/)
583
+ expect(() => fanOutLoopsGenerator(baseConfig([{ name: 'no-seat-kind' }]))).toThrow(/neither a harness nor an engine/)
584
+ })
585
+
586
+ it('materializes each candidate into the scratch surface, applies the winner through the normal prefilter path, and records provenance', async () => {
587
+ const WINNER = `${SEED}Always run the neighboring test file before finalizing.\n`
588
+ const LOSER = `${SEED}delete all tests\n`
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
+ }) => {
600
+ const content = await readFile(join(scratchPath, SURFACE), 'utf8')
601
+ seen.push({
602
+ content,
603
+ scratch: scratchPath,
604
+ evaluationKey,
605
+ hasCostLedger: costLedger !== undefined,
606
+ })
607
+ // The winner candidate resolves; the seed gets verify-pass only; the
608
+ // loser gets nothing — exercising resolve-dominates + tiebreak.
609
+ if (content === WINNER) return smokeVerdict({ resolved: true, verifyPass: true })
610
+ if (content === SEED) return smokeVerdict({ verifyPass: true })
611
+ return smokeVerdict()
612
+ }
613
+ const observed: { config?: unknown } = {}
614
+ const config = baseConfig([seat({ maxMetricCalls: 5 })], { activationGate: true })
615
+ const gen = fanOutLoopsGenerator(config, {
616
+ ...IMPLEMENTATION_REFS,
617
+ smokeRunner,
618
+ smokeInstanceId: 'astropy__astropy-13033',
619
+ scoreSplit: { privateInstances: ['django__django-11532'] },
620
+ gepaMethodFactory: fakeGepaFactory([LOSER, WINNER], observed),
621
+ gepaOptimizer: testOptimizer,
622
+ })
623
+
624
+ const result = await gen.generate(generatorArgs(0))
625
+
626
+ expect(result).toMatchObject({ applied: true, label: 'gepa-author' })
627
+ expect(result.rationale).toContain('engine gepa')
628
+ // 3 isolated inner calls (seed, loser, winner) + 1 stage-B prefilter smoke
629
+ // on the final candidate, all outside the driver worktree.
630
+ expect(seen).toHaveLength(4)
631
+ for (const call of seen) expect(call.scratch).not.toBe(driverWt)
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)
637
+ // The winner landed on the driver worktree with the mechanical predicate.
638
+ expect(await readFile(join(driverWt, SURFACE), 'utf8')).toBe(WINNER)
639
+ const predicate = parseActivationPredicate(await readFile(join(driverWt, ACTIVATION_PREDICATE_RELPATH), 'utf8'))
640
+ expect(predicate.ok).toBe(true)
641
+ // Only the surface + predicate changed.
642
+ const changed = (await runOk('git', ['-C', driverWt, 'status', '--porcelain', '--untracked-files=all'])).stdout
643
+ .split('\n')
644
+ .map((l) => l.slice(3).trim())
645
+ .filter(Boolean)
646
+ expect(changed.sort()).toEqual([ACTIVATION_PREDICATE_RELPATH, SURFACE].sort())
647
+ // Budget threaded into the adapter recipe.
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.
662
+ const inner = JSON.parse(await readFile(join(outDir, 'gepa-seat', 'gen0-gepa-author', 'inner-provenance.json'), 'utf8'))
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
+ })
700
+ expect(inner.innerScores.map((s: { composite: number }) => s.composite)).toEqual([0.25, 0, 1.25])
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)
704
+ expect(gen.drainPrefilterKills()).toEqual([])
705
+ })
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
+
858
+ it('enforces the inner-call budget cap fail-closed', async () => {
859
+ const runaway: GepaMethodFactory = () => ({
860
+ name: 'runaway',
861
+ async optimize(input) {
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
875
+ return {
876
+ winnerSurface: SEED,
877
+ cost: { totalCostUsd: 0, accountingComplete: false, incompleteReasons: [] },
878
+ durationMs: 1,
879
+ provenance: fullProvenance('runaway'),
880
+ }
881
+ },
882
+ })
883
+ const gen = fanOutLoopsGenerator(baseConfig([seat({ maxMetricCalls: 3 })]), {
884
+ ...IMPLEMENTATION_REFS,
885
+ smokeRunner: async () => smokeVerdict(),
886
+ smokeInstanceId: 'astropy__astropy-13033',
887
+ scoreSplit: null,
888
+ gepaMethodFactory: runaway,
889
+ })
890
+ await expect(gen.generate(generatorArgs(0))).rejects.toThrow(/inner-call budget 3 exhausted/)
891
+ })
892
+
893
+ it('refuses to feed a PRIVATE smoke verdict to the bridge', async () => {
894
+ const gen = fanOutLoopsGenerator(baseConfig([seat()]), {
895
+ ...IMPLEMENTATION_REFS,
896
+ smokeRunner: async () => smokeVerdict({ iid: 'django__django-11532' }),
897
+ smokeInstanceId: 'astropy__astropy-13033',
898
+ scoreSplit: { privateInstances: ['django__django-11532'] },
899
+ gepaMethodFactory: fakeGepaFactory([`${SEED}x line long enough\n`]),
900
+ })
901
+ await expect(gen.generate(generatorArgs(0))).rejects.toThrow(/PRIVATE instance django__django-11532/)
902
+ })
903
+
904
+ it('declines the slot without a kill when GEPA returns the seed unchanged', async () => {
905
+ const gen = fanOutLoopsGenerator(baseConfig([seat()]), {
906
+ ...IMPLEMENTATION_REFS,
907
+ smokeRunner: async () => smokeVerdict(),
908
+ smokeInstanceId: 'astropy__astropy-13033',
909
+ scoreSplit: null,
910
+ gepaMethodFactory: fakeGepaFactory([]),
911
+ })
912
+ const result = await gen.generate(generatorArgs(0))
913
+ expect(result.applied).toBe(false)
914
+ expect(result.summary).toContain('equals the seed')
915
+ expect(gen.drainPrefilterKills()).toEqual([])
916
+ expect((await runOk('git', ['-C', driverWt, 'status', '--porcelain'])).stdout.trim()).toBe('')
917
+ })
918
+ })
919
+
920
+ // ---------------------------------------------------------------------------
921
+ // Integration: one real Node-to-Python-to-score roundtrip through the installed
922
+ // bridge. The TypeScript adapter is a compile-time package dependency.
923
+ // ---------------------------------------------------------------------------
924
+
925
+ const pythonBridgeReady = (python: string): { ok: boolean; reason: string } => {
926
+ const probe = spawnSync(python, [
927
+ '-c',
928
+ 'import agent_eval_rpc.gepa_bridge; from gepa.optimize_anything import optimize_anything, OptimizeAnythingConfig',
929
+ ])
930
+ if (probe.status !== 0) {
931
+ return { ok: false, reason: `python bridge unavailable: ${String(probe.stderr).trim().split('\n').pop()}` }
932
+ }
933
+ return { ok: true, reason: '' }
934
+ }
935
+
936
+ describe('integration: real adapter roundtrip', () => {
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}`)
942
+ return
943
+ }
944
+
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
+
975
+ const loopsRepo = await mkdtemp(join(tmpdir(), 'gepa-int-repo-'))
976
+ const outDir = await mkdtemp(join(tmpdir(), 'gepa-int-out-'))
977
+ try {
978
+ await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
979
+ await runOk('git', ['-C', loopsRepo, 'config', 'user.email', 't@t.dev'])
980
+ await runOk('git', ['-C', loopsRepo, 'config', 'user.name', 'T'])
981
+ await mkdir(join(loopsRepo, 'extensions', 'pi', 'prompts'), { recursive: true })
982
+ await writeFile(join(loopsRepo, SURFACE), 'tiny synthetic surface\n')
983
+ await runOk('git', ['-C', loopsRepo, 'add', '-A'])
984
+ await runOk('git', ['-C', loopsRepo, 'commit', '-q', '-m', 'init'])
985
+ const driverWt = join(outDir, 'driver-wt')
986
+ await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', driverWt, 'HEAD'])
987
+
988
+ const gen = fanOutLoopsGenerator(
989
+ {
990
+ ...defaultRound4Config(),
991
+ loopsRepo,
992
+ outDir,
993
+ populationSize: 1,
994
+ proposers: [seat({ maxMetricCalls: 10, python })],
995
+ prefilter: { enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false },
996
+ },
997
+ {
998
+ ...IMPLEMENTATION_REFS,
999
+ smokeRunner: async ({ scratchPath }) => {
1000
+ const candidate = await readFile(join(scratchPath, SURFACE), 'utf8')
1001
+ return {
1002
+ iid: 'astropy__astropy-13033',
1003
+ pass: true,
1004
+ reason: 'stub smoke (integration)',
1005
+ resolved: candidate === winner,
1006
+ patchLines: 1,
1007
+ wallS: 0,
1008
+ verifyPass: true,
1009
+ }
1010
+ },
1011
+ smokeInstanceId: 'astropy__astropy-13033',
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
+ },
1031
+ },
1032
+ )
1033
+ const result = await gen.generate({
1034
+ worktreePath: driverWt,
1035
+ report: undefined,
1036
+ findings: [],
1037
+ maxShots: 1,
1038
+ signal: new AbortController().signal,
1039
+ generation: 0,
1040
+ candidateIndex: 0,
1041
+ })
1042
+ const inner = JSON.parse(
1043
+ await readFile(join(outDir, 'gepa-seat', 'gen0-gepa-author', 'inner-provenance.json'), 'utf8'),
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)
1047
+ expect(inner.innerCallCount).toBeGreaterThanOrEqual(1)
1048
+ expect(inner.accountingComplete).toBe(true)
1049
+ expect(inner.incompleteReasons).toEqual([])
1050
+ expect(inner.tokenUsage.calls).toBeGreaterThan(0)
1051
+ } finally {
1052
+ await new Promise<void>((resolve, reject) =>
1053
+ modelServer.close((error) => (error ? reject(error) : resolve())),
1054
+ )
1055
+ await rm(outDir, { recursive: true, force: true })
1056
+ await rm(loopsRepo, { recursive: true, force: true })
1057
+ }
1058
+ }, 300_000)
1059
+ })