@tangle-network/agent-bench 0.7.1 → 0.8.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 (131) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/HARNESS.md +1 -1
  3. package/dist/adapters.js +2 -2
  4. package/dist/benchmarks/appworld.d.ts +61 -2
  5. package/dist/benchmarks/appworld.js +111 -40
  6. package/dist/benchmarks/appworld.js.map +1 -1
  7. package/dist/benchmarks/cadbench.js +1 -1
  8. package/dist/benchmarks/cadgenbench.js +1 -1
  9. package/dist/benchmarks/finresearchbench.js +16 -22
  10. package/dist/benchmarks/finresearchbench.js.map +1 -1
  11. package/dist/benchmarks/finsearchcomp.js +16 -22
  12. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  13. package/dist/benchmarks/frames.js +18 -20
  14. package/dist/benchmarks/frames.js.map +1 -1
  15. package/dist/benchmarks/simpleqa.js +15 -19
  16. package/dist/benchmarks/simpleqa.js.map +1 -1
  17. package/dist/benchmarks/trata-hedge.js +20 -23
  18. package/dist/benchmarks/trata-hedge.js.map +1 -1
  19. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  20. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  21. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  22. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.js +10 -9
  25. package/dist/index.js.map +1 -1
  26. package/dist/router-turn-C2wMiDoo.js +31 -0
  27. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  28. package/package.json +7 -7
  29. package/scripts/appworld_driver.py +1 -170
  30. package/scripts/run-package-tests.mjs +56 -36
  31. package/scripts/run-package-tests.test.mjs +33 -0
  32. package/scripts/trata-hedge/README.md +3 -3
  33. package/scripts/trata-hedge/run.sh +6 -2
  34. package/src/aec-gate.mts +30 -9
  35. package/src/agent-graphs-gen2.mts +523 -0
  36. package/src/agent-graphs-gen3.mts +660 -0
  37. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  38. package/src/agent-graphs-improve.mts +747 -0
  39. package/src/atom-humaneval.mts +76 -55
  40. package/src/atom-mcp-e2e.mts +27 -13
  41. package/src/benchmarks/appworld.test.mts +119 -1
  42. package/src/benchmarks/appworld.ts +156 -33
  43. package/src/benchmarks/cadbench.ts +21 -8
  44. package/src/benchmarks/finresearchbench.ts +20 -16
  45. package/src/benchmarks/finsearchcomp.ts +20 -20
  46. package/src/benchmarks/frames.ts +19 -18
  47. package/src/benchmarks/simpleqa.ts +19 -17
  48. package/src/benchmarks/trata-hedge.ts +26 -18
  49. package/src/browser/ui-reviewer.ts +1 -1
  50. package/src/clbench-context-gate.mts +45 -9
  51. package/src/cloud-loop.mts +24 -6
  52. package/src/commit0-env-run.mts +6 -3
  53. package/src/commit0-env.ts +2 -3
  54. package/src/commit0-gate.mts +53 -24
  55. package/src/corpus.ts +3 -3
  56. package/src/david-attribution.mts +28 -9
  57. package/src/david-goliath.mts +31 -12
  58. package/src/egress-probe.mts +3 -3
  59. package/src/examples/lean-proof-gate.mts +10 -3
  60. package/src/examples/math-demo.mts +10 -3
  61. package/src/examples/strategy-demo.mts +10 -4
  62. package/src/fleet.mts +30 -8
  63. package/src/gate-cli.mts +9 -4
  64. package/src/gate.test.mts +7 -2
  65. package/src/gate.ts +23 -75
  66. package/src/generate-eval/certify.ts +13 -5
  67. package/src/hev-eval.mts +39 -10
  68. package/src/hev-improve.mts +28 -16
  69. package/src/hev-structural.mts +28 -20
  70. package/src/humaneval-gate.mts +24 -8
  71. package/src/humaneval-object-ablation.mts +52 -14
  72. package/src/humaneval-repair-gate.mts +53 -14
  73. package/src/mbpp-structural.mts +28 -20
  74. package/src/mcp-mount-probe.mts +23 -11
  75. package/src/official-optimizer-config.mts +61 -12
  76. package/src/official-optimizer-config.test.mts +72 -3
  77. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  78. package/src/quant-arena/quant-loop.mts +88 -61
  79. package/src/research-shot.ts +21 -11
  80. package/src/resolve-client.ts +6 -8
  81. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  82. package/src/router-executor.ts +1 -1
  83. package/src/router-turn.ts +135 -0
  84. package/src/run-benchmarks.ts +7 -2
  85. package/src/sandbox-run.ts +23 -41
  86. package/src/search-bench/bridge.ts +66 -8
  87. package/src/search-bench/parametric-check.mts +19 -4
  88. package/src/search-bench/profiles.ts +24 -9
  89. package/src/search-bench/run.mts +3 -4
  90. package/src/smoke-structural-rollout.mts +21 -5
  91. package/src/supervisor-arena.mts +28 -20
  92. package/src/swe-arena/arms.ts +73 -15
  93. package/src/swe-arena/capacity.ts +44 -41
  94. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  95. package/src/swe-arena/execution.test.mts +12 -12
  96. package/src/swe-arena/gepa-seat.mts +12 -5
  97. package/src/swe-arena/gepa-seat.test.mts +45 -33
  98. package/src/swe-arena/outer-loop.mts +94 -70
  99. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  100. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  101. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  102. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  103. package/src/swe-arena/proposer-fanout.mts +52 -99
  104. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  105. package/src/swe-arena/proposer-provenance.mts +1 -1
  106. package/src/swe-code-improve.mts +30 -52
  107. package/src/swe-emit-patch.mts +26 -5
  108. package/src/swe-improve.mts +27 -8
  109. package/src/swe-jail.test.ts +128 -0
  110. package/src/swe-jail.ts +106 -22
  111. package/src/swe-local-proof.mts +27 -5
  112. package/src/swe-repro-calibrate.mts +5 -0
  113. package/src/swe-self-improve.mts +45 -10
  114. package/src/swe-stream.mts +68 -6
  115. package/src/swe-structural.mts +46 -7
  116. package/src/tb-container-executor.mts +38 -6
  117. package/src/tb-container-executor.test.mts +11 -3
  118. package/src/tb-supervisor-sidecar.mts +11 -27
  119. package/src/terminal-compare.ts +38 -18
  120. package/src/trata-gate.mts +26 -26
  121. package/src/trata-gepa.mts +25 -16
  122. package/src/trata-hedge-solve.mts +80 -0
  123. package/src/worker-blender.ts +21 -11
  124. package/src/worker-browser.ts +18 -5
  125. package/src/worker-build123d.ts +17 -8
  126. package/src/worker-cad.ts +31 -12
  127. package/src/worker.ts +1 -1
  128. package/tb_agents/opencode_router_agent.py +84 -42
  129. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  130. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  131. package/scripts/trata-hedge/solve.py +0 -83
@@ -6,13 +6,11 @@
6
6
  * router 503-stormed. Rule encoded here: gate every arm on the endpoint that
7
7
  * arm actually calls; supervisor arms MUST include the router-path probe.
8
8
  *
9
- * Secrets discipline: probes spawn `dotenvx run -- bash -c 'curl …'` from
10
- * the secrets dir; the API key is referenced by NAME inside the child shell
11
- * (single-quoted script, so it is never expanded — let alone logged — in this
12
- * process).
9
+ * The containing experiment is launched through dotenvx, so probes read the already-scoped key
10
+ * from this process and enter Runtime through one exact AgentProfile.
13
11
  */
14
12
 
15
- import { run } from './proc'
13
+ import { runBenchRouterTurn } from '../router-turn'
16
14
  import type { SecretsEnv } from './arms'
17
15
 
18
16
  export type CapacityProbe = (signal?: AbortSignal) => Promise<boolean>
@@ -104,10 +102,10 @@ export async function waitForCapacity(gate: EndpointCapacityGate, signal?: Abort
104
102
 
105
103
  export interface HttpProbeSpec {
106
104
  url: string
105
+ provider: string
107
106
  /** NAME of the env var holding the bearer key (resolved in the dotenvx child). */
108
107
  apiKeyEnv: string
109
108
  model: string
110
- secrets: SecretsEnv
111
109
  /** curl --max-time, seconds. Default 40 (probe-capacity.sh). */
112
110
  maxTimeS?: number
113
111
  /**
@@ -117,55 +115,54 @@ export interface HttpProbeSpec {
117
115
  maxTokens?: number
118
116
  }
119
117
 
120
- export const ZAI_CODING_ENDPOINT = 'https://api.z.ai/api/coding/paas/v4/chat/completions'
121
- export const ROUTER_ENDPOINT = 'https://router.tangle.tools/v1/chat/completions'
122
-
123
- /** Build the probe request body (probe-body.json semantics). */
124
- export function probeBody(model: string, maxTokens: number): string {
125
- return JSON.stringify({
126
- model,
127
- messages: [{ role: 'user', content: 'Reply with the single word OK.' }],
128
- max_tokens: maxTokens,
129
- temperature: 0,
130
- })
131
- }
118
+ export const ZAI_CODING_ENDPOINT = 'https://api.z.ai/api/coding/paas/v4'
119
+ export const ROUTER_ENDPOINT = 'https://router.tangle.tools/v1'
132
120
 
133
121
  /**
134
- * Generic chat-completions probe: true iff the endpoint returns HTTP 200
135
- * within the time budget. The key stays inside the child shell.
122
+ * Generic chat-completions probe through Runtime: true only when the selected model emits `OK`
123
+ * within the time budget.
136
124
  */
137
125
  export function httpCapacityProbe(spec: HttpProbeSpec): CapacityProbe {
138
126
  if (!/^[A-Z_][A-Z0-9_]*$/.test(spec.apiKeyEnv)) {
139
127
  throw new Error(`invalid apiKeyEnv name: ${spec.apiKeyEnv}`)
140
128
  }
141
- const body = probeBody(spec.model, spec.maxTokens ?? 8000)
142
129
  const maxTime = spec.maxTimeS ?? 40
143
130
  return async (signal?: AbortSignal) => {
144
131
  signal?.throwIfAborted()
145
- // Body via stdin (--data @-) so the payload never sits on a command line.
146
- // The HTTP code is marker-anchored because dotenvx writes its injection
147
- // banner to the same stdout stream.
148
- const script =
149
- `curl -sS -o /dev/null -w "HTTP_CODE=%{http_code}" --max-time ${maxTime} ` +
150
- `-X POST "$PROBE_URL" ` +
151
- `-H "Authorization: Bearer $${spec.apiKeyEnv}" -H "Content-Type: application/json" ` +
152
- `--data @-`
153
- const argv = ['run', ...spec.secrets.envFiles.flatMap((f) => ['-f', f]), '--', 'bash', '-c', script]
154
- const res = await run('dotenvx', argv, {
155
- cwd: spec.secrets.secretsDir,
156
- timeoutMs: (maxTime + 20) * 1000,
157
- stdin: body,
158
- env: { ...process.env, PROBE_URL: spec.url },
159
- signal,
160
- })
161
- signal?.throwIfAborted()
162
- return /HTTP_CODE=200\s*$/.test(res.stdout)
132
+ const routerKey = process.env[spec.apiKeyEnv]
133
+ if (!routerKey) throw new Error(`${spec.apiKeyEnv} is required; launch through dotenvx`)
134
+ const turn = await runBenchRouterTurn(
135
+ {
136
+ routerBaseUrl: spec.url.replace(/\/chat\/completions\/?$/u, ''),
137
+ routerKey,
138
+ profile: {
139
+ name: `capacity-${spec.model}`,
140
+ harness: 'cli-base',
141
+ model: {
142
+ provider: spec.provider,
143
+ default: spec.model,
144
+ metadata: { temperature: 0, maxTokens: spec.maxTokens ?? 8000 },
145
+ },
146
+ prompt: { systemPrompt: 'Reply with the single word OK.' },
147
+ },
148
+ timeoutMs: maxTime * 1000,
149
+ signal,
150
+ },
151
+ 'Capacity probe.',
152
+ )
153
+ return turn.finalText.trim() === 'OK'
163
154
  }
164
155
  }
165
156
 
166
157
  /** probe-capacity.sh's z.ai coding-plan probe (the WORKER path). */
167
158
  export function zaiCodingProbe(secrets: SecretsEnv, model = 'glm-5.2'): CapacityProbe {
168
- return httpCapacityProbe({ url: ZAI_CODING_ENDPOINT, apiKeyEnv: 'ZAI_API_KEY', model, secrets })
159
+ void secrets
160
+ return httpCapacityProbe({
161
+ url: ZAI_CODING_ENDPOINT,
162
+ apiKeyEnv: 'ZAI_API_KEY',
163
+ provider: 'zai',
164
+ model,
165
+ })
169
166
  }
170
167
 
171
168
  /**
@@ -173,7 +170,13 @@ export function zaiCodingProbe(secrets: SecretsEnv, model = 'glm-5.2'): Capacity
173
170
  * Supervisor arms must gate on this; probing only z.ai is the proven blind spot.
174
171
  */
175
172
  export function routerProbe(secrets: SecretsEnv, model = 'glm-5.2'): CapacityProbe {
176
- return httpCapacityProbe({ url: ROUTER_ENDPOINT, apiKeyEnv: 'TANGLE_API_KEY', model, secrets })
173
+ void secrets
174
+ return httpCapacityProbe({
175
+ url: ROUTER_ENDPOINT,
176
+ apiKeyEnv: 'TANGLE_API_KEY',
177
+ provider: 'tangle-router',
178
+ model,
179
+ })
177
180
  }
178
181
 
179
182
  /** The gates an arm must pass, by kind: solo → worker path; supervisor → BOTH paths. */
@@ -12,9 +12,8 @@
12
12
  * gpt-5.5 / opus-4.8 slot in by editing the analyst spec list, never by a
13
13
  * hardcoded requirement on an unrouted model.
14
14
  * - analysts are blind: same bundle, no cross-talk, independent calls.
15
- * - secrets discipline matches capacity.ts: the API key is referenced by NAME
16
- * inside a dotenvx child shell; the response body lands in a file (dotenvx
17
- * writes its banner to stdout, so stdout is only trusted for the marker).
15
+ * - the containing run is launched through dotenvx; every analyst enters Runtime through one
16
+ * exact AgentProfile and its event record is retained with the response.
18
17
  */
19
18
 
20
19
  import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
@@ -22,7 +21,7 @@ import { join } from 'node:path'
22
21
  import { type AnalystFinding, makeFinding } from '@tangle-network/agent-eval'
23
22
  import { findSupervisorRunDir, type SecretsEnv } from './arms'
24
23
  import { ROUTER_ENDPOINT, sleepWithSignal } from './capacity'
25
- import { run } from './proc'
24
+ import { runBenchRouterTurn } from '../router-turn'
26
25
 
27
26
  // ---------------------------------------------------------------------------
28
27
  // Analyst specs.
@@ -32,6 +31,8 @@ export interface AnalystSpec {
32
31
  /** Stable label, e.g. 'glm-5.2#1'. Used in fusion attribution + filenames. */
33
32
  id: string
34
33
  model: string
34
+ /** Provider identity stamped into the exact profile. Default: tangle-router. */
35
+ provider?: string
35
36
  /** Chat-completions endpoint. Default: the Tangle router. */
36
37
  url?: string
37
38
  /** NAME of the env var holding the bearer key (resolved in the dotenvx child). */
@@ -247,8 +248,7 @@ export function parseAnalystFindings(text: string): AnalystRawFinding[] {
247
248
  })
248
249
  }
249
250
 
250
- /** POST one blind analyst call through the router via dotenvx (key stays in
251
- * the child; response body lands in `outFile`, never on shared stdout).
251
+ /** Run one blind analyst through Runtime's exact-profile Router boundary.
252
252
  * ONE retry on transport failure (5xx/524/timeout) — an edge flake was
253
253
  * observed live in the calibration smoke; a parse failure is NOT retried
254
254
  * (same prompt, same model ⇒ same bad shape). */
@@ -265,60 +265,63 @@ export async function runAnalyst(
265
265
  return { analystId: spec.id, model: spec.model, ok: false, findings: [], error: `invalid apiKeyEnv name: ${apiKeyEnv}` }
266
266
  }
267
267
  const url = spec.url ?? ROUTER_ENDPOINT
268
- const body = JSON.stringify({
269
- model: spec.model,
270
- messages: [{ role: 'user', content: analystPrompt(bundle) }],
271
- temperature: spec.temperature ?? 0,
272
- max_tokens: spec.maxTokens ?? 16_000,
273
- })
268
+ const routerKey = process.env[apiKeyEnv]
269
+ if (!routerKey) {
270
+ return {
271
+ analystId: spec.id,
272
+ model: spec.model,
273
+ ok: false,
274
+ findings: [],
275
+ error: `${apiKeyEnv} is required; launch through dotenvx`,
276
+ }
277
+ }
278
+ void secrets
274
279
  opts.signal?.throwIfAborted()
275
280
  await mkdir(scratchDir, { recursive: true })
276
281
  const outFile = join(scratchDir, `analyst-${spec.id.replace(/[^a-zA-Z0-9._-]/g, '_')}.response.json`)
277
282
  const timeoutMs = opts.timeoutMs ?? 600_000
278
- // Body via stdin (--data @-) so the payload never sits on a command line;
279
- // the HTTP code is marker-anchored on stdout (dotenvx banners share stdout).
280
- const script =
281
- `curl -sS -o "$DIAG_OUT" -w "HTTP_CODE=%{http_code}" --max-time ${Math.ceil(timeoutMs / 1000) - 20} ` +
282
- `-X POST "$DIAG_URL" -H "Authorization: Bearer $${apiKeyEnv}" -H "Content-Type: application/json" --data @-`
283
- const argv = ['run', ...secrets.envFiles.flatMap((f) => ['-f', f]), '--', 'bash', '-c', script]
284
283
  const attempts = 1 + (opts.retries ?? 1)
285
284
  let transportError = ''
286
- let transported = false
287
- for (let attempt = 0; attempt < attempts && !transported; attempt++) {
285
+ let content = ''
286
+ let tokens: AnalystReport['tokens']
287
+ for (let attempt = 0; attempt < attempts && content.length === 0; attempt++) {
288
288
  opts.signal?.throwIfAborted()
289
289
  if (attempt > 0) await sleepWithSignal(opts.retryDelayMs ?? 5_000, opts.signal)
290
- const res = await run('dotenvx', argv, {
291
- cwd: secrets.secretsDir,
292
- timeoutMs,
293
- stdin: body,
294
- env: { ...process.env, DIAG_URL: url, DIAG_OUT: outFile },
295
- signal: opts.signal,
296
- })
297
- opts.signal?.throwIfAborted()
298
- const codeMatch = res.stdout.match(/HTTP_CODE=(\d{3})\s*$/)
299
- if (codeMatch && codeMatch[1] === '200') {
300
- transported = true
301
- } else {
302
- transportError = `router call failed (http=${codeMatch?.[1] ?? 'none'}, rc=${res.code}${res.timedOut ? ', timeout' : ''}, attempt=${attempt + 1}/${attempts})`
290
+ try {
291
+ const turn = await runBenchRouterTurn(
292
+ {
293
+ routerBaseUrl: url.replace(/\/chat\/completions\/?$/u, ''),
294
+ routerKey,
295
+ profile: {
296
+ name: `diagnosis-${spec.id}`,
297
+ harness: 'cli-base',
298
+ model: {
299
+ provider: spec.provider ?? 'tangle-router',
300
+ default: spec.model,
301
+ metadata: {
302
+ temperature: spec.temperature ?? 0,
303
+ maxTokens: spec.maxTokens ?? 16_000,
304
+ },
305
+ },
306
+ prompt: { systemPrompt: 'Diagnose the supplied run evidence as an independent analyst.' },
307
+ },
308
+ timeoutMs,
309
+ signal: opts.signal,
310
+ },
311
+ analystPrompt(bundle),
312
+ )
313
+ content = turn.finalText
314
+ if (turn.usage.tokensKnown !== false) {
315
+ tokens = { input: turn.usage.input, output: turn.usage.output }
316
+ }
317
+ await writeFile(outFile, JSON.stringify(turn, null, 2))
318
+ } catch (cause) {
319
+ transportError = `router call failed (${(cause as Error).message}, attempt=${attempt + 1}/${attempts})`
303
320
  }
304
321
  }
305
- if (!transported) {
322
+ if (content.length === 0 && transportError) {
306
323
  return { analystId: spec.id, model: spec.model, ok: false, findings: [], error: transportError }
307
324
  }
308
- let content = ''
309
- let tokens: AnalystReport['tokens']
310
- try {
311
- const parsed = JSON.parse(await readFile(outFile, 'utf8')) as {
312
- choices?: Array<{ message?: { content?: string } }>
313
- usage?: { prompt_tokens?: number; completion_tokens?: number }
314
- }
315
- content = parsed.choices?.[0]?.message?.content ?? ''
316
- if (parsed.usage) {
317
- tokens = { input: parsed.usage.prompt_tokens ?? 0, output: parsed.usage.completion_tokens ?? 0 }
318
- }
319
- } catch (cause) {
320
- return { analystId: spec.id, model: spec.model, ok: false, findings: [], error: `unparseable response body: ${(cause as Error).message}` }
321
- }
322
325
  if (content.trim().length === 0) {
323
326
  return { analystId: spec.id, model: spec.model, ok: false, findings: [], error: 'empty content (max_tokens starvation?)', tokens }
324
327
  }
@@ -31,7 +31,12 @@ import {
31
31
  type SupervisorArmResult,
32
32
  } from './arms.ts'
33
33
  import { containerName, materializeWorkspace } from './materialize.ts'
34
- import { gatesForArmKind, probeBody, probeWindow, waitForCapacity, httpCapacityProbe } from './capacity.ts'
34
+ import {
35
+ gatesForArmKind,
36
+ httpCapacityProbe,
37
+ probeWindow,
38
+ waitForCapacity,
39
+ } from './capacity.ts'
35
40
  import { run, runOk } from './proc.ts'
36
41
  import {
37
42
  createSerializedJudge,
@@ -1065,15 +1070,6 @@ describe('capacity gate', () => {
1065
1070
  expect(await waitForCapacity(gate)).toBe(false)
1066
1071
  })
1067
1072
 
1068
- it('probe body defaults to max_tokens 8000 (glm-5.2 starves below it)', () => {
1069
- expect(JSON.parse(probeBody('glm-5.2', 8000))).toEqual({
1070
- model: 'glm-5.2',
1071
- messages: [{ role: 'user', content: 'Reply with the single word OK.' }],
1072
- max_tokens: 8000,
1073
- temperature: 0,
1074
- })
1075
- })
1076
-
1077
1073
  it('supervisor arms gate on BOTH worker and router paths; solo on worker only', () => {
1078
1074
  const secrets = { secretsDir: '/dev/null', envFiles: ['agent-state.env'] }
1079
1075
  expect(gatesForArmKind('solo', secrets).map((g) => g.name)).toEqual(['z.ai-coding'])
@@ -1081,9 +1077,13 @@ describe('capacity gate', () => {
1081
1077
  })
1082
1078
 
1083
1079
  it('rejects a non-env-shaped key name (no value smuggling)', () => {
1084
- const secrets = { secretsDir: '/dev/null', envFiles: [] }
1085
1080
  expect(() =>
1086
- httpCapacityProbe({ url: 'https://x', apiKeyEnv: 'k; cat /etc/passwd', model: 'm', secrets }),
1081
+ httpCapacityProbe({
1082
+ url: 'https://x',
1083
+ apiKeyEnv: 'k; cat /etc/passwd',
1084
+ provider: 'test',
1085
+ model: 'm',
1086
+ }),
1087
1087
  ).toThrow(/apiKeyEnv/)
1088
1088
  })
1089
1089
  })
@@ -41,6 +41,7 @@ import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promise
41
41
  import { dirname, join } from 'node:path'
42
42
  import {
43
43
  type DispatchContext,
44
+ type ComparisonCost,
44
45
  createRunCostLedger,
45
46
  fsCampaignStorage,
46
47
  type GepaOptimizationMethodConfig,
@@ -233,7 +234,7 @@ export function innerSmokeJudge(): JudgeConfig<SmokeVerdict, GepaSeatScenario> {
233
234
  // ---------------------------------------------------------------------------
234
235
 
235
236
  export const GEPA_PYTHON_INSTALL_HINT =
236
- 'install `agent-eval-rpc==0.131.0`, then install ' +
237
+ 'install `agent-eval-rpc==0.144.6`, then install ' +
237
238
  '`gepa[full] @ git+https://github.com/gepa-ai/gepa.git@f919db0a622e2e9f9204779b81fe00cc1b2d808f`'
238
239
 
239
240
  export type GepaMethodFactory = (
@@ -326,6 +327,7 @@ export interface GepaSeatInnerRun {
326
327
  tokenUsage: OptimizationTokenUsage
327
328
  artifactDir: string
328
329
  totalCostUsd: number
330
+ costProvenance: ComparisonCost['costProvenance']
329
331
  accountingComplete: boolean
330
332
  incompleteReasons: string[]
331
333
  durationMs: number
@@ -532,10 +534,7 @@ function completeProvenance(
532
534
  }
533
535
  }
534
536
 
535
- function assertCompleteCost(
536
- cost: { totalCostUsd: number; accountingComplete: boolean; incompleteReasons: string[] },
537
- seatName: string,
538
- ): void {
537
+ function assertCompleteCost(cost: ComparisonCost, seatName: string): void {
539
538
  if (!Number.isFinite(cost.totalCostUsd) || cost.totalCostUsd < 0) {
540
539
  throw new Error(`gepa seat '${seatName}': optimizer returned invalid total cost`)
541
540
  }
@@ -550,6 +549,13 @@ function assertCompleteCost(
550
549
  if (cost.accountingComplete !== (cost.incompleteReasons.length === 0)) {
551
550
  throw new Error(`gepa seat '${seatName}': optimizer returned inconsistent cost accounting`)
552
551
  }
552
+ if (cost.costProvenance.kind === 'uncaptured') {
553
+ if (cost.accountingComplete) {
554
+ throw new Error(`gepa seat '${seatName}': complete cost cannot have uncaptured provenance`)
555
+ }
556
+ } else if (cost.costProvenance.usd !== cost.totalCostUsd) {
557
+ throw new Error(`gepa seat '${seatName}': cost provenance does not match total cost`)
558
+ }
553
559
  if (!cost.accountingComplete) {
554
560
  throw new Error(
555
561
  `gepa seat '${seatName}': cost accounting is incomplete: ${cost.incompleteReasons.join('; ') || 'no reason provided'}`,
@@ -823,6 +829,7 @@ export function gepaSeatAuthor(config: OuterLoopConfig, deps: GepaSeatDeps): Aut
823
829
  : null,
824
830
  ...completeProvenance(result.provenance, spec.name),
825
831
  totalCostUsd: result.cost.totalCostUsd,
832
+ costProvenance: result.cost.costProvenance,
826
833
  accountingComplete: result.cost.accountingComplete,
827
834
  incompleteReasons: [...result.cost.incompleteReasons],
828
835
  durationMs: Date.now() - started,
@@ -34,6 +34,7 @@ import {
34
34
  import { defaultRound4Config, type OuterLoopConfig } from './outer-loop.mts'
35
35
  import { fanOutLoopsGenerator, type ProposerSpec, type SmokeRunner, type SmokeVerdict } from './proposer-fanout.mts'
36
36
  import { captureProposerProvenance } from './proposer-provenance.mts'
37
+ import { officialOptimizerModel } from '../official-optimizer-config.mts'
37
38
  import { runOk } from './proc.ts'
38
39
 
39
40
  const SURFACE = 'extensions/pi/prompts/worker-coding-system.md'
@@ -370,6 +371,7 @@ describe('recordGepaSeatInnerRun', () => {
370
371
  tokenUsage: result.tokenUsage,
371
372
  artifactDir: result.artifactDir,
372
373
  totalCostUsd: 0.25,
374
+ costProvenance: { kind: 'observed', usd: 0.25 },
373
375
  accountingComplete: true,
374
376
  incompleteReasons: [],
375
377
  durationMs: 5,
@@ -426,24 +428,26 @@ describe('recordGepaSeatInnerRun', () => {
426
428
  // ---------------------------------------------------------------------------
427
429
 
428
430
  const fakeCtx = {} as unknown as DispatchContext
429
- const testOptimizer = {
431
+ const testOptimizerEnv: NodeJS.ProcessEnv = {
432
+ TEST_OPTIMIZER_INPUT_USD_PER_MILLION: '1',
433
+ TEST_OPTIMIZER_CACHED_INPUT_USD_PER_MILLION: '0.1',
434
+ TEST_OPTIMIZER_CACHE_WRITE_USD_PER_MILLION: '1.25',
435
+ TEST_OPTIMIZER_OUTPUT_USD_PER_MILLION: '5',
436
+ TEST_OPTIMIZER_MAX_REQUESTS: '10',
437
+ TEST_OPTIMIZER_MAX_REQUEST_BYTES: '100000',
438
+ TEST_OPTIMIZER_MAX_RESPONSE_BYTES: '100000',
439
+ }
440
+
441
+ const testOptimizer = officialOptimizerModel({
442
+ env: testOptimizerEnv,
443
+ envPrefix: 'TEST_OPTIMIZER',
430
444
  model: 'optimizer-model',
431
445
  baseUrl: 'http://127.0.0.1:1/v1',
432
446
  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
+ maxCostUsd: 1,
448
+ maxOutputTokensPerRequest: 2_000,
449
+ callRef: 'test:optimizer-model',
450
+ })
447
451
 
448
452
  const fullProvenance = (
449
453
  runId = 'gepa-run',
@@ -504,7 +508,12 @@ const fakeGepaFactory =
504
508
  }
505
509
  return {
506
510
  winnerSurface: best.surface,
507
- cost: { totalCostUsd: 0.125, accountingComplete: true, incompleteReasons: [] },
511
+ cost: {
512
+ totalCostUsd: 0.125,
513
+ costProvenance: { kind: 'observed', usd: 0.125 },
514
+ accountingComplete: true,
515
+ incompleteReasons: [],
516
+ },
508
517
  durationMs: 1,
509
518
  provenance: fullProvenance(),
510
519
  }
@@ -737,7 +746,12 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
737
746
  ])
738
747
  return {
739
748
  winnerSurface: candidateA,
740
- cost: { totalCostUsd: 0, accountingComplete: true, incompleteReasons: [] },
749
+ cost: {
750
+ totalCostUsd: 0,
751
+ costProvenance: { kind: 'observed', usd: 0 },
752
+ accountingComplete: true,
753
+ incompleteReasons: [],
754
+ },
741
755
  durationMs: 1,
742
756
  provenance: fullProvenance('parallel-gepa', { evaluationCount: 2 }),
743
757
  }
@@ -823,6 +837,7 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
823
837
  winnerSurface: WINNER,
824
838
  cost: {
825
839
  totalCostUsd: 0.25,
840
+ costProvenance: { kind: 'uncaptured', usd: null },
826
841
  accountingComplete: false,
827
842
  incompleteReasons: ['optimizer model receipt missing'],
828
843
  },
@@ -873,7 +888,12 @@ describe('fanOutLoopsGenerator with the gepa seat', () => {
873
888
  if (rejected) throw rejected.reason
874
889
  return {
875
890
  winnerSurface: SEED,
876
- cost: { totalCostUsd: 0, accountingComplete: false, incompleteReasons: [] },
891
+ cost: {
892
+ totalCostUsd: 0,
893
+ costProvenance: { kind: 'uncaptured', usd: null },
894
+ accountingComplete: false,
895
+ incompleteReasons: [],
896
+ },
877
897
  durationMs: 1,
878
898
  provenance: fullProvenance('runaway'),
879
899
  }
@@ -1009,24 +1029,16 @@ describe('integration: real adapter roundtrip', () => {
1009
1029
  },
1010
1030
  smokeInstanceId: 'astropy__astropy-13033',
1011
1031
  scoreSplit: null,
1012
- gepaOptimizer: {
1032
+ gepaOptimizer: officialOptimizerModel({
1033
+ env: testOptimizerEnv,
1034
+ envPrefix: 'TEST_OPTIMIZER',
1013
1035
  model: 'test-optimizer',
1014
1036
  baseUrl: `http://127.0.0.1:${address.port}/v1`,
1015
1037
  apiKey: 'local-test-key',
1016
- budget: {
1017
- maxCostUsd: 1,
1018
- maxRequests: 10,
1019
- maxRequestBytes: 100_000,
1020
- maxResponseBytes: 100_000,
1021
- maxOutputTokensPerRequest: 2_000,
1022
- pricing: {
1023
- inputUsdPerMillion: 1,
1024
- cachedInputUsdPerMillion: 0.1,
1025
- cacheWriteUsdPerMillion: 1.25,
1026
- outputUsdPerMillion: 5,
1027
- },
1028
- },
1029
- },
1038
+ maxCostUsd: 1,
1039
+ maxOutputTokensPerRequest: 2_000,
1040
+ callRef: 'test:integration-optimizer',
1041
+ }),
1030
1042
  },
1031
1043
  )
1032
1044
  const result = await gen.generate({