@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
@@ -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
 
package/src/fleet.mts CHANGED
@@ -12,8 +12,13 @@
12
12
  *
13
13
  * Run it twice: the second run injects the first run's learnings into the workers.
14
14
  */
15
- import { createChatClient } from '@tangle-network/agent-eval'
16
- import { FileCorpus, observe, openSandboxRun, renderReport } from '@tangle-network/agent-runtime/kernel'
15
+ import {
16
+ defaultAnalystInstruction,
17
+ FileCorpus,
18
+ observe,
19
+ openSandboxRun,
20
+ renderReport,
21
+ } from '@tangle-network/agent-runtime/kernel'
17
22
  import { Sandbox } from '@tangle-network/sandbox'
18
23
  import { answerOutput, sandboxAgentRun, type WorkerBackendType } from './sandbox-run'
19
24
 
@@ -41,7 +46,7 @@ interface WorkerResult {
41
46
 
42
47
  async function runWorker(
43
48
  client: Sandbox,
44
- cfg: { backendType: WorkerBackendType; model: string; routerBaseUrl: string },
49
+ cfg: { backendType: WorkerBackendType; model: string; provider: string; routerBaseUrl: string },
45
50
  id: string,
46
51
  task: string,
47
52
  priorLearnings: string,
@@ -51,7 +56,13 @@ async function runWorker(
51
56
  const controller = new AbortController()
52
57
  const timer = setTimeout(() => controller.abort(), Number(process.env.TIMEOUT_MS ?? 240_000))
53
58
  try {
54
- const agentRun = sandboxAgentRun({ ...cfg, name: id })
59
+ const agentRun = sandboxAgentRun({
60
+ profile: {
61
+ name: id,
62
+ harness: cfg.backendType,
63
+ model: { provider: cfg.provider, default: cfg.model },
64
+ },
65
+ })
55
66
  const run = await openSandboxRun<string>(
56
67
  client,
57
68
  { agentRun, signal: controller.signal },
@@ -74,13 +85,19 @@ async function main(): Promise<void> {
74
85
  const routerKey = env('TANGLE_API_KEY')
75
86
  const cfg = {
76
87
  backendType: env('BACKEND', 'opencode') as WorkerBackendType,
77
- model: env('MODEL', 'gpt-4.1'),
88
+ model: env('MODEL', 'deepseek-v4-flash'),
89
+ provider: env('WORKER_PROVIDER', 'openai-compat'),
78
90
  routerBaseUrl: env('ROUTER_BASE_URL', 'https://router.tangle.tools/v1'),
79
91
  }
80
92
  const n = Math.min(Number(env('N', '2')), subtasks.length)
81
93
  const corpus = new FileCorpus(env('CORPUS', '/tmp/fleet-corpus.jsonl'))
82
- const observerModel = env('OBSERVER_MODEL', 'gpt-4.1')
83
- const chat = createChatClient({ transport: 'router', apiKey: routerKey, baseUrl: cfg.routerBaseUrl, defaultModel: observerModel })
94
+ const observerModel = env('OBSERVER_MODEL', 'deepseek-v4-flash')
95
+ const observerProfile = {
96
+ name: 'fleet-observer',
97
+ harness: 'cli-base' as const,
98
+ model: { provider: 'tangle-router', default: observerModel },
99
+ prompt: { systemPrompt: defaultAnalystInstruction },
100
+ }
84
101
  const client = new Sandbox({ baseUrl: env('SANDBOX_BASE_URL', 'https://sandbox.tangle.tools'), apiKey: routerKey })
85
102
 
86
103
  // ── continuous: read what prior runs LEARNED, inject it into this run's workers
@@ -104,7 +121,12 @@ async function main(): Promise<void> {
104
121
  if (w.error) continue
105
122
  const ob = await observe(
106
123
  { task: w.task, output: w.output, trace: w.events, outcome: w.output ? 'passed' : 'unknown', runId: w.id },
107
- { chat, model: observerModel, corpus, tags: [cfg.backendType, 'fleet'] },
124
+ {
125
+ profile: observerProfile,
126
+ executor: { backend: 'router', routerBaseUrl: cfg.routerBaseUrl, routerKey },
127
+ corpus,
128
+ tags: [cfg.backendType, 'fleet'],
129
+ },
108
130
  )
109
131
  totalLearned += ob.learned.length
110
132
  console.error(` answer: ${w.output.slice(0, 120).replace(/\n/g, ' ')}`)
package/src/gate-cli.mts CHANGED
@@ -46,9 +46,16 @@ async function main(): Promise<void> {
46
46
 
47
47
  const profile = {
48
48
  name: 'gate-solver',
49
- model: { default: model },
49
+ harness: 'cli-base',
50
+ model: {
51
+ provider: 'tangle-router',
52
+ default: model,
53
+ metadata: {
54
+ temperature: Number(process.env.TEMPERATURE ?? 0.7),
55
+ },
56
+ },
50
57
  prompt: { systemPrompt: 'You are an expert agent. Produce the single best deliverable the task’s grader will accept.' },
51
- } as unknown as AgentProfile
58
+ } satisfies AgentProfile
52
59
 
53
60
  const report = await runGate({
54
61
  adapter,
@@ -56,8 +63,6 @@ async function main(): Promise<void> {
56
63
  strategies,
57
64
  routerBaseUrl,
58
65
  routerKey,
59
- model,
60
- ...(process.env.TEMPERATURE ? { temperature: Number(process.env.TEMPERATURE) } : {}),
61
66
  n: Number(process.env.N ?? 20),
62
67
  ...(process.env.IDS ? { ids: process.env.IDS.split(',') } : {}),
63
68
  ...(process.env.SPLIT ? { split: process.env.SPLIT } : {}),
package/src/gate.test.mts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import assert from 'node:assert/strict'
14
+ import type { AgentProfile } from '@tangle-network/agent-interface'
14
15
  import type {
15
16
  AgentSpec,
16
17
  DefaultVerdict,
@@ -80,7 +81,12 @@ function stubAdapter(n: number): BenchmarkAdapter {
80
81
  }
81
82
  }
82
83
 
83
- const profile = { name: 'stub-solver', model: { default: 'stub-model' } } as never
84
+ const profile = {
85
+ name: 'stub-solver',
86
+ harness: 'pi',
87
+ model: { provider: 'test', default: 'stub-model' },
88
+ prompt: { systemPrompt: 'Solve the supplied benchmark task.' },
89
+ } satisfies AgentProfile
84
90
 
85
91
  const report = await runGate({
86
92
  adapter: stubAdapter(5),
@@ -88,7 +94,6 @@ const report = await runGate({
88
94
  strategies: ['plain restate', 'use the STRONG verified approach', 'enumerate edge cases'],
89
95
  routerBaseUrl: 'http://unused',
90
96
  routerKey: 'unused',
91
- model: 'stub-model',
92
97
  solverRegistry: stubRegistry,
93
98
  })
94
99