@tangle-network/agent-bench 0.7.0 → 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 +12 -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
@@ -14,6 +14,7 @@
14
14
  import { readFile } from 'node:fs/promises'
15
15
  import type { BenchScore, BenchTask, BenchmarkAdapter, LoadOptions } from './types'
16
16
  import { renderBpy } from '../worker-blender'
17
+ import { runBenchRouterTurn } from '../router-turn'
17
18
 
18
19
  interface CadBenchMeta {
19
20
  name: string
@@ -48,14 +49,26 @@ async function judgeCriteria(
48
49
  `Return ONLY a JSON array of exactly ${criteria.length} booleans (true=satisfied, false=not), in order, no prose.\n\nCRITERIA:\n${numbered}\n\nSCRIPT:\n\`\`\`python\n${script.slice(0, 6000)}\n\`\`\``
49
50
  const content: unknown[] = [{ type: 'text', text }]
50
51
  for (const url of renders) content.push({ type: 'image_url', image_url: { url } })
51
- const res = await fetch(`${base}/chat/completions`, {
52
- method: 'POST',
53
- headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
54
- body: JSON.stringify({ model, max_tokens: 1500, temperature: 0, messages: [{ role: 'user', content }] }),
55
- })
56
- if (!res.ok) throw new Error(`judge ${model} ${res.status}: ${(await res.text()).slice(0, 200)}`)
57
- const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
58
- const raw = data.choices?.[0]?.message?.content ?? ''
52
+ const turn = await runBenchRouterTurn(
53
+ {
54
+ routerBaseUrl: base,
55
+ routerKey: key,
56
+ profile: {
57
+ name: 'cadbench-vision-judge',
58
+ harness: 'cli-base',
59
+ model: {
60
+ provider: 'tangle-router',
61
+ default: model,
62
+ metadata: {
63
+ temperature: 0,
64
+ maxTokens: Number(process.env.JUDGE_MAX_TOKENS ?? 1500),
65
+ },
66
+ },
67
+ },
68
+ },
69
+ { messages: [{ role: 'user', content }] },
70
+ )
71
+ const raw = turn.finalText
59
72
  const m = /\[\s*(?:true|false)[\s\S]*?\]/i.exec(raw)
60
73
  if (!m) return { passed: criteria.map(() => false), note: `judge returned no parseable verdict: ${raw.slice(0, 80)}` }
61
74
  let arr: unknown
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { readFile, stat } from 'node:fs/promises'
11
11
  import { join } from 'node:path'
12
+ import { runBenchRouterTurn } from '../router-turn'
12
13
  import { benchRoot } from './_harness'
13
14
  import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
14
15
 
@@ -180,22 +181,25 @@ function parseJudgeScore(content: string): { score: number; raw: unknown } {
180
181
  async function runOfficialJudge(meta: FinResearchMeta, response: string): Promise<BenchScore> {
181
182
  if (!meta.judgeSystemPrompt) throw new Error(`FinResearchBench task ${meta.id} missing judge_system_prompt`)
182
183
  const router = routerConfig()
183
- const res = await fetch(`${router.baseUrl}/chat/completions`, {
184
- method: 'POST',
185
- headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
186
- body: JSON.stringify({
187
- model: router.model,
188
- temperature: 0,
189
- messages: [
190
- { role: 'system', content: meta.judgeSystemPrompt },
191
- { role: 'user', content: fillTemplate(meta, response) },
192
- ],
193
- }),
194
- })
195
- if (!res.ok) throw new Error(`FinResearchBench judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
196
- const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
197
- const content = body.choices?.[0]?.message?.content
198
- if (typeof content !== 'string') throw new Error(`FinResearchBench judge returned no message content: ${JSON.stringify(body).slice(0, 300)}`)
184
+ const turn = await runBenchRouterTurn(
185
+ {
186
+ routerBaseUrl: router.baseUrl,
187
+ routerKey: router.key,
188
+ profile: {
189
+ name: 'finresearchbench-judge',
190
+ harness: 'cli-base',
191
+ model: {
192
+ provider: 'tangle-router',
193
+ default: router.model,
194
+ metadata: { temperature: 0 },
195
+ },
196
+ prompt: { systemPrompt: meta.judgeSystemPrompt },
197
+ },
198
+ },
199
+ fillTemplate(meta, response),
200
+ )
201
+ const content = turn.finalText
202
+ if (!content) throw new Error('FinResearchBench judge returned no message content')
199
203
  const { score, raw } = parseJudgeScore(content)
200
204
  return {
201
205
  resolved: score >= Number(process.env.FINRESEARCHBENCH_PASS_THRESHOLD ?? 0.8),
@@ -32,6 +32,7 @@
32
32
  import { readFile } from 'node:fs/promises'
33
33
  import { join } from 'node:path'
34
34
  import { fileURLToPath } from 'node:url'
35
+ import { runBenchRouterTurn } from '../router-turn'
35
36
  import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
36
37
 
37
38
  const BENCH_ROOT = fileURLToPath(new URL('../..', import.meta.url))
@@ -206,26 +207,25 @@ function parseJudgeOutput(content: string): { resolved: boolean; score: number;
206
207
 
207
208
  /** Run the record's own judge via the router. Fail loud on transport/parse errors. */
208
209
  async function runRecordJudge(meta: FinSearchMeta, response: string, router: JudgeRouter): Promise<BenchScore> {
209
- const res = await fetch(`${router.baseUrl}/chat/completions`, {
210
- method: 'POST',
211
- headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
212
- body: JSON.stringify({
213
- model: router.model,
214
- temperature: 0,
215
- messages: [
216
- { role: 'system', content: meta.judgeSystemPrompt },
217
- { role: 'user', content: fillJudgePrompt(meta, response) },
218
- ],
219
- }),
220
- })
221
- if (!res.ok) {
222
- throw new Error(`FinSearchComp judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
223
- }
224
- const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
225
- const content = body.choices?.[0]?.message?.content
226
- if (typeof content !== 'string') {
227
- throw new Error(`FinSearchComp judge returned no message content: ${JSON.stringify(body).slice(0, 300)}`)
228
- }
210
+ const turn = await runBenchRouterTurn(
211
+ {
212
+ routerBaseUrl: router.baseUrl,
213
+ routerKey: router.key,
214
+ profile: {
215
+ name: 'finsearchcomp-judge',
216
+ harness: 'cli-base',
217
+ model: {
218
+ provider: 'tangle-router',
219
+ default: router.model,
220
+ metadata: { temperature: 0 },
221
+ },
222
+ prompt: { systemPrompt: meta.judgeSystemPrompt },
223
+ },
224
+ },
225
+ fillJudgePrompt(meta, response),
226
+ )
227
+ const content = turn.finalText
228
+ if (!content) throw new Error('FinSearchComp judge returned no message content')
229
229
  const { resolved, score, raw } = parseJudgeOutput(content)
230
230
  return {
231
231
  resolved,
@@ -25,6 +25,7 @@ import { readFile } from 'node:fs/promises'
25
25
  import { join } from 'node:path'
26
26
  import { fileURLToPath } from 'node:url'
27
27
  import { promisify } from 'node:util'
28
+ import { runBenchRouterTurn } from '../router-turn'
28
29
  import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
29
30
 
30
31
  const execFileAsync = promisify(execFile)
@@ -322,24 +323,24 @@ async function tier2Judge(
322
323
  candidate: string,
323
324
  router: JudgeRouter,
324
325
  ): Promise<boolean> {
325
- const res = await fetch(`${router.baseUrl}/chat/completions`, {
326
- method: 'POST',
327
- headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
328
- body: JSON.stringify({
329
- model: router.model,
330
- temperature: 0,
331
- seed: 0,
332
- messages: [{ role: 'user', content: JUDGE_PROMPT(question, gold, candidate) }],
333
- }),
334
- })
335
- if (!res.ok) {
336
- throw new Error(`FRAMES Tier-2 judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
337
- }
338
- const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
339
- const content = body.choices?.[0]?.message?.content
340
- if (typeof content !== 'string') {
341
- throw new Error(`FRAMES Tier-2 judge returned no message content: ${JSON.stringify(body).slice(0, 300)}`)
342
- }
326
+ const turn = await runBenchRouterTurn(
327
+ {
328
+ routerBaseUrl: router.baseUrl,
329
+ routerKey: router.key,
330
+ profile: {
331
+ name: 'frames-equivalence-judge',
332
+ harness: 'cli-base',
333
+ model: {
334
+ provider: 'tangle-router',
335
+ default: router.model,
336
+ metadata: { temperature: 0, seed: 0 },
337
+ },
338
+ },
339
+ },
340
+ JUDGE_PROMPT(question, gold, candidate),
341
+ )
342
+ const content = turn.finalText
343
+ if (!content) throw new Error('FRAMES Tier-2 judge returned no message content')
343
344
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/)
344
345
  const raw = (fenced ? fenced[1] : content)?.trim() ?? ''
345
346
  let parsed: { verdict?: unknown }
@@ -30,6 +30,7 @@ import { readFile } from 'node:fs/promises'
30
30
  import { join } from 'node:path'
31
31
  import { fileURLToPath } from 'node:url'
32
32
  import { promisify } from 'node:util'
33
+ import { runBenchRouterTurn } from '../router-turn'
33
34
  import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
34
35
 
35
36
  const execFileAsync = promisify(execFile)
@@ -195,23 +196,24 @@ async function gradeAnswer(
195
196
  predicted: string,
196
197
  router: GraderRouter,
197
198
  ): Promise<Grade> {
198
- const res = await fetch(`${router.baseUrl}/chat/completions`, {
199
- method: 'POST',
200
- headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
201
- body: JSON.stringify({
202
- model: router.model,
203
- temperature: 0,
204
- messages: [{ role: 'user', content: GRADER_PROMPT(question, gold, predicted) }],
205
- }),
206
- })
207
- if (!res.ok) {
208
- throw new Error(`SimpleQA grader HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
209
- }
210
- const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
211
- const content = body.choices?.[0]?.message?.content
212
- if (typeof content !== 'string') {
213
- throw new Error(`SimpleQA grader returned no message content: ${JSON.stringify(body).slice(0, 300)}`)
214
- }
199
+ const turn = await runBenchRouterTurn(
200
+ {
201
+ routerBaseUrl: router.baseUrl,
202
+ routerKey: router.key,
203
+ profile: {
204
+ name: 'simpleqa-grader',
205
+ harness: 'cli-base',
206
+ model: {
207
+ provider: 'tangle-router',
208
+ default: router.model,
209
+ metadata: { temperature: 0 },
210
+ },
211
+ },
212
+ },
213
+ GRADER_PROMPT(question, gold, predicted),
214
+ )
215
+ const content = turn.finalText
216
+ if (!content) throw new Error('SimpleQA grader returned no message content')
215
217
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/)
216
218
  const raw = (fenced ? fenced[1] : content)?.trim() ?? ''
217
219
  let parsed: { grade?: unknown }
@@ -26,6 +26,7 @@
26
26
 
27
27
  import { readdirSync, readFileSync, statSync } from 'node:fs'
28
28
  import { join } from 'node:path'
29
+ import { runBenchRouterTurn } from '../router-turn'
29
30
  import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
30
31
 
31
32
  const DEFAULT_BENCH_ROOT = '/tmp/trata-hedge-bench'
@@ -191,25 +192,32 @@ function parseJsonFallback(raw: string): unknown {
191
192
 
192
193
  async function callJudge(router: JudgeRouter, prompt: string, maxAttempts = 2): Promise<unknown> {
193
194
  for (let i = 0; i < maxAttempts; i++) {
194
- const res = await fetch(`${router.baseUrl}/chat/completions`, {
195
- method: 'POST',
196
- headers: { 'content-type': 'application/json', authorization: `Bearer ${router.key}` },
197
- body: JSON.stringify({
198
- model: router.model,
199
- temperature: 0,
200
- max_tokens: 16384,
201
- messages: [{ role: 'user', content: prompt }],
202
- }),
203
- })
204
- if (!res.ok) {
205
- if (i < maxAttempts - 1) continue
206
- throw new Error(`Trata judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`)
195
+ try {
196
+ const turn = await runBenchRouterTurn(
197
+ {
198
+ routerBaseUrl: router.baseUrl,
199
+ routerKey: router.key,
200
+ profile: {
201
+ name: 'trata-hedge-judge',
202
+ harness: 'cli-base',
203
+ model: {
204
+ provider: 'tangle-router',
205
+ default: router.model,
206
+ metadata: {
207
+ temperature: 0,
208
+ maxTokens: Number(process.env.JUDGE_MAX_TOKENS ?? 16384),
209
+ },
210
+ },
211
+ },
212
+ },
213
+ prompt,
214
+ )
215
+ const content = turn.finalText
216
+ const parsed = parseJsonFallback(content)
217
+ if (parsed !== null) return parsed
218
+ } catch (error) {
219
+ if (i + 1 === maxAttempts) throw error
207
220
  }
208
- const body = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
209
- const content = body.choices?.[0]?.message?.content
210
- if (typeof content !== 'string') continue
211
- const parsed = parseJsonFallback(content)
212
- if (parsed !== null) return parsed
213
221
  }
214
222
  return null
215
223
  }
@@ -3,7 +3,7 @@
3
3
  * browser-agent harness (./agent-adapter.ts).
4
4
  *
5
5
  * "More reviewers, the better": run a PANEL of UI reviewers over the same target
6
- * and union their findings — #114's runLoop ui-auditor, browser-agent-driver's
6
+ * and union their findings — #114's runAgentRounds ui-auditor, browser-agent-driver's
7
7
  * `bad design-audit`, a future reviewer — each a pluggable `UiReviewerAdapter`,
8
8
  * none of them owning the verdict.
9
9
  *
@@ -36,7 +36,12 @@ import { execFileSync } from 'node:child_process'
36
36
  import { existsSync, readFileSync } from 'node:fs'
37
37
  import { composeStrategies } from './directives'
38
38
  import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
39
- import { type RouterConfig, routerChatWithUsage } from '@tangle-network/agent-runtime/kernel'
39
+ import {
40
+ benchRouterProfile,
41
+ type BenchRouterTarget,
42
+ runBenchRouterTurn,
43
+ withBenchProfile,
44
+ } from './router-turn'
40
45
  import { selfConsistencySelect, verifierGroundedSelect } from './selector'
41
46
  import { type PairedLift, pairedLift, pool } from './stats.mts'
42
47
 
@@ -48,7 +53,7 @@ function must(name: string): string {
48
53
  return v
49
54
  }
50
55
 
51
- interface ChatMessage {
56
+ interface ChatMessage extends Readonly<Record<string, unknown>> {
52
57
  role: string
53
58
  content: string
54
59
  }
@@ -154,7 +159,7 @@ function parseJudge(reply: string, rubricCount: number): RubricVerdict {
154
159
 
155
160
  /** Grade one completion with the rubric judge. A judge API/parse failure is a real
156
161
  * zero (the response could not be validated) — surfaced, never masked. */
157
- async function judgeRubrics(cfg: RouterConfig, task: CtxTask, output: string): Promise<RubricVerdict> {
162
+ async function judgeRubrics(cfg: BenchRouterTarget, task: CtxTask, output: string): Promise<RubricVerdict> {
158
163
  if (!output.trim()) return { fraction: 0, allPass: false, graded: 0 }
159
164
  const rubricsText = task.rubrics.map((r, i) => `${i + 1}. ${r}`).join('\n')
160
165
  // Fault-isolate the judge: a transient router failure (after retries) or an
@@ -162,8 +167,18 @@ async function judgeRubrics(cfg: RouterConfig, task: CtxTask, output: string): P
162
167
  // NOT throw — one bad grade would otherwise crash the whole N×K×2 run. graded=0
163
168
  // marks it as judge-failed so it's distinguishable from a real 0/N rubric pass.
164
169
  try {
165
- const res = await routerChatWithUsage(cfg, [{ role: 'user', content: judgePrompt(rubricsText, output) }], { temperature: 0 })
166
- return parseJudge(typeof res.content === 'string' ? res.content : '', task.rubrics.length)
170
+ const res = await runBenchRouterTurn(
171
+ {
172
+ routerBaseUrl: cfg.routerBaseUrl,
173
+ routerKey: cfg.routerKey,
174
+ profile: withBenchProfile(cfg.profile, {
175
+ name: 'clbench-rubric-judge',
176
+ temperature: 0,
177
+ }),
178
+ },
179
+ judgePrompt(rubricsText, output),
180
+ )
181
+ return parseJudge(res.finalText, task.rubrics.length)
167
182
  } catch {
168
183
  return { fraction: 0, allPass: false, graded: 0 }
169
184
  }
@@ -190,8 +205,18 @@ async function main(): Promise<void> {
190
205
  if (!Number.isInteger(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
191
206
  if (!Number.isInteger(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
192
207
 
193
- const workerCfg: RouterConfig = { routerBaseUrl, routerKey, model }
194
- const judgeCfg: RouterConfig = { routerBaseUrl, routerKey, model: judgeModel }
208
+ const workerCfg: BenchRouterTarget = {
209
+ routerBaseUrl,
210
+ routerKey,
211
+ profile: benchRouterProfile('clbench-context-worker', model, {
212
+ temperature: Number(process.env.TEMPERATURE ?? '0.8'),
213
+ }),
214
+ }
215
+ const judgeCfg: BenchRouterTarget = {
216
+ routerBaseUrl,
217
+ routerKey,
218
+ profile: benchRouterProfile('clbench-rubric-judge', judgeModel, { temperature: 0 }),
219
+ }
195
220
 
196
221
  console.log(`=== CL-bench (Context Learning) selector gate · N=${n} K=${k} offset=${offset} ===`)
197
222
  console.log(` worker=${model} judge=${judgeModel} (rubric-fraction verifier) router=${routerBaseUrl}`)
@@ -213,8 +238,19 @@ async function main(): Promise<void> {
213
238
  }
214
239
  console.log(`\n▶ solving ${units.length} attempts (${tasks.length} tasks × ${k} shots × 2 arms) via router, conc=${solveConcurrency}`)
215
240
  const outputs = await pool(units, solveConcurrency, async (u) => {
216
- const res = await routerChatWithUsage(workerCfg, u.messages, { temperature: Number(process.env.TEMPERATURE ?? '0.8') })
217
- return typeof res.content === 'string' ? res.content : ''
241
+ const system = u.messages.find((message) => message.role === 'system')?.content
242
+ const res = await runBenchRouterTurn(
243
+ {
244
+ routerBaseUrl: workerCfg.routerBaseUrl,
245
+ routerKey: workerCfg.routerKey,
246
+ profile: withBenchProfile(workerCfg.profile, {
247
+ name: 'clbench-context-worker',
248
+ ...(system ? { systemPrompt: system } : {}),
249
+ }),
250
+ },
251
+ { messages: u.messages.filter((message) => message.role !== 'system') },
252
+ )
253
+ return res.finalText
218
254
  })
219
255
 
220
256
  console.log(`▶ grading ${outputs.length} completions with the rubric judge (${judgeModel}), conc=${solveConcurrency}`)
@@ -29,8 +29,11 @@
29
29
  * dotenvx run -f …/.env.keys -f …/agent-state.env -- \
30
30
  * env MODEL=gpt-4.1 ROUNDS=3 pnpm exec tsx src/cloud-loop.mts
31
31
  */
32
- import { createChatClient } from '@tangle-network/agent-eval'
33
- import { observe, openSandboxRun } from '@tangle-network/agent-runtime/kernel'
32
+ import {
33
+ defaultAnalystInstruction,
34
+ observe,
35
+ openSandboxRun,
36
+ } from '@tangle-network/agent-runtime/kernel'
34
37
  import { Sandbox } from '@tangle-network/sandbox'
35
38
  import { answerOutput, sandboxAgentRun } from './sandbox-run'
36
39
 
@@ -68,11 +71,17 @@ function tools(events: ReadonlyArray<unknown>): string[] {
68
71
 
69
72
  async function main(): Promise<void> {
70
73
  const routerKey = env('TANGLE_API_KEY')
71
- const model = env('MODEL', 'gpt-4.1')
74
+ const model = env('MODEL', 'deepseek-v4-flash')
75
+ const workerProvider = env('WORKER_PROVIDER', 'openai-compat')
72
76
  const routerBaseUrl = env('ROUTER_BASE_URL', 'https://router.tangle.tools/v1')
73
77
  const rounds = Number(env('ROUNDS', '3'))
74
78
  const client = new Sandbox({ baseUrl: env('SANDBOX_BASE_URL', 'https://sandbox.tangle.tools'), apiKey: routerKey })
75
- const chat = createChatClient({ transport: 'router', apiKey: routerKey, baseUrl: routerBaseUrl, defaultModel: model })
79
+ const observerProfile = {
80
+ name: 'trace-observer',
81
+ harness: 'cli-base' as const,
82
+ model: { provider: 'tangle-router', default: model },
83
+ prompt: { systemPrompt: defaultAnalystInstruction },
84
+ }
76
85
 
77
86
  console.error(`\n=== LIVE observe→steer loop · ${model} · real cloud worker + real observer ===\n`)
78
87
  const steers: string[] = []
@@ -89,7 +98,13 @@ async function main(): Promise<void> {
89
98
  let output = ''
90
99
  let events: unknown[] = []
91
100
  try {
92
- const agentRun = sandboxAgentRun({ model, routerBaseUrl, backendType: 'opencode', name: `worker-r${round}` })
101
+ const agentRun = sandboxAgentRun({
102
+ profile: {
103
+ name: `worker-r${round}`,
104
+ harness: 'opencode',
105
+ model: { provider: workerProvider, default: model },
106
+ },
107
+ })
93
108
  const run = await openSandboxRun<string>(
94
109
  client,
95
110
  { agentRun, signal: controller.signal },
@@ -116,7 +131,10 @@ async function main(): Promise<void> {
116
131
  // THE JOIN: a REAL observer reads the REAL trace → a finding → next round's steer.
117
132
  const ob = await observe(
118
133
  { task, output, trace: events, outcome: 'failed', runId: `r${round}` },
119
- { chat, model },
134
+ {
135
+ profile: observerProfile,
136
+ executor: { backend: 'router', routerBaseUrl, routerKey },
137
+ },
120
138
  )
121
139
  const next = ob.findings.flatMap((f) => (f.recommended_action ? [f.recommended_action] : [])).slice(0, 3)
122
140
  if (next.length === 0) {
@@ -12,6 +12,7 @@ import { readFileSync } from 'node:fs'
12
12
  import { join } from 'node:path'
13
13
  import { adaptiveRefine, printBenchmarkReport, refine, runBenchmark, sample, type Strategy } from '@tangle-network/agent-runtime/kernel'
14
14
  import { type Commit0Row, createCommit0Environment, rowToTask } from './commit0-env'
15
+ import { benchRouterProfile } from './router-turn'
15
16
 
16
17
  function must(name: string): string {
17
18
  const v = process.env[name]
@@ -42,9 +43,11 @@ async function main(): Promise<void> {
42
43
  worker: {
43
44
  routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
44
45
  routerKey: must('TANGLE_API_KEY'),
45
- model,
46
- innerTurns: Number(process.env.INNER_TURNS ?? 10),
47
- temperature: 0.4,
46
+ workerProfile: benchRouterProfile('commit0-worker', model, {
47
+ systemPrompt: 'You are a senior Python engineer. Work through the available tools until the task is verified complete.',
48
+ maxTurns: Number(process.env.INNER_TURNS ?? 10),
49
+ temperature: 0.4,
50
+ }),
48
51
  },
49
52
  strategies,
50
53
  budget,
@@ -65,12 +65,11 @@ function parsePytest(out: string): { passed: number; failed: number } {
65
65
  export function rowToTask(row: Commit0Row): AgenticTask {
66
66
  return {
67
67
  id: row.instance_id,
68
- systemPrompt:
68
+ userPrompt:
69
69
  'You are a senior Python engineer implementing a stubbed library so its existing test suite passes. ' +
70
70
  'Workflow: list_files and read the tests + stubs to learn the required behavior, write COMPLETE implementations ' +
71
71
  `with write_file (source under ${row.src_dir} only — the test dir is read-only), then run_tests and fix failures. ` +
72
- 'Iterate until the suite passes. Reply DONE only when run_tests shows no failures.',
73
- userPrompt:
72
+ 'Iterate until the suite passes. Reply DONE only when run_tests shows no failures.\n\n' +
74
73
  `Implement the stubbed library "${row.repo}". The public functions/classes under \`${row.src_dir}\` have empty bodies. ` +
75
74
  `Make the existing tests under \`${row.test.test_dir}\` pass.${row.setup.specification ? ` Spec: ${row.setup.specification}` : ''}`,
76
75
  meta: { instanceId: row.instance_id },