@tangle-network/agent-bench 0.8.32 → 0.9.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.
@@ -152,6 +152,7 @@ describe('briefing text', () => {
152
152
  const repo = await mkdtemp(join(tmpdir(), 'briefing-repo-'))
153
153
  try {
154
154
  await runOk('git', ['init', '-q', '-b', 'main', repo])
155
+ await runOk('git', ['-C', repo, 'config', 'core.hooksPath', '/dev/null'])
155
156
  await runOk('git', ['-C', repo, 'config', 'user.email', 't@t.dev'])
156
157
  await runOk('git', ['-C', repo, 'config', 'user.name', 'T'])
157
158
  await writeFile(join(repo, 'base.txt'), 'x\n')
@@ -344,6 +344,7 @@ describe('arms: patch extraction (real git, no docker)', () => {
344
344
  const ws = await scratch('swe-arena-git-')
345
345
  const git = (...argv: string[]) => runOk('git', ['-C', ws, ...argv])
346
346
  await runOk('git', ['init', '-q', ws])
347
+ await runOk('git', ['-C', ws, 'config', 'core.hooksPath', '/dev/null'])
347
348
  await git('config', 'user.email', 't@t')
348
349
  await git('config', 'user.name', 't')
349
350
  await writeFile(join(ws, 'lib.py'), 'x = 1\n')
@@ -904,7 +905,7 @@ describe('serialized-judge', () => {
904
905
  const dir = await scratch('swe-arena-judge-flock-stale-bytes-')
905
906
  const lockFile = join(dir, 'lock')
906
907
  await writeFile(lockFile, 'half-written-or-dead-owner\n')
907
- await expect(withJudgeLock(lockFile, async () => 'acquired', { timeoutMs: 1_000 })).resolves.toBe('acquired')
908
+ await expect(withJudgeLock(lockFile, async () => 'acquired', { timeoutMs: 5_000 })).resolves.toBe('acquired')
908
909
  expect(await readFile(lockFile, 'utf8')).toBe('half-written-or-dead-owner\n')
909
910
  })
910
911
 
@@ -926,7 +927,7 @@ describe('serialized-judge', () => {
926
927
 
927
928
  await expect(result).rejects.toThrow('cancelled active judge')
928
929
  expect((await readFile(attempts, 'utf8')).trim().split('\n')).toEqual(['attempt'])
929
- await expect(withJudgeLock(lockFile, async () => 'released', { timeoutMs: 1_000 })).resolves.toBe('released')
930
+ await expect(withJudgeLock(lockFile, async () => 'released', { timeoutMs: 5_000 })).resolves.toBe('released')
930
931
  }, 15_000)
931
932
 
932
933
  it('bounds stale-container cleanup before starting the judge child', async () => {
@@ -246,6 +246,7 @@ async function makeSyntheticMirror(root: string): Promise<SyntheticMirror> {
246
246
  const mirror = join(root, 'mirror')
247
247
  await mkdir(mirror, { recursive: true })
248
248
  await runOk('git', ['-C', mirror, 'init', '-q', '-b', 'main'])
249
+ await runOk('git', ['-C', mirror, 'config', 'core.hooksPath', '/dev/null'])
249
250
  await writeFile(join(mirror, 'README.md'), '# synthetic\n')
250
251
  await writeFile(
251
252
  join(mirror, 'package.json'),
@@ -359,7 +360,8 @@ describe('parseVitestSummary', () => {
359
360
  })
360
361
  })
361
362
 
362
- describe('factory command credential isolation', () => {
363
+ // The isolated Docker controller uses the Linux daemon socket, never personal Docker contexts.
364
+ describe.skipIf(process.platform !== 'linux')('factory command credential isolation', () => {
363
365
  it('blocks arbitrary env, auth sockets, npm config, and home credentials from a package lifecycle script', async () => {
364
366
  const ambientHome = join(root, 'ambient-home')
365
367
  const ambientXdg = join(root, 'ambient-xdg')
@@ -506,7 +508,7 @@ describe('loadFactoryInstance', () => {
506
508
  // Judge child pipeline on the synthetic fixture.
507
509
  // ---------------------------------------------------------------------------
508
510
 
509
- describe('judgeFactoryPatch', () => {
511
+ describe.skipIf(process.platform !== 'linux')('judgeFactoryPatch', () => {
510
512
  it('gold (impl-only PR diff) resolves with full score', async () => {
511
513
  const { result } = await judgeFactoryPatch(goodInst, await goldImplPatch(goodInst))
512
514
  expect(result).toMatchObject({ resolved: true, score: 1, passed: 2, total: 2 })
@@ -597,7 +599,7 @@ describe('materializeFactoryWorkspace', () => {
597
599
  // Calibration admission gate — both rejection directions.
598
600
  // ---------------------------------------------------------------------------
599
601
 
600
- describe('calibrateFactoryInstance', () => {
602
+ describe.skipIf(process.platform !== 'linux')('calibrateFactoryInstance', () => {
601
603
  it('admits a well-formed instance (gold passes, base fails)', async () => {
602
604
  const r = await calibrateFactoryInstance(goodInst)
603
605
  expect(r).toMatchObject({
@@ -130,7 +130,7 @@ export type GepaSeatRecipe = Extract<
130
130
  /** Build the bounded recipe for a seat. The TOTAL inner-evaluation budget is
131
131
  * exactly `maxMetricCalls`. The adapter's local callback enforces the sum
132
132
  * of per-run limits, and the seat's own dispatch wrapper re-enforces it. */
133
- export function recipeForSeat(spec: GepaSeatSpec): GepaSeatRecipe {
133
+ export function recipeForSeat(spec: GepaSeatSpec, optimizerModel?: string): GepaSeatRecipe {
134
134
  const calls = spec.maxMetricCalls ?? DEFAULT_MAX_METRIC_CALLS
135
135
  const cost = spec.maxProposerCostUsd ?? DEFAULT_MAX_PROPOSER_COST_USD
136
136
  if (spec.engine === 'gepa') {
@@ -145,6 +145,7 @@ export function recipeForSeat(spec: GepaSeatSpec): GepaSeatRecipe {
145
145
  engine,
146
146
  maxEvaluations: perExplore,
147
147
  maxProposerCostUsd: perRunCost,
148
+ ...(engine !== 'gepa' && optimizerModel ? { engineConfig: { model: optimizerModel } } : {}),
148
149
  }))
149
150
  return {
150
151
  kind: 'omni',
@@ -758,6 +759,7 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
758
759
  : officialOptimizerModel({
759
760
  env: process.env,
760
761
  envPrefix: 'GEPA_OPTIMIZER',
762
+ anthropicEndpoint: spec.engine === 'omni',
761
763
  model: process.env.GEPA_OPTIMIZER_MODEL ?? config.arm.driverModel,
762
764
  baseUrl:
763
765
  process.env.GEPA_OPTIMIZER_BASE_URL ??
@@ -771,7 +773,7 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
771
773
  }))
772
774
  const method = factory({
773
775
  name: `gepa-seat:${spec.name}`,
774
- recipe,
776
+ recipe: recipeForSeat(spec, optimizer?.model),
775
777
  objective,
776
778
  evaluationId: gepaSeatEvaluationId({
777
779
  smokeInstanceId: deps.smokeInstanceId,
@@ -4,9 +4,12 @@ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promis
4
4
  import { createServer } from 'node:http'
5
5
  import { tmpdir } from 'node:os'
6
6
  import { join } from 'node:path'
7
- import type {
8
- DispatchContext,
9
- OptimizationMethodProvenance,
7
+ import {
8
+ gepaOptimizationMethod,
9
+ createRunCostLedger,
10
+ fsCampaignStorage,
11
+ type DispatchContext,
12
+ type OptimizationMethodProvenance,
10
13
  } from '@tangle-network/agent-eval/campaign'
11
14
  import { afterEach, beforeEach, describe, expect, it } from 'vitest'
12
15
  import { ACTIVATION_PREDICATE_RELPATH, parseActivationPredicate } from './activation.mts'
@@ -89,6 +92,68 @@ describe('validateGepaSeat', () => {
89
92
  // ---------------------------------------------------------------------------
90
93
 
91
94
  describe('recipeForSeat', () => {
95
+ it.each(['gepa', 'omni'] as const)('transports the real %s optimizer configuration', async (engine) => {
96
+ const runDir = await mkdtemp(join(tmpdir(), 'gepa-seat-transport-'))
97
+ const requests: unknown[] = []
98
+ try {
99
+ const optimizer = officialOptimizerModel({
100
+ env: { OPT_INPUT_USD_PER_MILLION: '1', OPT_CACHED_INPUT_USD_PER_MILLION: '1', OPT_CACHE_WRITE_USD_PER_MILLION: '1', OPT_OUTPUT_USD_PER_MILLION: '1' },
101
+ envPrefix: 'OPT', model: 'fixture-model', baseUrl: 'http://127.0.0.1:1/v1', apiKey: 'fixture-key',
102
+ maxCostUsd: 1, maxOutputTokensPerRequest: 100, anthropicEndpoint: engine === 'omni',
103
+ complete: async (request) => {
104
+ requests.push(request)
105
+ return { model: 'fixture-model', choices: [{ message: { content: 'ok' }, finish_reason: 'stop' }], usage: { prompt_tokens: 2, completion_tokens: 1, cost: 0.000003 } }
106
+ },
107
+ })
108
+ const spec = seat({ engine })
109
+ validateGepaSeat(spec)
110
+ const runtime = {
111
+ python: { implementation: 'CPython', version: '3.12.0' },
112
+ bridge: { package: 'agent-eval-rpc', version: 'fixture', sourceUrl: 'https://github.com/tangle-network/agent-eval', revision: 'fixture', sourceSha256: 'a'.repeat(64) },
113
+ optimizer: { package: 'gepa', version: 'fixture', sourceUrl: 'https://github.com/gepa-ai/gepa', revision: 'fixture', sourceSha256: 'b'.repeat(64) },
114
+ engineModules: [],
115
+ }
116
+ // Only the child optimizer is substituted; the real Eval proxy calls Runtime.
117
+ const bridge = join(runDir, 'transport.mjs')
118
+ await writeFile(bridge, `
119
+ import fs from 'node:fs'
120
+ const input = JSON.parse(fs.readFileSync(process.argv[process.argv.indexOf('--input') + 1], 'utf8'))
121
+ if (input.operation === 'inspect') {
122
+ fs.writeFileSync(process.argv[process.argv.indexOf('--output') + 1], JSON.stringify({ runtime: ${JSON.stringify(runtime)} }))
123
+ } else {
124
+ const omni = input.recipe.kind === 'omni'
125
+ if (omni && input.recipe.explore.some(run => run.engine !== 'gepa' && run.engineConfig.model !== input.modelProxy.model)) throw new Error('wrong engine model')
126
+ const send = maxTokens => fetch(input.modelProxy.baseUrl + (omni ? '/messages' : '/chat/completions'), {
127
+ method: 'POST',
128
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + input.modelProxy.apiKey, 'x-api-key': input.modelProxy.apiKey, 'anthropic-version': '2023-06-01' },
129
+ body: JSON.stringify({ model: input.modelProxy.model, max_tokens: maxTokens, messages: [{ role: 'user', content: 'transport proof' }] }),
130
+ })
131
+ const overBudget = await send(101)
132
+ if (overBudget.ok) throw new Error('output ceiling was not enforced')
133
+ await overBudget.text()
134
+ const response = await send(10)
135
+ if (!response.ok) throw new Error('model transport failed: ' + response.status + ' ' + await response.text())
136
+ await response.json()
137
+ throw new Error('transport-handshake-complete')
138
+ }
139
+ `)
140
+ const method = gepaOptimizationMethod({
141
+ recipe: recipeForSeat(spec, optimizer.model), optimizer,
142
+ objective: 'Check transport only', evaluationId: 'bench-transport',
143
+ runner: { command: process.execPath, args: [bridge] },
144
+ })
145
+ await expect(method.optimize({
146
+ baselineSurface: 'seed', trainScenarios: [{ id: 'train', kind: 'fixture' }], selectionScenarios: [{ id: 'selection', kind: 'fixture' }],
147
+ dispatchWithSurface: async () => 'unused',
148
+ judges: [{ name: 'fixture', dimensions: [{ key: 'score', description: 'fixture' }], score: async () => ({ dimensions: { score: 1 }, composite: 1 }) }],
149
+ runDir, seed: 42, runOptions: {}, costLedger: createRunCostLedger({ storage: fsCampaignStorage(), runDir: join(runDir, 'cost') }),
150
+ })).rejects.toThrow('transport-handshake-complete')
151
+ expect(requests).toHaveLength(1)
152
+ } finally {
153
+ await rm(runDir, { recursive: true, force: true })
154
+ }
155
+ })
156
+
92
157
  it("'gepa' is one bounded engine run carrying the full budget (default 10)", () => {
93
158
  const recipe = recipeForSeat(seat() as GepaSeatSpec)
94
159
  expect(recipe).toMatchObject({ kind: 'engine', run: { engine: 'gepa', maxEvaluations: DEFAULT_MAX_METRIC_CALLS } })
@@ -535,6 +600,7 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
535
600
  loopsRepo = await mkdtemp(join(tmpdir(), 'gepa-repo-'))
536
601
  outDir = await mkdtemp(join(tmpdir(), 'gepa-out-'))
537
602
  await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
603
+ await runOk('git', ['-C', loopsRepo, 'config', 'core.hooksPath', '/dev/null'])
538
604
  await git(['config', 'user.email', 't@t.dev'], loopsRepo)
539
605
  await git(['config', 'user.name', 'T'], loopsRepo)
540
606
  await mkdir(join(loopsRepo, 'extensions', 'pi', 'prompts'), { recursive: true })
@@ -995,6 +1061,7 @@ describe('integration: real adapter roundtrip', () => {
995
1061
  const outDir = await mkdtemp(join(tmpdir(), 'gepa-int-out-'))
996
1062
  try {
997
1063
  await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
1064
+ await runOk('git', ['-C', loopsRepo, 'config', 'core.hooksPath', '/dev/null'])
998
1065
  await runOk('git', ['-C', loopsRepo, 'config', 'user.email', 't@t.dev'])
999
1066
  await runOk('git', ['-C', loopsRepo, 'config', 'user.name', 'T'])
1000
1067
  await mkdir(join(loopsRepo, 'extensions', 'pi', 'prompts'), { recursive: true })
@@ -182,6 +182,7 @@ describe('loadFullCampaignCells + incumbentSurfaceHash (real fs/git)', () => {
182
182
 
183
183
  it('computes the same incumbent hash for the unchanged tip (baseCommit == candidateCommit, empty patch)', async () => {
184
184
  await runOk('git', ['init', '-q', '-b', 'main', repo])
185
+ await runOk('git', ['-C', repo, 'config', 'core.hooksPath', '/dev/null'])
185
186
  await runOk('git', ['-C', repo, 'config', 'user.email', 't@t.dev'])
186
187
  await runOk('git', ['-C', repo, 'config', 'user.name', 'T'])
187
188
  await writeFile(join(repo, 'a.txt'), 'x\n')
@@ -65,18 +65,16 @@ describe('run process-group timeout', () => {
65
65
  const dir = await mkdtemp(join(tmpdir(), 'swe-proc-tree-'))
66
66
  dirs.push(dir)
67
67
  const pidFile = join(dir, 'grandchild.pid')
68
- const grandchild = `process.on('SIGTERM',()=>{}); setInterval(()=>{},1000)`
68
+ const grandchild = `process.on('SIGTERM',()=>{}); require('node:fs').writeFileSync(process.argv[1],String(process.pid)); setInterval(()=>{},1000)`
69
69
  const parent = [
70
70
  `const {spawn}=require('node:child_process')`,
71
- `const fs=require('node:fs')`,
72
- `const child=spawn(process.execPath,['-e',${JSON.stringify(grandchild)}],{stdio:'ignore'})`,
73
- `fs.writeFileSync(process.argv[1],String(child.pid))`,
71
+ `spawn(process.execPath,['-e',${JSON.stringify(grandchild)},process.argv[1]],{stdio:'ignore'})`,
74
72
  `process.on('SIGTERM',()=>process.exit(0))`,
75
73
  `setInterval(()=>{},1000)`,
76
74
  ].join(';')
77
75
 
78
76
  const result = await run(process.execPath, ['-e', parent, pidFile], {
79
- timeoutMs: 100,
77
+ timeoutMs: 2_000,
80
78
  killGraceMs: 50,
81
79
  })
82
80
  const grandchildPid = Number(await readFile(pidFile, 'utf8'))
@@ -358,6 +358,7 @@ describe('fanOutLoopsGenerator', () => {
358
358
  loopsRepo = await mkdtemp(join(tmpdir(), 'fanout-repo-'))
359
359
  outDir = await mkdtemp(join(tmpdir(), 'fanout-out-'))
360
360
  await runOk('git', ['init', '-q', '-b', 'main', loopsRepo])
361
+ await runOk('git', ['-C', loopsRepo, 'config', 'core.hooksPath', '/dev/null'])
361
362
  await git(['config', 'user.email', 't@t.dev'], loopsRepo)
362
363
  await git(['config', 'user.name', 'T'], loopsRepo)
363
364
  await writeFile(join(loopsRepo, 'src.ts'), 'base\n')
@@ -399,6 +399,8 @@ export async function materializeFactoryWorkspace(
399
399
  await writeFile(join(dest, 'SPEC.md'), inst.spec)
400
400
 
401
401
  await runOk('git', ['-C', dest, 'init', '-q', '-b', 'work'])
402
+ // This synthetic benchmark repository must not execute the host's personal Git hooks.
403
+ await runOk('git', ['-C', dest, 'config', 'core.hooksPath', '/dev/null'])
402
404
  await runOk('git', ['-C', dest, 'config', 'user.email', 'factory-bench@local'])
403
405
  await runOk('git', ['-C', dest, 'config', 'user.name', 'factory-bench'])
404
406
  await runOk('git', ['-C', dest, 'add', '-A'])
@@ -21,6 +21,7 @@ describe('scratch worktrees', () => {
21
21
  const output = await mkdtemp(join(tmpdir(), 'scratch-worktree-out-'))
22
22
  roots.push(repository, output)
23
23
  await runOk('git', ['init', '-q', '-b', 'main', repository])
24
+ await runOk('git', ['-C', repository, 'config', 'core.hooksPath', '/dev/null'])
24
25
  await runOk('git', ['-C', repository, 'config', 'user.email', 'test@example.com'])
25
26
  await runOk('git', ['-C', repository, 'config', 'user.name', 'Test'])
26
27
  await writeFile(join(repository, 'seed.txt'), 'seed\n')
@@ -51,5 +52,5 @@ describe('scratch worktrees', () => {
51
52
  await runOk('git', ['-C', repository, 'worktree', 'list', '--porcelain'])
52
53
  ).stdout
53
54
  expect(listed.match(/^worktree /gmu)).toHaveLength(1)
54
- })
55
+ }, 60_000)
55
56
  })
@@ -93,9 +93,13 @@ describe('SWE worker prompts', () => {
93
93
 
94
94
  describe('SWE temporary directory', () => {
95
95
  it('stays absolute when model temperature is configured through TEMPERATURE', () => {
96
+ const priorTmpdir = process.env.TMPDIR
97
+ const priorTmp = process.env.TMP
96
98
  const priorTemp = process.env.TEMP
97
99
  const priorTemperature = process.env.TEMPERATURE
98
100
  try {
101
+ delete process.env.TMPDIR
102
+ delete process.env.TMP
99
103
  delete process.env.TEMP
100
104
  process.env.TEMPERATURE = '0.8'
101
105
  assert.equal(isAbsolute(absoluteSweTempDir()), true)
@@ -103,6 +107,10 @@ describe('SWE temporary directory', () => {
103
107
  process.env.TEMP = '0.8'
104
108
  assert.throws(() => absoluteSweTempDir(), /must be absolute.*TEMPERATURE/)
105
109
  } finally {
110
+ if (priorTmpdir === undefined) delete process.env.TMPDIR
111
+ else process.env.TMPDIR = priorTmpdir
112
+ if (priorTmp === undefined) delete process.env.TMP
113
+ else process.env.TMP = priorTmp
106
114
  if (priorTemp === undefined) delete process.env.TEMP
107
115
  else process.env.TEMP = priorTemp
108
116
  if (priorTemperature === undefined) delete process.env.TEMPERATURE
@@ -197,7 +205,7 @@ describe('isInsideJail (realpath containment)', () => {
197
205
  symlinkSync('/etc', link)
198
206
  // `resolveInJail` does `realpathSync(join(ws.dir, relPath))` then this containment check.
199
207
  const real = realpathSync(join(dir, 'escape/passwd'))
200
- assert.equal(real, '/etc/passwd')
208
+ assert.equal(real, realpathSync('/etc/passwd'))
201
209
  assert.equal(isInsideJail(jailRoot, real), false)
202
210
  })
203
211