@tangle-network/agent-bench 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +4 -1
- package/dist/benchmarks/_harness.js +3 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
- package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
- package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
- package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
- package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
- package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
- package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
- package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
- package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
- package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
- package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
- package/dist/chunk-EIETHPD5.js.map +1 -0
- package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
- package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
- package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
- package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
- package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
- package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
- package/dist/chunk-JSQOUKXS.js.map +1 -0
- package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
- package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
- package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
- package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
- package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
- package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +4 -4
- package/pier_agents/candidate_contract.py +238 -24
- package/pier_agents/tangle_candidate.py +75 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +65 -1
- package/scripts/verify-pier-agent.mts +6 -4
- package/src/benchmarks/_harness.test.mts +16 -1
- package/src/benchmarks/_harness.ts +9 -2
- package/src/benchmarks/terminal-bench.ts +2 -1
- package/src/gate.ts +1 -1
- package/src/hev-eval.mts +5 -2
- package/src/hev-improve.mts +118 -73
- package/src/official-optimizer-config.mts +89 -0
- package/src/official-optimizer-config.test.mts +88 -0
- package/src/profiles.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +1 -4
- package/src/swe-arena/activation.test.mts +10 -13
- package/src/swe-arena/gepa-seat.mts +425 -131
- package/src/swe-arena/gepa-seat.test.mts +524 -100
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/outer-loop.mts +103 -76
- package/src/swe-arena/proposer-fanout.mts +51 -36
- package/src/swe-arena/proposer-fanout.test.mts +0 -1
- package/src/swe-arena/proposer-provenance.mts +11 -16
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-code-improve.mts +24 -25
- package/src/swe-improve.mts +129 -96
- package/src/swe-local-proof.mts +6 -1
- package/src/swe-stream.mts +4 -2
- package/src/tb-container-executor.test.mts +30 -6
- package/src/tb-supervisor-sidecar.mts +2 -1
- package/src/trata-gepa.mts +182 -245
- package/dist/chunk-WSKWVEQB.js.map +0 -1
- package/dist/chunk-XYA4XSNU.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- package/src/swe-arena/lineage-record.mts +0 -164
- package/src/swe-arena/lineage-record.test.mts +0 -115
- /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
- /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
- /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
- /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
- /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
- /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
- /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
- /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
- /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
- /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
- /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
- /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
- /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
- /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
- /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
- /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
- /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
- /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
- /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
- /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
- /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
|
@@ -1,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 {
|
|
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
|
-
|
|
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
|
|
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'
|
|
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('
|
|
91
|
-
if (recipe.kind !== '
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
|
|
272
|
-
)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
expect(record.
|
|
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:
|
|
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<{
|
|
444
|
-
|
|
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({
|
|
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
|
|
467
|
-
// final candidate
|
|
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
|
-
|
|
483
|
-
|
|
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({
|
|
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
|
|
488
|
-
expect(
|
|
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
|
-
|
|
497
|
-
|
|
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:
|
|
542
|
-
//
|
|
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(
|
|
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
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
|
|
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
|
-
|
|
574
|
-
|
|
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:
|
|
994
|
+
proposers: [seat({ maxMetricCalls: 10, python })],
|
|
596
995
|
prefilter: { enabled: true, smokeInstance: 'cheapest-of-set', requireResolved: false },
|
|
597
996
|
},
|
|
598
997
|
{
|
|
599
|
-
|
|
600
|
-
|
|
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:
|
|
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(
|
|
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
|
}
|