@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.
- package/README.md +15 -0
- package/package.json +35 -0
- package/src/adapters.ts +60 -0
- package/src/aec-gate.mts +217 -0
- package/src/atom-humaneval.mts +197 -0
- package/src/atom-mcp-e2e.mts +223 -0
- package/src/benchmarks/_harness.ts +206 -0
- package/src/benchmarks/aec-bench.test.mts +53 -0
- package/src/benchmarks/aec-bench.ts +319 -0
- package/src/benchmarks/appworld.test.mts +45 -0
- package/src/benchmarks/appworld.ts +426 -0
- package/src/benchmarks/cad-design.ts +429 -0
- package/src/benchmarks/cadbench.ts +135 -0
- package/src/benchmarks/cadgenbench.ts +121 -0
- package/src/benchmarks/commit0.test.mts +71 -0
- package/src/benchmarks/commit0.ts +266 -0
- package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
- package/src/benchmarks/enterpriseops-gym.ts +343 -0
- package/src/benchmarks/finsearchcomp.ts +371 -0
- package/src/benchmarks/frames.ts +520 -0
- package/src/benchmarks/hotpotqa.ts +320 -0
- package/src/benchmarks/humaneval.ts +251 -0
- package/src/benchmarks/mind2web.ts +311 -0
- package/src/benchmarks/programbench.test.mts +60 -0
- package/src/benchmarks/programbench.ts +210 -0
- package/src/benchmarks/simpleqa.ts +390 -0
- package/src/benchmarks/swe-bench.ts +152 -0
- package/src/benchmarks/terminal-bench.ts +182 -0
- package/src/benchmarks/trata-hedge.ts +496 -0
- package/src/benchmarks/types.ts +58 -0
- package/src/browser/adapters/bad-design-audit.ts +153 -0
- package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
- package/src/browser/adapters/bad.ts +165 -0
- package/src/browser/agent-adapter.ts +145 -0
- package/src/browser/process-adapter.ts +146 -0
- package/src/browser/run-to-spans.ts +105 -0
- package/src/browser/run-to-spans.verify.ts +89 -0
- package/src/browser/ui-reviewer.ts +200 -0
- package/src/browser/ui-reviewer.verify.ts +94 -0
- package/src/browser/verify.ts +96 -0
- package/src/clbench-codebase-gate.mts +314 -0
- package/src/clbench-context-gate.mts +305 -0
- package/src/cloud-loop.mts +138 -0
- package/src/coding-skills/minimal-diff.md +9 -0
- package/src/coding-skills/read-before-edit.md +9 -0
- package/src/coding-skills/reproduce-first.md +10 -0
- package/src/coding-skills/run-tests-after-edit.md +9 -0
- package/src/coding-skills/trace-the-failure.md +9 -0
- package/src/commit0-env-run.mts +59 -0
- package/src/commit0-env.ts +173 -0
- package/src/commit0-gate.mts +529 -0
- package/src/commit0-prereqs.sh +48 -0
- package/src/corpus-replay.mts +300 -0
- package/src/corpus-report.mts +504 -0
- package/src/corpus.test.mts +273 -0
- package/src/corpus.ts +325 -0
- package/src/decoder-live.mts +133 -0
- package/src/directives.ts +84 -0
- package/src/diverse-gate.mjs +112 -0
- package/src/egress-probe.mts +26 -0
- package/src/eops-skills/address-every-subtask.md +5 -0
- package/src/eops-skills/exact-tools-and-args.md +5 -0
- package/src/eops-skills/full-sequence-to-goal.md +5 -0
- package/src/eops-skills/ground-every-value.md +5 -0
- package/src/eops-skills/honor-the-policies.md +5 -0
- package/src/examples/README.md +58 -0
- package/src/examples/math-demo.mts +110 -0
- package/src/examples/strategy-demo.mts +119 -0
- package/src/fleet.mts +121 -0
- package/src/gate-cli.mts +101 -0
- package/src/gate.test.mts +129 -0
- package/src/gate.ts +460 -0
- package/src/generate-eval/certify.ts +178 -0
- package/src/generate-eval/schema.ts +78 -0
- package/src/humaneval-gate.mts +204 -0
- package/src/humaneval-repair-gate.mts +143 -0
- package/src/index.ts +19 -0
- package/src/mcp-mount-probe.mts +126 -0
- package/src/profile-coordinates.ts +134 -0
- package/src/profiles.ts +128 -0
- package/src/refine-loop.test.mts +106 -0
- package/src/refine-loop.ts +106 -0
- package/src/research-gate.mts +132 -0
- package/src/research-shot.ts +134 -0
- package/src/resolve-client.ts +58 -0
- package/src/router-executor.ts +51 -0
- package/src/run-pool.ts +48 -0
- package/src/runtime-hook-recorder.ts +137 -0
- package/src/sandbox-run.ts +125 -0
- package/src/search-bench/bridge.ts +124 -0
- package/src/search-bench/export.mts +0 -0
- package/src/search-bench/parametric-check.mts +63 -0
- package/src/search-bench/profiles.ts +98 -0
- package/src/search-bench/run.mts +287 -0
- package/src/search-bench/tasks-fresh.ts +688 -0
- package/src/search-bench/tasks.ts +129 -0
- package/src/search-tool.ts +95 -0
- package/src/selector.test.mts +189 -0
- package/src/selector.ts +366 -0
- package/src/skill-sandbox-smoke.mts +100 -0
- package/src/stats.mts +90 -0
- package/src/terminal-compare.ts +519 -0
- package/src/trajectory-assemble.mjs +130 -0
- package/src/trata-gate.mts +243 -0
- package/src/trata-gepa.mts +434 -0
- package/src/worker-blender.ts +230 -0
- package/src/worker-browser.ts +102 -0
- package/src/worker-build123d.ts +143 -0
- package/src/worker-cad.ts +451 -0
- package/src/worker.ts +136 -0
- 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
|
+
})
|