@tangle-network/agent-bench 0.7.1 → 0.8.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/HARNESS.md +2 -2
  3. package/dist/adapters.js +6 -2
  4. package/dist/adapters.js.map +1 -1
  5. package/dist/benchmarks/appworld.d.ts +61 -2
  6. package/dist/benchmarks/appworld.js +111 -40
  7. package/dist/benchmarks/appworld.js.map +1 -1
  8. package/dist/benchmarks/cadbench.js +1 -1
  9. package/dist/benchmarks/cadgenbench.js +1 -1
  10. package/dist/benchmarks/finresearchbench.js +16 -22
  11. package/dist/benchmarks/finresearchbench.js.map +1 -1
  12. package/dist/benchmarks/finsearchcomp.js +16 -22
  13. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  14. package/dist/benchmarks/frames.js +18 -20
  15. package/dist/benchmarks/frames.js.map +1 -1
  16. package/dist/benchmarks/mcad-bench.d.ts +106 -0
  17. package/dist/benchmarks/mcad-bench.js +569 -0
  18. package/dist/benchmarks/mcad-bench.js.map +1 -0
  19. package/dist/benchmarks/mcad-cq-bench.d.ts +82 -0
  20. package/dist/benchmarks/mcad-cq-bench.js +339 -0
  21. package/dist/benchmarks/mcad-cq-bench.js.map +1 -0
  22. package/dist/benchmarks/mcad-cq-golds.d.ts +36 -0
  23. package/dist/benchmarks/mcad-cq-golds.js +342 -0
  24. package/dist/benchmarks/mcad-cq-golds.js.map +1 -0
  25. package/dist/benchmarks/mcad-golds.d.ts +20 -0
  26. package/dist/benchmarks/mcad-golds.js +318 -0
  27. package/dist/benchmarks/mcad-golds.js.map +1 -0
  28. package/dist/benchmarks/mcad-tasks.d.ts +66 -0
  29. package/dist/benchmarks/mcad-tasks.js +508 -0
  30. package/dist/benchmarks/mcad-tasks.js.map +1 -0
  31. package/dist/benchmarks/simpleqa.js +15 -19
  32. package/dist/benchmarks/simpleqa.js.map +1 -1
  33. package/dist/benchmarks/trata-hedge.js +20 -23
  34. package/dist/benchmarks/trata-hedge.js.map +1 -1
  35. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  36. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  37. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  38. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.js +10 -9
  41. package/dist/index.js.map +1 -1
  42. package/dist/router-turn-C2wMiDoo.js +31 -0
  43. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  44. package/package.json +7 -7
  45. package/scripts/appworld_driver.py +1 -170
  46. package/scripts/run-package-tests.mjs +56 -36
  47. package/scripts/run-package-tests.test.mjs +33 -0
  48. package/scripts/trata-hedge/README.md +3 -3
  49. package/scripts/trata-hedge/run.sh +6 -2
  50. package/src/adapters.ts +11 -0
  51. package/src/aec-gate.mts +30 -9
  52. package/src/agent-graphs-gen2.mts +523 -0
  53. package/src/agent-graphs-gen3.mts +660 -0
  54. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  55. package/src/agent-graphs-improve.mts +747 -0
  56. package/src/atom-humaneval.mts +76 -55
  57. package/src/atom-mcp-e2e.mts +27 -13
  58. package/src/benchmarks/appworld.test.mts +119 -1
  59. package/src/benchmarks/appworld.ts +156 -33
  60. package/src/benchmarks/cadbench.ts +21 -8
  61. package/src/benchmarks/finresearchbench.ts +20 -16
  62. package/src/benchmarks/finsearchcomp.ts +20 -20
  63. package/src/benchmarks/frames.ts +19 -18
  64. package/src/benchmarks/mcad-bench.test.mts +455 -0
  65. package/src/benchmarks/mcad-bench.ts +561 -0
  66. package/src/benchmarks/mcad-cq-bench.ts +423 -0
  67. package/src/benchmarks/mcad-cq-golds.ts +374 -0
  68. package/src/benchmarks/mcad-cq.test.mts +386 -0
  69. package/src/benchmarks/mcad-golds.ts +359 -0
  70. package/src/benchmarks/mcad-tasks.ts +490 -0
  71. package/src/benchmarks/simpleqa.ts +19 -17
  72. package/src/benchmarks/trata-hedge.ts +26 -18
  73. package/src/browser/ui-reviewer.ts +1 -1
  74. package/src/clbench-context-gate.mts +45 -9
  75. package/src/cloud-loop.mts +24 -6
  76. package/src/commit0-env-run.mts +6 -3
  77. package/src/commit0-env.ts +2 -3
  78. package/src/commit0-gate.mts +53 -24
  79. package/src/corpus.ts +3 -3
  80. package/src/david-attribution.mts +28 -9
  81. package/src/david-goliath.mts +31 -12
  82. package/src/egress-probe.mts +3 -3
  83. package/src/examples/lean-proof-gate.mts +10 -3
  84. package/src/examples/math-demo.mts +10 -3
  85. package/src/examples/strategy-demo.mts +10 -4
  86. package/src/fleet.mts +30 -8
  87. package/src/gate-cli.mts +9 -4
  88. package/src/gate.test.mts +7 -2
  89. package/src/gate.ts +23 -75
  90. package/src/generate-eval/certify.ts +13 -5
  91. package/src/hev-eval.mts +39 -10
  92. package/src/hev-improve.mts +28 -16
  93. package/src/hev-structural.mts +28 -20
  94. package/src/humaneval-gate.mts +24 -8
  95. package/src/humaneval-object-ablation.mts +52 -14
  96. package/src/humaneval-repair-gate.mts +53 -14
  97. package/src/mbpp-structural.mts +28 -20
  98. package/src/mcp-mount-probe.mts +23 -11
  99. package/src/official-optimizer-config.mts +61 -12
  100. package/src/official-optimizer-config.test.mts +72 -3
  101. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  102. package/src/quant-arena/quant-loop.mts +88 -61
  103. package/src/research-shot.ts +21 -11
  104. package/src/resolve-client.ts +6 -8
  105. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  106. package/src/router-executor.ts +1 -1
  107. package/src/router-turn.ts +135 -0
  108. package/src/run-benchmarks.ts +7 -2
  109. package/src/sandbox-run.ts +23 -41
  110. package/src/search-bench/bridge.ts +66 -8
  111. package/src/search-bench/parametric-check.mts +19 -4
  112. package/src/search-bench/profiles.ts +24 -9
  113. package/src/search-bench/run.mts +3 -4
  114. package/src/smoke-structural-rollout.mts +21 -5
  115. package/src/supervisor-arena.mts +28 -20
  116. package/src/swe-arena/arms.ts +73 -15
  117. package/src/swe-arena/capacity.ts +44 -41
  118. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  119. package/src/swe-arena/execution.test.mts +12 -12
  120. package/src/swe-arena/gepa-seat.mts +12 -5
  121. package/src/swe-arena/gepa-seat.test.mts +45 -33
  122. package/src/swe-arena/outer-loop.mts +94 -70
  123. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  124. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  125. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  126. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  127. package/src/swe-arena/proposer-fanout.mts +52 -99
  128. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  129. package/src/swe-arena/proposer-provenance.mts +1 -1
  130. package/src/swe-code-improve.mts +30 -52
  131. package/src/swe-emit-patch.mts +26 -5
  132. package/src/swe-improve.mts +27 -8
  133. package/src/swe-jail.test.ts +128 -0
  134. package/src/swe-jail.ts +106 -22
  135. package/src/swe-local-proof.mts +27 -5
  136. package/src/swe-repro-calibrate.mts +5 -0
  137. package/src/swe-self-improve.mts +45 -10
  138. package/src/swe-stream.mts +68 -6
  139. package/src/swe-structural.mts +46 -7
  140. package/src/tb-container-executor.mts +38 -6
  141. package/src/tb-container-executor.test.mts +11 -3
  142. package/src/tb-supervisor-sidecar.mts +11 -27
  143. package/src/terminal-compare.ts +38 -18
  144. package/src/trata-gate.mts +26 -26
  145. package/src/trata-gepa.mts +25 -16
  146. package/src/trata-hedge-solve.mts +80 -0
  147. package/src/worker-blender.ts +21 -11
  148. package/src/worker-browser.ts +18 -5
  149. package/src/worker-build123d.ts +17 -8
  150. package/src/worker-cad.ts +31 -12
  151. package/src/worker.ts +1 -1
  152. package/tb_agents/opencode_router_agent.py +84 -42
  153. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  154. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  155. package/scripts/trata-hedge/solve.py +0 -83
@@ -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 },
@@ -53,10 +53,13 @@ import { mkdtemp, rm } from 'node:fs/promises'
53
53
  import { tmpdir } from 'node:os'
54
54
  import { join } from 'node:path'
55
55
  import {
56
+ collectAgentTurn,
57
+ createExecutor,
56
58
  type AgentRunSpec,
57
59
  type Deliverable,
58
60
  openSandboxRun,
59
61
  type SandboxRun,
62
+ streamAgentTurn,
60
63
  } from '@tangle-network/agent-runtime/kernel'
61
64
  import { Sandbox } from '@tangle-network/sandbox'
62
65
  import { createCommit0Adapter } from './benchmarks/commit0'
@@ -153,8 +156,17 @@ interface ShotCfg {
153
156
  * registered model names (e.g. gpt-4.1). Override via WORKER_PROVIDER. */
154
157
  provider: string
155
158
  timeoutMs: number
156
- /** local-backend: the opencode CLI binary (cli-bridge fallback when the sandbox is down). */
157
- opencodeBin: string
159
+ /** Local Runtime bridge transport. The profile, not this transport, selects the harness/model. */
160
+ bridgeUrl?: string
161
+ bridgeBearer?: string
162
+ }
163
+
164
+ function workerProfile(cfg: ShotCfg, name: string) {
165
+ return {
166
+ name,
167
+ harness: 'opencode' as const,
168
+ model: { provider: cfg.provider, default: cfg.model },
169
+ }
158
170
  }
159
171
 
160
172
  /** The diff the in-box agent produces, read back off the box FS (+ any stream error). */
@@ -189,21 +201,15 @@ async function runShot(task: BenchTask, attempt: number, cfg: ShotCfg, steer?: s
189
201
  // other rollout (the powered-run crash).
190
202
  const controller = new AbortController()
191
203
  const timer = cfg.timeoutMs > 0 ? setTimeout(() => controller.abort(), cfg.timeoutMs) : undefined
192
- // backend.model pins provider/model/baseUrl only; the platform writes the in-box
193
- // provider config keyed to the box's own OPENCODE_MODEL_API_KEY. The inline
194
- // profile + backend override is the same generic AgentRunSpec the runLoop kernel
195
- // boots. Never inject an external key — the egress proxy 403s foreign credentials.
204
+ // The exact profile owns harness/provider/model. Sandbox overrides contain only box
205
+ // infrastructure; Runtime derives the backend from the profile and refuses conflicts.
196
206
  const agentRun: AgentRunSpec<string> = {
197
- profile: { name: 'commit0-worker', metadata: { backendType: 'opencode' } },
207
+ profile: workerProfile(cfg, 'commit0-worker'),
198
208
  name: 'commit0-worker',
199
209
  taskToPrompt: () => '', // unused — the prompt is streamed directly by openSandboxRun
200
210
  sandboxOverrides: {
201
211
  name: `commit0-${task.id}-${attempt}-${randomSuffix()}`.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 60),
202
212
  environment: 'universal',
203
- backend: {
204
- type: 'opencode',
205
- model: { provider: cfg.provider, model: cfg.model, baseUrl: cfg.routerBaseUrl },
206
- },
207
213
  },
208
214
  }
209
215
  let run: SandboxRun<RolloutDeliverable> | undefined
@@ -310,15 +316,29 @@ async function runShotLocal(task: BenchTask, attempt: number, cfg: ShotCfg, stee
310
316
  if (co.code !== 0) {
311
317
  return { task, attempt, diff: '', ok: false, events: 0, wallMs: Date.now() - startedAt, detail: `git checkout ${meta.baseCommit} failed: ${co.out.trim().slice(-180)}` }
312
318
  }
313
- // openai/* → route through the router (OPENAI_* env); anything else → opencode's
314
- // OWN configured auth (kimi-for-coding / zai coding-plan subscriptions).
315
- const env = cfg.model.startsWith('openai/')
316
- ? { ...process.env, OPENAI_API_KEY: cfg.routerKey, OPENAI_BASE_URL: cfg.routerBaseUrl }
317
- : process.env
318
319
  const prompt = steer ? steeredPrompt(localRolloutPrompt(meta), steer) : localRolloutPrompt(meta)
319
- const oc = await sh(cfg.opencodeBin, ['run', prompt, '-m', cfg.model, '--dir', dir], { timeoutMs: cfg.timeoutMs, env })
320
- const lines = oc.out.split('\n')
321
- const events = lines.length
320
+ if (!cfg.bridgeUrl || !cfg.bridgeBearer) {
321
+ throw new Error('local rollout requires CLI_BRIDGE_URL/BRIDGE_URL and CLI_BRIDGE_BEARER/BRIDGE_BEARER')
322
+ }
323
+ const factory = createExecutor({
324
+ backend: 'bridge',
325
+ bridgeUrl: cfg.bridgeUrl,
326
+ bridgeBearer: cfg.bridgeBearer,
327
+ cwd: dir,
328
+ ...(cfg.timeoutMs > 0 ? { timeoutMs: cfg.timeoutMs } : {}),
329
+ })
330
+ const turn = await collectAgentTurn(
331
+ streamAgentTurn(
332
+ { kind: 'executor', factory, profile: workerProfile(cfg, `commit0-local-${attempt}`) },
333
+ prompt,
334
+ cfg.timeoutMs > 0 ? { timeoutMs: cfg.timeoutMs } : {},
335
+ ),
336
+ )
337
+ if (turn.status !== 'completed') {
338
+ throw new Error(turn.error?.message ?? `Runtime bridge ended with ${turn.status}`)
339
+ }
340
+ const traceEvents = turn.events.map((event) => JSON.stringify(event))
341
+ const events = turn.events.length
322
342
  // Read the diff straight from git, scoped to src_dir (excludes the .venv the agent made).
323
343
  const diffRes = await sh('bash', ['-c', `cd ${JSON.stringify(dir)} && git add -- ${JSON.stringify(meta.srcDir)} && git diff --cached -- ${JSON.stringify(meta.srcDir)}`], { timeoutMs: 60_000 })
324
344
  const diff = diffRes.out
@@ -329,10 +349,10 @@ async function runShotLocal(task: BenchTask, attempt: number, cfg: ShotCfg, stee
329
349
  diff,
330
350
  ok,
331
351
  events,
332
- traceEvents: lines.slice(-TRACE_EVENTS_TAIL),
352
+ traceEvents: traceEvents.slice(-TRACE_EVENTS_TAIL),
333
353
  ...(steer ? { steer } : {}),
334
354
  wallMs: Date.now() - startedAt,
335
- ...(ok ? {} : { detail: `no diff (opencode rc=${oc.code}): ${oc.out.trim().slice(-160)}` }),
355
+ ...(ok ? {} : { detail: `no diff (Runtime bridge completed): ${turn.finalText.trim().slice(-160)}` }),
336
356
  }
337
357
  } catch (err) {
338
358
  return { task, attempt, diff: '', ok: false, events: 0, ...(steer ? { steer } : {}), wallMs: Date.now() - startedAt, detail: `local rollout error: ${(err instanceof Error ? err.message : String(err)).slice(0, 180)}` }
@@ -349,7 +369,7 @@ async function main(): Promise<void> {
349
369
  const backend = process.env.COMMIT0_BACKEND === 'local' ? 'local' : 'sandbox'
350
370
  const n = Number(process.env.N ?? 8)
351
371
  const k = Number(process.env.K ?? 4)
352
- const model = process.env.WORKER_MODEL ?? (backend === 'local' ? 'kimi-for-coding/kimi-k2-thinking' : 'gpt-4.1')
372
+ const model = process.env.WORKER_MODEL ?? (backend === 'local' ? 'kimi-for-coding/kimi-k2-thinking' : 'deepseek-v4-flash')
353
373
  const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
354
374
  // The arms under test. `random` = K independent blind shots (the equal-compute
355
375
  // control); `refineAudit` = blind shot 0, then trace-only-analyst-steered shots.
@@ -363,7 +383,6 @@ async function main(): Promise<void> {
363
383
  const needsRouterKey = backend === 'sandbox' || model.startsWith('openai/') || armNames.includes('refineAudit')
364
384
  const routerKey = needsRouterKey ? must('TANGLE_API_KEY') : (process.env.TANGLE_API_KEY ?? '')
365
385
  const sandboxBaseUrl = process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools'
366
- const opencodeBin = process.env.OPENCODE_BIN ?? join(process.env.HOME ?? '', '.local/bin/opencode')
367
386
  // openai-compat = generic passthrough so cheap router models resolve in-box;
368
387
  // `openai` rejects non-registered model names. Override via WORKER_PROVIDER.
369
388
  const provider = process.env.WORKER_PROVIDER ?? 'openai-compat'
@@ -384,7 +403,17 @@ async function main(): Promise<void> {
384
403
 
385
404
  // Phase 1 — rollouts, concurrent. sandbox = remote box; local = cli-bridge (opencode
386
405
  // in a tmpdir, diff read from git). Both fault-isolated → a failure is a NO-DIFF, never a throw.
387
- const cfg: ShotCfg = { sandboxBaseUrl, sandboxKey: routerKey, routerBaseUrl, routerKey, model, provider, timeoutMs, opencodeBin }
406
+ const cfg: ShotCfg = {
407
+ sandboxBaseUrl,
408
+ sandboxKey: routerKey,
409
+ routerBaseUrl,
410
+ routerKey,
411
+ model,
412
+ provider,
413
+ timeoutMs,
414
+ bridgeUrl: process.env.CLI_BRIDGE_URL ?? process.env.BRIDGE_URL,
415
+ bridgeBearer: process.env.CLI_BRIDGE_BEARER ?? process.env.BRIDGE_BEARER,
416
+ }
388
417
  const runRollout = backend === 'local' ? runShotLocal : runShot
389
418
  const analyze: AnalystFn = llmAnalyst({ routerBaseUrl, routerKey, model: analystModel })
390
419
  const logShot = (armName: string, s: Shot) =>
package/src/corpus.ts CHANGED
@@ -23,7 +23,7 @@ import type { BenchRuntimeDecisionPoint, BenchRuntimeHookEvent } from './runtime
23
23
  * verdict, the measured economics, and a bounded trace summary.
24
24
  *
25
25
  * `costUsd`/`tokensIn`/`tokensOut`/`wallMs` are OPTIONAL on purpose: they are
26
- * present only when the worker actually reported them (the `runLoop`/kernel
26
+ * present only when the worker actually reported them (the `runAgentRounds`/kernel
27
27
  * path). A worker that reports no usage (e.g. a raw opencode-stdout shot)
28
28
  * OMITS them — it never writes a fabricated `0`. Absence means "unmeasured",
29
29
  * which is honest and lets the canonical bridge below refuse to forge a
@@ -106,7 +106,7 @@ function summarizeAttempt<Task, Output>(iter: Iteration<Task, Output>): AttemptR
106
106
  }
107
107
  }
108
108
 
109
- /** Build a RunRecord from a runLoop result. `now` injected for determinism in tests. */
109
+ /** Build a RunRecord from a runAgentRounds result. `now` injected for determinism in tests. */
110
110
  export function buildRunRecord<Task, Output>(args: {
111
111
  benchmark: string
112
112
  instanceId: string
@@ -247,7 +247,7 @@ export interface CorpusProjection {
247
247
  * (`costUsd`/`tokensIn`/`tokensOut`/`wallMs`/`output`) is reported in
248
248
  * `unmappable` rather than backfilled with phantom zeros. This is WHY the local
249
249
  * raw-stdout path (which omits economics) cannot feed the gate — only the
250
- * measured `runLoop`/sandbox path can, which is the correct, honest constraint.
250
+ * measured `runAgentRounds`/sandbox path can, which is the correct, honest constraint.
251
251
  */
252
252
  export async function benchRecordToCorpusRecords(
253
253
  rec: RunRecord,
@@ -20,24 +20,43 @@ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
20
20
  import { tmpdir } from 'node:os'
21
21
  import { join } from 'node:path'
22
22
  import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
23
+ import { benchRouterProfile, runBenchRouterTurn } from './router-turn'
23
24
 
24
- const KEY = process.env.TANGLE_API_KEY!
25
+ function requiredEnv(name: string): string {
26
+ const value = process.env[name]
27
+ if (!value) throw new Error(`${name} required`)
28
+ return value
29
+ }
30
+
31
+ const KEY = requiredEnv('TANGLE_API_KEY')
25
32
  const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
26
33
  const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
27
34
  const N = Number(process.env.N ?? 8)
28
35
  const T = Number(process.env.T ?? 5)
29
36
  const NTASKS = Number(process.env.NTASKS ?? 60)
30
37
  const CONC = Number(process.env.CONCURRENCY ?? 6)
38
+ const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 1000)
39
+ const LLM_TIMEOUT_MS = Number(process.env.LLM_TIMEOUT_MS ?? 60_000)
31
40
 
32
- const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
33
41
  async function chat(messages: { role: string; content: string }[], temp: number): Promise<string> {
34
- for (let a = 0; ; a++) {
35
- try {
36
- const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model: DAVID, messages, temperature: temp, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
37
- if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
38
- if (!r.ok) return ''
39
- return (((await r.json()) as { choices?: { message?: { content?: string } }[] }).choices?.[0]?.message?.content) ?? ''
40
- } catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
42
+ try {
43
+ const system = messages.find((message) => message.role === 'system')?.content
44
+ const turn = await runBenchRouterTurn(
45
+ {
46
+ routerBaseUrl: ROUTER,
47
+ routerKey: KEY,
48
+ profile: benchRouterProfile('david-attribution-worker', DAVID, {
49
+ ...(system ? { systemPrompt: system } : {}),
50
+ temperature: temp,
51
+ maxTokens: MAX_TOKENS,
52
+ }),
53
+ timeoutMs: LLM_TIMEOUT_MS,
54
+ },
55
+ { messages: messages.filter((message) => message.role !== 'system') },
56
+ )
57
+ return turn.finalText
58
+ } catch {
59
+ return ''
41
60
  }
42
61
  }
43
62
  const exec = (f: string, a: string[], o: object) => new Promise<number>((res) => execFile(f, a, { ...o, maxBuffer: 8e6 }, (e) => res((e as { code?: number } | null)?.code ?? (e ? 1 : 0))))
@@ -17,7 +17,7 @@
17
17
  * generator punch above its solo weight — the standing "verification is live" claim
18
18
  * at its most dramatic. Paired McNemar on per-task discordant pairs for significance.
19
19
  *
20
- * Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant GOLIATH=anthropic/claude-haiku-4-5-20251001 \
20
+ * Run from cwd=bench: env DAVID=glm-5.2 GOLIATH=deepseek-v4-flash \
21
21
  * N=8 T=5 NTASKS=164 REPS=2 node_modules/.bin/tsx src/david-goliath.mts
22
22
  */
23
23
  import { execFile } from 'node:child_process'
@@ -25,18 +25,22 @@ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
25
25
  import { tmpdir } from 'node:os'
26
26
  import { join } from 'node:path'
27
27
  import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
28
+ import { runBenchRouterTurn } from './router-turn'
28
29
 
29
30
  const KEY = process.env.TANGLE_API_KEY
30
31
  if (!KEY) throw new Error('TANGLE_API_KEY required')
32
+ const ROUTER_KEY = KEY
31
33
  const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
32
34
  const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
33
- const GOLIATH = process.env.GOLIATH ?? 'anthropic/claude-haiku-4-5-20251001'
35
+ const GOLIATH = process.env.GOLIATH ?? 'deepseek-v4-flash'
34
36
  const N = Number(process.env.N ?? 8) // David candidate solutions
35
37
  const T = Number(process.env.T ?? 5) // David generated tests
36
38
  const NTASKS = Number(process.env.NTASKS ?? 164)
37
39
  const REPS = Number(process.env.REPS ?? 2)
38
40
  const CONC = Number(process.env.CONCURRENCY ?? 6)
39
41
  const EXEC_TIMEOUT = Number(process.env.EXEC_TIMEOUT_MS ?? 6000)
42
+ const LLM_TIMEOUT = Number(process.env.LLM_TIMEOUT_MS ?? 60_000)
43
+ const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 1000)
40
44
 
41
45
  // Approx $/1M tokens (in,out) for cost accounting — the router does not price
42
46
  // every model inline, so use public rates; a cheap/frontier gap of ~20-30x is the
@@ -56,18 +60,33 @@ const addU = (a: Usage, b: Usage) => { a.in += b.in; a.out += b.out }
56
60
  const usd = (m: string, u: Usage) => { const [pi, po] = priceOf(m); return (u.in * pi + u.out * po) / 1e6 }
57
61
 
58
62
  async function chat(model: string, messages: { role: string; content: string }[], temperature: number, usage: Usage): Promise<string> {
59
- for (let a = 0; ; a++) {
60
- try {
61
- const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model, messages, temperature, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
62
- if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
63
- if (!r.ok) return ''
64
- const j = (await r.json()) as { choices?: { message?: { content?: string } }[]; usage?: { prompt_tokens?: number; completion_tokens?: number } }
65
- addU(usage, { in: j.usage?.prompt_tokens ?? 0, out: j.usage?.completion_tokens ?? 0 })
66
- return j.choices?.[0]?.message?.content ?? ''
67
- } catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
63
+ try {
64
+ const system = messages.find((message) => message.role === 'system')?.content
65
+ const result = await runBenchRouterTurn(
66
+ {
67
+ routerBaseUrl: ROUTER,
68
+ routerKey: ROUTER_KEY,
69
+ profile: {
70
+ name: 'david-goliath-worker',
71
+ harness: 'cli-base',
72
+ model: {
73
+ provider: 'tangle-router',
74
+ default: model,
75
+ metadata: { temperature, maxTokens: MAX_TOKENS },
76
+ },
77
+ ...(system ? { prompt: { systemPrompt: system } } : {}),
78
+ },
79
+ timeoutMs: LLM_TIMEOUT,
80
+ },
81
+ { messages: messages.filter((message) => message.role !== 'system') },
82
+ )
83
+ if (result.usage.tokensKnown === false) throw new Error('provider omitted token usage')
84
+ addU(usage, { in: result.usage.input, out: result.usage.output })
85
+ return result.finalText
86
+ } catch {
87
+ return ''
68
88
  }
69
89
  }
70
- const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
71
90
  const exec = (file: string, args: string[], o: object) => new Promise<{ code: number; stdout: string }>((res) => execFile(file, args, { ...o, maxBuffer: 8 * 1024 * 1024 }, (e, stdout) => res({ code: (e as { code?: number } | null)?.code ?? (e ? 1 : 0), stdout: String(stdout) })))
72
91
  async function runPy(program: string): Promise<{ ok: boolean }> {
73
92
  const d = mkdtempSync(join(tmpdir(), 'dg-'))
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Sandbox->router egress probe: spins a box and makes one authed chat call to
2
+ * Sandbox->router egress probe: spins a box and makes one model-free authenticated API read from
3
3
  * https://router.tangle.tools/v1 using the BOX-PROVISIONED key (OPENCODE_MODEL_API_KEY
4
4
  * inside the box). That is the sanctioned flow: the egress proxy validates/injects
5
5
  * credentials at the boundary and 403s foreign keys passed in from outside — a raw
@@ -19,8 +19,8 @@ try {
19
19
  const r = await box.exec(`curl -s -o /dev/null -w '%{http_code}' --max-time 15 https://${h}/ || echo FAIL`)
20
20
  console.log(h, '→', out(r))
21
21
  }
22
- const chat = await box.exec(`curl -s --max-time 30 -X POST https://router.tangle.tools/v1/chat/completions -H "Authorization: Bearer $OPENCODE_MODEL_API_KEY" -H 'Content-Type: application/json' -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"say ok"}],"max_tokens":5}' -o /tmp/b -w '%{http_code}'; echo ' |'; head -c 250 /tmp/b`)
23
- console.log('chat (box-provisioned key):', out(chat) || 'EMPTY — egress broken')
22
+ const models = await box.exec(`curl -s --max-time 30 https://router.tangle.tools/v1/models -H "Authorization: Bearer $OPENCODE_MODEL_API_KEY" -o /tmp/b -w '%{http_code}'; echo ' |'; head -c 250 /tmp/b`)
23
+ console.log('models API (box-provisioned key):', out(models) || 'EMPTY — egress broken')
24
24
  } finally {
25
25
  await box.delete()
26
26
  }
@@ -24,6 +24,7 @@ import {
24
24
  sampleThenRefine,
25
25
  } from '@tangle-network/agent-runtime/kernel'
26
26
  import { ensureLeanImage, leanCheck } from './lean-verify.js'
27
+ import { benchRouterProfile } from '../router-turn'
27
28
 
28
29
  // Real, mathlib-free Lean 4 theorems (compile in core Lean). `header` is everything up to `:=`;
29
30
  // the prover supplies the proof term / tactic block. `reference` is a known-good proof used only
@@ -145,9 +146,15 @@ async function main(): Promise<void> {
145
146
  worker: {
146
147
  routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
147
148
  routerKey,
148
- model: process.env.WORKER_MODEL ?? 'gpt-4.1',
149
- innerTurns: 8, // room to call lean_check and fix
150
- temperature: 0.4,
149
+ workerProfile: benchRouterProfile(
150
+ 'lean-proof-worker',
151
+ process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
152
+ {
153
+ systemPrompt: 'Construct and verify Lean proofs with the available checker before answering.',
154
+ maxTurns: 8,
155
+ temperature: 0.4,
156
+ },
157
+ ),
151
158
  },
152
159
  strategies: [sample, refine, sampleThenRefine],
153
160
  budget: Number(process.env.BUDGET ?? 3),
@@ -20,6 +20,7 @@ import {
20
20
  sample,
21
21
  sampleThenRefine,
22
22
  } from '@tangle-network/agent-runtime/kernel'
23
+ import { benchRouterProfile } from '../router-turn'
23
24
 
24
25
  // GSM8K-style problems; meta.answer is the ground truth the deployable check compares to.
25
26
  const problems: Array<{ q: string; answer: number }> = [
@@ -95,9 +96,15 @@ async function main(): Promise<void> {
95
96
  worker: {
96
97
  routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
97
98
  routerKey,
98
- model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
99
- innerTurns: 6,
100
- temperature: 0.6,
99
+ workerProfile: benchRouterProfile(
100
+ 'math-worker',
101
+ process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
102
+ {
103
+ systemPrompt: 'Solve the requested math task with the available tools and verify the result.',
104
+ maxTurns: 6,
105
+ temperature: 0.6,
106
+ },
107
+ ),
101
108
  },
102
109
  strategies: [sample, refine, sampleThenRefine],
103
110
  budget: Number(process.env.BUDGET ?? 3),
@@ -13,6 +13,7 @@
13
13
  * TANGLE_API_KEY=... WORKER_MODEL=gpt-4o-mini tsx src/examples/strategy-demo.mts
14
14
  */
15
15
  import { adaptiveRefine, type AgenticTask, type ArtifactHandle, defineStrategy, type Environment, printBenchmarkReport, refine, runBenchmark, sample } from '@tangle-network/agent-runtime/kernel'
16
+ import { benchRouterProfile } from '../router-turn'
16
17
 
17
18
  // ── 1. Implement an Environment (the only thing a new domain writes) ──────────────
18
19
  // A toy: the agent must drive a counter to exactly the target using the increment tool.
@@ -59,8 +60,7 @@ const counterEnv: Environment = {
59
60
 
60
61
  const task: AgenticTask = {
61
62
  id: 'counter-to-5',
62
- systemPrompt: 'You operate a counter with tools.',
63
- userPrompt: `Use the increment tool to bring the counter to exactly ${target}. Use read_count to verify before you finish. Reply DONE when the count equals ${target}.`,
63
+ userPrompt: `You operate a counter with tools. Use the increment tool to bring the counter to exactly ${target}. Use read_count to verify before you finish. Reply DONE when the count equals ${target}.`,
64
64
  }
65
65
 
66
66
  // ── 3. Author your OWN strategy in ~10 lines — the lego (no Supervisor ceremony) ──
@@ -97,8 +97,14 @@ async function main(): Promise<void> {
97
97
  const worker = {
98
98
  routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
99
99
  routerKey: process.env.TANGLE_API_KEY ?? '',
100
- model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
101
- innerTurns: 6,
100
+ workerProfile: benchRouterProfile(
101
+ 'strategy-demo-worker',
102
+ process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
103
+ {
104
+ systemPrompt: 'Use the available tools to complete and verify the task.',
105
+ maxTurns: 6,
106
+ },
107
+ ),
102
108
  }
103
109
  if (!worker.routerKey) throw new Error('set TANGLE_API_KEY (the worker calls the router)')
104
110