@tangle-network/agent-bench 0.1.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 (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The generated-eval interchange contract.
3
+ *
4
+ * This schema — not our code — is the integration surface: authoring agents
5
+ * emit it, the certifier validates it, runners consume it, the leaderboard
6
+ * aggregates it. It extends the bench `SearchTask` (so admitted evals drop
7
+ * straight into the existing search-bench runner) with the two things
8
+ * generation needs: a reproducible environment (`setup`) and an executable
9
+ * reference (`reference`) the certifier can re-run independently.
10
+ *
11
+ * Soundness contract (enforced by certify.ts, never by the authoring model):
12
+ * - grounding: the reference must execute and pass against the real pinned
13
+ * target named in `setup`.
14
+ * - discrimination: a no-tools parametric baseline must FAIL the oracle.
15
+ * An eval without a passed `certification` block is a CANDIDATE, not an eval.
16
+ */
17
+ import type { SearchTask } from '../search-bench/tasks'
18
+
19
+ export const generatedEvalSchemaVersion = 1
20
+
21
+ export interface EvalReference {
22
+ /** Files written into the certification workspace, path → content. */
23
+ files: Record<string, string>
24
+ /** Command that exercises the reference against the pinned target.
25
+ * Exit 0 ⇒ grounding gate passes. Run from the workspace root. */
26
+ cmd: string
27
+ /** Optional stdout requirement — every string must appear in the command's
28
+ * stdout. Guards against a reference that "passes" by doing nothing. */
29
+ stdoutContains?: string[]
30
+ }
31
+
32
+ export interface EvalCertification {
33
+ schemaVersion: number
34
+ /** Reference executed + passed against the pinned target. */
35
+ groundingPassed: boolean
36
+ /** No-tools baseline failed the oracle (the task discriminates). */
37
+ parametricFailed: boolean
38
+ /** Model used for the parametric gate. */
39
+ parametricModel: string
40
+ /** Exact target the reference ran against (e.g. hono@4.6.3). */
41
+ resolvedTarget: string
42
+ certifiedAt: string
43
+ /** Tool that performed certification (name@version). */
44
+ certifier: string
45
+ }
46
+
47
+ export interface GeneratedEval extends SearchTask {
48
+ schemaVersion: number
49
+ /** Shell commands that provision a CLEAN workspace with the pinned target
50
+ * (e.g. ["npm init -y", "npm i hono@4.6.3"]). Re-run by the certifier from
51
+ * scratch — the authoring agent's own workspace is never trusted. */
52
+ setup: string[]
53
+ reference: EvalReference
54
+ /** Present + both-gates-true ⇔ admitted. */
55
+ certification?: EvalCertification
56
+ }
57
+
58
+ /** Parse + structurally validate a candidate emitted by an authoring agent.
59
+ * Fail-loud: a malformed candidate is a generation failure, not a skip. */
60
+ export function parseCandidate(raw: string): GeneratedEval {
61
+ const v = JSON.parse(raw) as Partial<GeneratedEval>
62
+ const problems: string[] = []
63
+ if (v.schemaVersion !== generatedEvalSchemaVersion)
64
+ problems.push(`schemaVersion must be ${generatedEvalSchemaVersion}`)
65
+ if (!v.id || !/^[a-z0-9][a-z0-9-]*$/.test(v.id)) problems.push('id must be kebab-case')
66
+ if (!v.domain) problems.push('missing domain')
67
+ if (!v.prompt || v.prompt.length < 40) problems.push('prompt missing or too short')
68
+ if (!v.needsFreshDocs) problems.push('missing needsFreshDocs')
69
+ const o = v.oracle
70
+ if (!o || !(o.containsAll?.length || o.containsAny?.length || o.regex?.length))
71
+ problems.push('oracle needs containsAll, containsAny, or regex')
72
+ if (!v.setup?.length) problems.push('setup must pin the target')
73
+ if (!v.reference?.cmd || !v.reference.files || Object.keys(v.reference.files).length === 0)
74
+ problems.push('reference needs files + cmd')
75
+ if (!v.sourceUrl) problems.push('missing sourceUrl (primary-doc provenance)')
76
+ if (problems.length) throw new Error(`invalid candidate: ${problems.join('; ')}`)
77
+ return v as GeneratedEval
78
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * HumanEval deployable-verifier gate — the thesis test the answer-oracle benches
3
+ * (aec-bench, finsearch) could not reach.
4
+ *
5
+ * The repo's deployable self-consistency selector LOSES on both aec-bench (−9.4pp)
6
+ * and finsearch (−8.2pp): diversity opens a large ORACLE ceiling, but the selector
7
+ * cannot capture it because those benches verify by RECOMPUTING the gold answer (an
8
+ * oracle, not a deployable check). Code+tests is the deployable-checker regime — the
9
+ * agent runs the task's provided tests (which it legitimately has in production) and
10
+ * keeps a passer. This file asks: at EQUAL k, does diverse@k + a deployable
11
+ * verifier-grounded pick beat random@k + the same pick, and beat blind@1?
12
+ *
13
+ * SCOPE — read the numbers as a LOWER BOUND. Here a "shot" is a single STATELESS
14
+ * completion (one router call, `maxTurns=0`, NO `AgentProfile` / sandbox / keystone —
15
+ * it calls the router directly). That is the *degenerate* rollout (HARNESS.md's
16
+ * "Terminology"): it isolates the SELECTOR with the generator unable to self-correct,
17
+ * so it measures the selector's value at its MAXIMUM. A real rollout (an `AgentProfile`
18
+ * through `runLoop`, `maxTurns>0` over a persistent workspace) self-verifies by
19
+ * iterating, which shrinks the external selector's job — that is the next experiment,
20
+ * not this one. A positive result here is the science (the selector works in a
21
+ * deployable-checker regime), not the product.
22
+ *
23
+ * Two paired arms over the SAME tasks (each "shot" = one stateless completion):
24
+ * random@K — K identical-base-prompt completions/task (the compute control)
25
+ * diverse@K — K completions, the i-th prefixed with composeStrategies(base, K)[i]
26
+ *
27
+ * The DEPLOYABLE CHECKER runs each candidate against the task's own `test` in an
28
+ * isolated `--network=none` python:3.12-slim container (hard timeout) — exit 0 = pass.
29
+ * No gold `canonical_solution` is ever shown to the model or the selector.
30
+ *
31
+ * Metrics (paired across the same tasks):
32
+ * blind pass@1 — first attempt passes
33
+ * random-pass@k — verifierGroundedSelect over the K random shots passes
34
+ * diverse-pass@k — verifierGroundedSelect over the K diverse shots passes
35
+ * oracle@k — any of the K passes (the ceiling)
36
+ * self-consistency@k — selfConsistencySelect (answer-clustering, NOT the checker)
37
+ * over the diverse shots — the direct contrast with the
38
+ * −8/−9pp answer-oracle selector.
39
+ * Each delta carries a 95% paired-bootstrap CI.
40
+ *
41
+ * N=20 K=4 npx tsx src/humaneval-gate.mts
42
+ */
43
+
44
+ import { composeStrategies } from './directives'
45
+ import { basePrompt, type CheckResult, extractCode, type HumanEvalTask, loadHumanEval, runChecker } from './benchmarks/humaneval'
46
+ import { type RouterConfig, routerChatWithUsage } from '@tangle-network/agent-runtime/loops'
47
+ import { selfConsistencySelect, verifierGroundedSelect } from './selector'
48
+ import { type PairedLift, pairedLift, pool } from './stats.mts'
49
+
50
+ const dockerImage = 'python:3.12-slim'
51
+ const dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)
52
+
53
+ function must(name: string): string {
54
+ const v = process.env[name]
55
+ if (!v) throw new Error(`env ${name} is required`)
56
+ return v
57
+ }
58
+
59
+ interface Attempt {
60
+ code: string
61
+ pass: number
62
+ }
63
+
64
+ interface TaskOutcome {
65
+ taskId: string
66
+ randomAttempts: Attempt[]
67
+ diverseAttempts: Attempt[]
68
+ }
69
+
70
+ const pct = (x: number) => `${(x * 100).toFixed(1)}%`
71
+ const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
72
+
73
+ async function main(): Promise<void> {
74
+ const n = Number(process.env.N ?? 20)
75
+ const k = Number(process.env.K ?? 4)
76
+ const offset = Number(process.env.OFFSET ?? 0)
77
+ const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
78
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
79
+ const routerKey = must('TANGLE_API_KEY')
80
+ const solveConcurrency = Number(process.env.CONCURRENCY ?? 8)
81
+ const dockerConcurrency = Number(process.env.DOCKER_CONCURRENCY ?? 6)
82
+
83
+ if (!Number.isInteger(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
84
+ if (!Number.isInteger(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
85
+ if (!Number.isInteger(offset) || offset < 0) throw new Error(`OFFSET must be a non-negative integer, got ${process.env.OFFSET}`)
86
+
87
+ const cfg: RouterConfig = { routerBaseUrl, routerKey, model }
88
+
89
+ console.log(`=== HumanEval deployable-verifier gate · N=${n} K=${k} offset=${offset} model=${model} ===`)
90
+ console.log(` router=${routerBaseUrl} docker=${dockerImage} (--network=none, timeout ${dockerTimeoutMs}ms)`)
91
+ console.log(
92
+ ' regime: STATELESS single completions (maxTurns=0, no AgentProfile/sandbox) — the selector no-self-correction LOWER BOUND, not a rollout/product number',
93
+ )
94
+
95
+ const tasks = await loadHumanEval(n, offset)
96
+ console.log(`loaded ${tasks.length} HumanEval task(s): ${tasks.map((t) => t.taskId).join(', ')}`)
97
+
98
+ // Build the full work set: per task, K random + K diverse solve calls.
99
+ type Unit = { taskIdx: number; arm: 'random' | 'diverse'; shot: number; prompt: string }
100
+ const units: Unit[] = []
101
+ for (let ti = 0; ti < tasks.length; ti += 1) {
102
+ const task = tasks[ti] as HumanEvalTask
103
+ const base = basePrompt(task)
104
+ const diverse = composeStrategies(base, k)
105
+ for (let s = 0; s < k; s += 1) {
106
+ units.push({ taskIdx: ti, arm: 'random', shot: s, prompt: base })
107
+ units.push({ taskIdx: ti, arm: 'diverse', shot: s, prompt: diverse[s] as string })
108
+ }
109
+ }
110
+ console.log(`\n▶ solving ${units.length} attempts (${tasks.length} tasks × ${k} shots × 2 arms) via router, conc=${solveConcurrency}`)
111
+
112
+ const codes = await pool(units, solveConcurrency, async (u) => {
113
+ const res = await routerChatWithUsage(cfg, [{ role: 'user', content: u.prompt }], {
114
+ temperature: Number(process.env.TEMPERATURE ?? '0.8'),
115
+ })
116
+ return extractCode(typeof res.content === 'string' ? res.content : '')
117
+ })
118
+
119
+ console.log(`▶ running ${codes.length} candidates through the Docker deployable checker, conc=${dockerConcurrency}`)
120
+ const passes = await pool(units, dockerConcurrency, (u, i) => runChecker(tasks[u.taskIdx] as HumanEvalTask, codes[i] as string))
121
+
122
+ // Regroup into per-task arms, preserving shot order.
123
+ const outcomes: TaskOutcome[] = tasks.map((t) => ({ taskId: t.taskId, randomAttempts: [], diverseAttempts: [] }))
124
+ units.forEach((u, i) => {
125
+ const att: Attempt = { code: codes[i] as string, pass: (passes[i] as CheckResult).pass }
126
+ const o = outcomes[u.taskIdx] as TaskOutcome
127
+ if (u.arm === 'random') o.randomAttempts[u.shot] = att
128
+ else o.diverseAttempts[u.shot] = att
129
+ })
130
+
131
+ // Per-task {0,1} outcomes for each metric, aligned across the same tasks.
132
+ const blind: number[] = []
133
+ const randomAtK: number[] = []
134
+ const diverseAtK: number[] = []
135
+ const oracleAtK: number[] = []
136
+ const selfConsistencyAtK: number[] = []
137
+
138
+ for (const o of outcomes) {
139
+ const rPasses = o.randomAttempts.map((a) => a.pass)
140
+ const dPasses = o.diverseAttempts.map((a) => a.pass)
141
+ // blind = first random shot (one-shot baseline)
142
+ blind.push((o.randomAttempts[0] as Attempt).pass)
143
+ // random@k = verifier-grounded pick over the K random shots
144
+ randomAtK.push((o.randomAttempts[verifierGroundedSelect(rPasses)] as Attempt).pass)
145
+ // diverse@k = verifier-grounded pick over the K diverse shots
146
+ diverseAtK.push((o.diverseAttempts[verifierGroundedSelect(dPasses)] as Attempt).pass)
147
+ // oracle@k = any diverse shot passes (the ceiling the diverse arm opens)
148
+ oracleAtK.push(dPasses.some((p) => p > 0) ? 1 : 0)
149
+ // self-consistency@k = answer-clustering pick over the diverse shots (the
150
+ // −8/−9pp answer-oracle selector; uses code text, NOT the checker).
151
+ const scIdx = selfConsistencySelect(o.diverseAttempts.map((a) => a.code))
152
+ selfConsistencyAtK.push((o.diverseAttempts[scIdx] as Attempt).pass)
153
+ }
154
+
155
+ const rate = (xs: number[]) => xs.reduce((s, x) => s + x, 0) / xs.length
156
+
157
+ const blindRate = rate(blind)
158
+ const randomRate = rate(randomAtK)
159
+ const diverseRate = rate(diverseAtK)
160
+ const oracleRate = rate(oracleAtK)
161
+ const scRate = rate(selfConsistencyAtK)
162
+ // oracle ceiling of the RANDOM arm — for the diverse-vs-random ceiling contrast.
163
+ const randomOracleRate = rate(outcomes.map((o) => (o.randomAttempts.some((a) => a.pass > 0) ? 1 : 0)))
164
+
165
+ console.log(`\n${'='.repeat(78)}`)
166
+ console.log(`RESULTS · HumanEval · n=${tasks.length} tasks · k=${k} · model=${model}`)
167
+ console.log('='.repeat(78))
168
+ console.log(` blind pass@1 ${pct(blindRate)}`)
169
+ console.log(` random-pass@k (verifier) ${pct(randomRate)}`)
170
+ console.log(` diverse-pass@k (verifier) ${pct(diverseRate)}`)
171
+ console.log(` oracle@k (diverse, any) ${pct(oracleRate)} [random-arm oracle ${pct(randomOracleRate)}]`)
172
+ console.log(` self-consistency@k ${pct(scRate)} (answer-clustering selector over the diverse set)`)
173
+
174
+ const liftDiverseVsRandom = pairedLift(randomAtK, diverseAtK)
175
+ const liftRandomVsBlind = pairedLift(blind, randomAtK)
176
+ const liftDiverseVsBlind = pairedLift(blind, diverseAtK)
177
+ const liftScVsRandom = pairedLift(randomAtK, selfConsistencyAtK)
178
+ const liftDiverseVsScVerifier = pairedLift(selfConsistencyAtK, diverseAtK)
179
+
180
+ const row = (label: string, l: PairedLift) =>
181
+ console.log(
182
+ ` ${label.padEnd(34)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] (paired ${l.pairs}, discordant ${l.discordant})`,
183
+ )
184
+
185
+ console.log(`\n PAIRED LIFTS (95% bootstrap CI, B=10000):`)
186
+ row('diverse@k − random@k (verifier)', liftDiverseVsRandom)
187
+ row('random@k − blind (compute)', liftRandomVsBlind)
188
+ row('diverse@k − blind (total)', liftDiverseVsBlind)
189
+ row('self-consistency@k − random@k', liftScVsRandom)
190
+ row('verifier-pick − sc-pick (diverse)', liftDiverseVsScVerifier)
191
+
192
+ const sig = (l: PairedLift) => (l.low > 0 ? 'POSITIVE (CI excludes 0)' : l.high < 0 ? 'NEGATIVE (CI excludes 0)' : 'n.s. (CI spans 0)')
193
+ console.log(`\n VERDICT:`)
194
+ console.log(` diverse@k beats blind@1? ${liftDiverseVsBlind.point > 0 ? 'yes' : 'no'} (${pp(liftDiverseVsBlind.point)}, ${sig(liftDiverseVsBlind)})`)
195
+ console.log(` diverse@k beats random@k @k? ${liftDiverseVsRandom.point > 0 ? 'yes' : 'no'} (${pp(liftDiverseVsRandom.point)}, ${sig(liftDiverseVsRandom)})`)
196
+ console.log(` is the diversity ceiling capturable with a deployable checker?`)
197
+ console.log(` diverse oracle ${pct(oracleRate)} → verifier-pick ${pct(diverseRate)} (gap ${pp(oracleRate - diverseRate)});`)
198
+ console.log(` contrast: the SAME diverse set under the answer-clustering selector resolves ${pct(scRate)} (verifier−sc ${pp(diverseRate - scRate)}).`)
199
+ }
200
+
201
+ main().catch((err) => {
202
+ console.error(`humaneval-gate: ${err instanceof Error ? err.message : String(err)}`)
203
+ process.exit(1)
204
+ })
@@ -0,0 +1,143 @@
1
+ /**
2
+ * HumanEval self-repair gate — the TOOL-USING router backend vs blind resampling
3
+ * at equal compute, the strongest form of the steering question.
4
+ *
5
+ * The earlier steering gate (the rsi analyst arm) used an LLM that AUDITED the
6
+ * prior code WITHOUT running it — and was a null (−1.2pp, n.s.). This removes that
7
+ * weakness: the worker gets a `run_tests` tool (the deployable Docker checker), so
8
+ * it actually RUNS the tests, sees the real failure, and fixes — execution-grounded
9
+ * self-repair, off-box over the Tangle router's tool-calling (no sandbox). If
10
+ * steering ever beats compute on a deployable checker, this is where it should.
11
+ *
12
+ * blind@K — K independent completions, verifier-grounded pick (breadth/resample)
13
+ * repair@K — ONE worker, up to K tool-turns: write → run_tests → fix → … (depth/tool-grounded)
14
+ *
15
+ * Equal budget: one inference turn = one router completion, so both arms spend ≤K
16
+ * completions. Both finals are judged by the SAME check() suite. Per-task {0,1}
17
+ * outcomes, paired 95% bootstrap CI (discordant pairs = the power).
18
+ *
19
+ * TANGLE_API_KEY=… N=82 K=3 OFFSET=82 WORKER_MODEL=gpt-3.5-turbo \
20
+ * tsx src/humaneval-repair-gate.mts
21
+ */
22
+ import { type HumanEvalTask, basePrompt, extractCode, loadHumanEval, runChecker } from './benchmarks/humaneval'
23
+ import { type RouterConfig, type ToolSpec, routerChatWithUsage, routerToolLoop } from '@tangle-network/agent-runtime/loops'
24
+ import { verifierGroundedSelect } from './selector'
25
+ import { type PairedLift, pairedLift, pool } from './stats.mts'
26
+
27
+ function must(name: string): string {
28
+ const v = process.env[name]
29
+ if (!v) throw new Error(`env ${name} is required`)
30
+ return v
31
+ }
32
+
33
+ const runTestsTool: ToolSpec = {
34
+ type: 'function',
35
+ function: {
36
+ name: 'run_tests',
37
+ description:
38
+ "Run the task's test suite against your candidate function and return PASS or the real failure output. Verify with this before giving your final answer.",
39
+ parameters: {
40
+ type: 'object',
41
+ properties: { code: { type: 'string', description: 'The COMPLETE Python function definition to test (signature + body, plus any imports).' } },
42
+ required: ['code'],
43
+ },
44
+ },
45
+ }
46
+
47
+ const repairSystem = [
48
+ 'You complete a Python function. You have a run_tests tool that runs the REAL test suite against your code.',
49
+ 'Workflow: write the function, call run_tests to check it, and if it fails read the error and fix the function, then call run_tests again.',
50
+ 'When run_tests reports all tests passed, reply with the final function in a single ```python block and do NOT call the tool again.',
51
+ ].join(' ')
52
+
53
+ /** repair@K: one worker, up to K inference turns, steering on real test failures. */
54
+ async function repairAttempt(cfg: RouterConfig, task: HumanEvalTask, k: number): Promise<number> {
55
+ let lastTested = ''
56
+ const r = await routerToolLoop(
57
+ cfg,
58
+ repairSystem,
59
+ basePrompt(task),
60
+ [runTestsTool],
61
+ async (name, args) => {
62
+ if (name !== 'run_tests') return `error: unknown tool ${name}`
63
+ const code = extractCode(String(args.code ?? ''))
64
+ lastTested = code
65
+ const res = await runChecker(task, code)
66
+ return res.pass === 1
67
+ ? 'ALL TESTS PASSED. Reply with the final function now; do not call run_tests again.'
68
+ : `TESTS FAILED:\n${res.detail ?? 'no output'}\n\nFix the function and call run_tests again.`
69
+ },
70
+ { maxTurns: k, temperature: 0.3 },
71
+ )
72
+ // Judge the model's final answer; fall back to the last code it tested (it may
73
+ // report "done" without re-pasting the passing function).
74
+ const finalCode = extractCode(r.final) || lastTested
75
+ if (!finalCode) return 0
76
+ return (await runChecker(task, finalCode)).pass
77
+ }
78
+
79
+ /** blind@K: K independent completions, verifier-grounded pick (the resample control). */
80
+ async function blindAttempts(cfg: RouterConfig, task: HumanEvalTask, k: number): Promise<number[]> {
81
+ const base = basePrompt(task)
82
+ const passes: number[] = []
83
+ for (let i = 0; i < k; i += 1) {
84
+ const res = await routerChatWithUsage(cfg, [{ role: 'user', content: base }], { temperature: 0.8 })
85
+ passes.push((await runChecker(task, extractCode(res.content))).pass)
86
+ }
87
+ return passes
88
+ }
89
+
90
+ const pct = (x: number) => `${(x * 100).toFixed(1)}%`
91
+ const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
92
+
93
+ async function main(): Promise<void> {
94
+ const n = Number(process.env.N ?? 82)
95
+ const k = Number(process.env.K ?? 3)
96
+ const offset = Number(process.env.OFFSET ?? 82)
97
+ const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
98
+ const cfg: RouterConfig = { routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1', routerKey: must('TANGLE_API_KEY'), model }
99
+ const concurrency = Number(process.env.CONCURRENCY ?? 6)
100
+ if (k < 2) throw new Error('K must be >= 2 (repair needs at least write + one fix)')
101
+
102
+ console.log(`=== HumanEval self-repair gate · tool-using router worker · N=${n} K=${k} offset=${offset} model=${model} ===`)
103
+ const tasks = await loadHumanEval(n, offset)
104
+ console.log(`loaded ${tasks.length} task(s); running blind@${k} (resample) vs repair@${k} (run_tests-grounded), conc=${concurrency}\n`)
105
+
106
+ const rows = await pool(tasks, concurrency, async (task, i) => {
107
+ const blind = await blindAttempts(cfg, task, k)
108
+ const repair = await repairAttempt(cfg, task, k)
109
+ const blind1 = blind[0] ?? 0
110
+ const blindK = blind[verifierGroundedSelect(blind)] ?? 0
111
+ process.stderr.write(` [${i + 1}/${tasks.length}] ${task.taskId}: blind@1=${blind1} blind@${k}=${blindK} repair@${k}=${repair}\n`)
112
+ return { blind1, blindK, repair }
113
+ })
114
+
115
+ const blind1 = rows.map((r) => r.blind1)
116
+ const blindK = rows.map((r) => r.blindK)
117
+ const repairK = rows.map((r) => r.repair)
118
+ const rate = (xs: number[]) => xs.reduce((s, x) => s + x, 0) / xs.length
119
+
120
+ console.log(`\n${'='.repeat(74)}`)
121
+ console.log(`RESULTS · HumanEval self-repair · n=${tasks.length} · k=${k} · ${model}`)
122
+ console.log('='.repeat(74))
123
+ console.log(` blind pass@1 ${pct(rate(blind1))}`)
124
+ console.log(` blind@${k} (resample) ${pct(rate(blindK))}`)
125
+ console.log(` repair@${k} (tools) ${pct(rate(repairK))}`)
126
+
127
+ const row = (label: string, l: PairedLift) =>
128
+ console.log(` ${label.padEnd(34)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] (paired ${l.pairs}, discordant ${l.discordant})`)
129
+ const sig = (l: PairedLift) => (l.low > 0 ? 'SIGNIF +' : l.high < 0 ? 'SIGNIF -' : 'n.s. (CI spans 0)')
130
+
131
+ const repairVsBlind = pairedLift(blindK, repairK)
132
+ const computeVsBlind1 = pairedLift(blind1, blindK)
133
+ console.log(`\n PAIRED LIFTS (95% bootstrap CI, B=10000):`)
134
+ row(`repair@${k} − blind@${k} (steering)`, repairVsBlind)
135
+ row(`blind@${k} − blind@1 (more-compute)`, computeVsBlind1)
136
+ console.log(`\n VERDICT:`)
137
+ console.log(` execution-grounded self-repair beats blind resampling @ equal k? ${repairVsBlind.point > 0 ? 'yes' : 'no'} (${pp(repairVsBlind.point)}, ${sig(repairVsBlind)})`)
138
+ }
139
+
140
+ main().catch((err) => {
141
+ console.error(`humaneval-repair-gate: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`)
142
+ process.exit(1)
143
+ })
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@tangle-network/agent-bench` — the unified benchmark suite for agent-runtime agents.
3
+ *
4
+ * Any consumer (a product, a supervisor harness, a profile/skill/prompt change) imports the registry
5
+ * and scores its agent's artifact against a real, deterministic judge — without owning the benchmark:
6
+ *
7
+ * import { resolveAdapter } from '@tangle-network/agent-bench'
8
+ * const verdict = await resolveAdapter('commit0').judge(task, myAgentsDiff)
9
+ *
10
+ * Each adapter fails loud in `preflight()` when its harness/deps (Docker, a venv, a dataset) are
11
+ * absent, so importing the registry is cheap; running a specific benchmark pulls only its deps.
12
+ */
13
+ export { ADAPTERS, resolveAdapter } from './adapters'
14
+ export type {
15
+ BenchmarkAdapter,
16
+ BenchScore,
17
+ BenchTask,
18
+ LoadOptions,
19
+ } from './benchmarks/types'
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The critical de-risk for the real e2e: does an in-box opencode harness (via the cli-bridge)
3
+ * actually MOUNT my coordination MCP and CALL spawn_worker — landing on a real Scope.spawn?
4
+ *
5
+ * Serves the coordination MCP over a live Scope, then asks the bridge's opencode (with that MCP in
6
+ * its config) to call spawn_worker + await_event. If the Scope spawned+settled, the in-box driving
7
+ * path is real. No mock.
8
+ *
9
+ * ROUTER_BASE=http://127.0.0.1:3355/v1 TANGLE_API_KEY=<bridge-bearer> \
10
+ * WORKER_MODEL=opencode/zai-coding-plan/glm-5-turbo npx tsx bench/src/mcp-mount-probe.mts
11
+ */
12
+
13
+ import {
14
+ type Agent,
15
+ type AgentProfile,
16
+ type AgentSpec,
17
+ createExecutorRegistry,
18
+ createSupervisor,
19
+ type Executor,
20
+ type ExecutorResult,
21
+ InMemoryResultBlobStore,
22
+ InMemorySpawnJournal,
23
+ type Scope,
24
+ type UsageEvent,
25
+ } from '../../src/runtime/index'
26
+ import { serveCoordinationMcp } from '../../src/runtime/supervise/coordination-mcp'
27
+
28
+ const BRIDGE = process.env.ROUTER_BASE ?? 'http://127.0.0.1:3355/v1'
29
+ const BEARER = process.env.TANGLE_API_KEY ?? ''
30
+ const MODEL = process.env.WORKER_MODEL ?? 'opencode/zai-coding-plan/glm-5-turbo'
31
+
32
+ function deliveringLeaf(name: string, out: unknown): Agent<unknown, unknown> {
33
+ const ex: Executor<unknown> = {
34
+ runtime: 'router',
35
+ execute() {
36
+ return (async function* () {
37
+ yield { kind: 'iteration' } as UsageEvent
38
+ yield { kind: 'tokens', input: 5, output: 5 } as UsageEvent
39
+ })()
40
+ },
41
+ teardown: () => Promise.resolve({ destroyed: true }),
42
+ resultArtifact: (): ExecutorResult<unknown> => ({
43
+ outRef: `w:${name}`,
44
+ out,
45
+ verdict: { valid: true, score: 1 },
46
+ spent: { iterations: 1, tokens: { input: 5, output: 5 }, usd: 0, ms: 0 },
47
+ }),
48
+ }
49
+ const spec: AgentSpec = { profile: { name } as AgentProfile, harness: null, executor: ex }
50
+ return { name, act: async () => out, executorSpec: spec } as Agent<unknown, unknown> & {
51
+ executorSpec: AgentSpec
52
+ }
53
+ }
54
+
55
+ async function bridgeChat(messages: Array<{ role: string; content: string }>, mcpUrl: string): Promise<string> {
56
+ const r = await fetch(`${BRIDGE.replace(/\/$/, '')}/chat/completions`, {
57
+ method: 'POST',
58
+ headers: { authorization: `Bearer ${BEARER}`, 'content-type': 'application/json' },
59
+ body: JSON.stringify({
60
+ model: MODEL,
61
+ messages,
62
+ mcp: { mcpServers: { coordination: { type: 'http', url: mcpUrl } } },
63
+ }),
64
+ })
65
+ if (!r.ok) return `(bridge HTTP ${r.status}: ${(await r.text()).slice(0, 200)})`
66
+ const j = (await r.json()) as { choices?: Array<{ message?: { content?: string } }> }
67
+ return j.choices?.[0]?.message?.content ?? ''
68
+ }
69
+
70
+ async function main(): Promise<void> {
71
+ const blobs = new InMemoryResultBlobStore()
72
+ let mounted = false
73
+ const root: Agent<unknown, unknown> = {
74
+ name: 'mcp-mount-probe',
75
+ async act(_t, scope: Scope<unknown>) {
76
+ const mcp = await serveCoordinationMcp({
77
+ scope,
78
+ blobs,
79
+ makeWorkerAgent: () => deliveringLeaf('w', { ok: true }),
80
+ perWorker: { maxIterations: 4, maxTokens: 2000 },
81
+ })
82
+ console.error(`[probe] coordination MCP live at ${mcp.url}`)
83
+ try {
84
+ const content = await bridgeChat(
85
+ [
86
+ {
87
+ role: 'user',
88
+ content:
89
+ 'You have an MCP server named "coordination" with tools: spawn_worker, await_event, stop. ' +
90
+ 'Call spawn_worker with arguments {"profile":{},"task":"hello"}. Then call await_event. ' +
91
+ 'Then reply with exactly what await_event returned.',
92
+ },
93
+ ],
94
+ mcp.url,
95
+ )
96
+ const settled = mcp.settled()
97
+ mounted = settled.length > 0
98
+ console.error(`[probe] opencode replied: ${content.slice(0, 400)}`)
99
+ console.error(`[probe] Scope spawned+settled = ${settled.length}: ${JSON.stringify(settled)}`)
100
+ return mounted ? { mounted: true } : undefined
101
+ } finally {
102
+ await mcp.close()
103
+ }
104
+ },
105
+ }
106
+
107
+ const result = await createSupervisor<unknown, unknown>().run(root, 'probe', {
108
+ budget: { maxIterations: 100, maxTokens: 400_000 },
109
+ runId: 'mcp-probe',
110
+ journal: new InMemorySpawnJournal(),
111
+ blobs,
112
+ executors: createExecutorRegistry(),
113
+ maxDepth: 4,
114
+ now: () => Date.now(),
115
+ })
116
+ console.log(
117
+ mounted
118
+ ? '✅ MCP MOUNT WORKS — the in-box opencode harness called spawn_worker → real Scope.spawn'
119
+ : `❌ opencode did NOT call spawn_worker (result=${result.kind}) — protocol/mount needs work`,
120
+ )
121
+ }
122
+
123
+ main().catch((e) => {
124
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
125
+ process.exit(1)
126
+ })