@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
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # agent-runtime-bench
2
+
3
+ Private experiment workspace nested in agent-runtime; decoupled from its build/lint/release (the package builds `src/`, lints `src tests examples` — `bench/` is none of those).
4
+
5
+ **Read [`bench/HARNESS.md`](./HARNESS.md) FIRST.** It is the one maintained map: the commands, the `rollout → corpus → selector → CI → gate` data flow, the canonical-suite table, the wired/needs-creds/scaffolded matrix, and the gate one-liners — kept verified against source.
6
+
7
+ ## SWE-bench judge setup (the one block not in HARNESS.md)
8
+
9
+ ```bash
10
+ python3 -m venv .venv && .venv/bin/pip install swebench # SWE-bench harness
11
+ pnpm install # tsx + link parent
12
+ # Docker daemon must be running (judges build/run per-instance images)
13
+ ```
14
+
15
+ The judge needs only Docker; workers need a model key (Tangle router `TANGLE_API_KEY`, or a direct provider).
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@tangle-network/agent-bench",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "The unified benchmark suite for agent-runtime agents: 18 adapters (commit0, enterpriseops-gym, trata-hedge, finsearchcomp, swe-bench, humaneval, …) behind one resolveAdapter registry, each with a real deterministic judge. Score any profile/skill/prompt change against them. Map: bench/HARNESS.md.",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./adapters": "./src/adapters.ts",
11
+ "./benchmarks/*": "./src/benchmarks/*.ts"
12
+ },
13
+ "scripts": {
14
+ "gate": "tsx src/corpus-replay.mts corpus/finsearch.jsonl --selector",
15
+ "gate-cli": "tsx src/gate-cli.mts",
16
+ "gate-report": "tsx src/corpus-report.mts corpus/finsearch.jsonl",
17
+ "terminal-compare": "tsx src/terminal-compare.ts"
18
+ },
19
+ "dependencies": {
20
+ "@tangle-network/agent-eval": "^0.100.0",
21
+ "@tangle-network/agent-runtime": "^0.78.0",
22
+ "@tangle-network/sandbox": "^0.9.3"
23
+ },
24
+ "devDependencies": {
25
+ "tsx": "^4.19.0",
26
+ "typescript": "^6.0.3"
27
+ },
28
+ "files": [
29
+ "src",
30
+ "README.md"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The benchmark registry — the single source of truth for every wired benchmark.
3
+ * One key per benchmark; the value is its `BenchmarkAdapter` factory. `runBenchmarks`
4
+ * (the unifier) maps over this; `run.ts`, `rsi.ts`, and `corpus-replay.mts` all read it
5
+ * here rather than each keeping their own copy.
6
+ */
7
+
8
+ import { createAecBenchAdapter } from './benchmarks/aec-bench'
9
+ import { createAppWorldAdapter, createAppWorldReactAdapter } from './benchmarks/appworld'
10
+ import { createCadBenchAdapter } from './benchmarks/cadbench'
11
+ import { createCadDesignAdapter } from './benchmarks/cad-design'
12
+ import { createCadGenBenchAdapter } from './benchmarks/cadgenbench'
13
+ import { createCommit0Adapter } from './benchmarks/commit0'
14
+ import { createEnterpriseOpsGymAdapter } from './benchmarks/enterpriseops-gym'
15
+ import { createFinsearchcompAdapter } from './benchmarks/finsearchcomp'
16
+ import { createFramesAdapter } from './benchmarks/frames'
17
+ import { createHotpotqaAdapter } from './benchmarks/hotpotqa'
18
+ import { createHumanEvalAdapter } from './benchmarks/humaneval'
19
+ import { createMind2WebAdapter } from './benchmarks/mind2web'
20
+ import { createProgrambenchAdapter } from './benchmarks/programbench'
21
+ import { createSimpleQaAdapter } from './benchmarks/simpleqa'
22
+ import { createSweBenchAdapter } from './benchmarks/swe-bench'
23
+ import { createTerminalBenchAdapter } from './benchmarks/terminal-bench'
24
+ import { createTrataHedgeAdapter } from './benchmarks/trata-hedge'
25
+ import type { BenchmarkAdapter } from './benchmarks/types'
26
+
27
+ export const ADAPTERS: Record<string, () => BenchmarkAdapter> = {
28
+ 'swe-bench': createSweBenchAdapter,
29
+ 'terminal-bench': createTerminalBenchAdapter,
30
+ // Code-benches sharing ./benchmarks/_harness (stage → external evaluator → report).
31
+ // loadTasks fetches the REAL dataset (committed fixtures fallback offline); judge
32
+ // delegates to the benchmark's own harness and fails loud when it/Docker is absent.
33
+ 'aec-bench': createAecBenchAdapter,
34
+ commit0: createCommit0Adapter,
35
+ programbench: createProgrambenchAdapter,
36
+ appworld: createAppWorldAdapter,
37
+ // AppWorld's native interactive protocol — the worker is the in-engine ReAct
38
+ // episode (execution feedback every turn), the mode published baselines use.
39
+ 'appworld-react': createAppWorldReactAdapter,
40
+ 'enterpriseops-gym': createEnterpriseOpsGymAdapter,
41
+ 'cad-design': createCadDesignAdapter,
42
+ cadbench: createCadBenchAdapter,
43
+ cadgenbench: createCadGenBenchAdapter,
44
+ frames: createFramesAdapter,
45
+ finsearchcomp: createFinsearchcompAdapter,
46
+ simpleqa: createSimpleQaAdapter,
47
+ hotpotqa: createHotpotqaAdapter,
48
+ // Deployable-checker code domain: worker = router completion, judge = Docker test
49
+ // run (--network=none). The steering A/B counterpart to humaneval-gate.mts (selection).
50
+ humaneval: createHumanEvalAdapter,
51
+ mind2web: createMind2WebAdapter,
52
+ 'trata-hedge': createTrataHedgeAdapter,
53
+ }
54
+
55
+ /** Resolve a benchmark key to its adapter, failing loud with the known keys. */
56
+ export function resolveAdapter(key: string): BenchmarkAdapter {
57
+ const make = ADAPTERS[key]
58
+ if (!make) throw new Error(`unknown benchmark ${JSON.stringify(key)} (have: ${Object.keys(ADAPTERS).join(', ')})`)
59
+ return make()
60
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Router-based gate runner for aec-bench — the fix for the null-score sandbox path.
3
+ *
4
+ * aec-bench is closed-form reasoning + a deterministic local verify.py judge, so a
5
+ * sandbox is unnecessary: solve each task with one direct router chat call, then
6
+ * judge the raw response locally (verify.py extracts the last fenced ```json block
7
+ * itself). The prior sandbox path emitted the JSON in-stream but never fed it to
8
+ * the judge, so every verdict.score came out null — the bug this runner fixes by
9
+ * passing the model's full response straight to adapter.judge().
10
+ *
11
+ * Two paired arms over the SAME task set (loadTasks once):
12
+ * random@K — K identical-base-prompt shots/task (the compute control)
13
+ * diverse@K — K shots, the i-th prefixed with composeStrategies(base, K)[i]
14
+ *
15
+ * Each attempt carries a REAL numeric verdict.score (from verify.py) + the output,
16
+ * written as a corpus RunRecord (condition random@K / diverse@K) the existing
17
+ * corpus-replay --selector + corpus-report consume unchanged. Fail loud on a router
18
+ * error — never a fabricated score.
19
+ */
20
+
21
+ import { resolveAdapter } from './adapters'
22
+ import type { BenchmarkAdapter, BenchTask } from './benchmarks/types'
23
+ import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
24
+ import { composeStrategies } from './directives'
25
+ import { type RouterConfig, routerChatWithUsage } from '@tangle-network/agent-runtime/loops'
26
+ import { pool } from './stats.mts'
27
+
28
+ function must(name: string): string {
29
+ const v = process.env[name]
30
+ if (!v) throw new Error(`env ${name} is required`)
31
+ return v
32
+ }
33
+
34
+ interface ArmSpec {
35
+ /** Corpus condition label the selector/report filter on (e.g. random@4). */
36
+ condition: string
37
+ /** Per-attempt prompt builder: the i-th of K shots for a task. */
38
+ promptFor(task: BenchTask, i: number, k: number): string
39
+ }
40
+
41
+ interface AttemptOutcome {
42
+ prompt: string
43
+ output: string
44
+ score: number
45
+ resolved: boolean
46
+ costUsd?: number
47
+ tokensIn?: number
48
+ tokensOut?: number
49
+ wallMs: number
50
+ /** the router/judge call failed after retries — EXCLUDED from stats, never scored 0. */
51
+ infraError?: boolean
52
+ }
53
+
54
+ async function runAttempt(
55
+ cfg: RouterConfig,
56
+ adapter: BenchmarkAdapter,
57
+ task: BenchTask,
58
+ prompt: string,
59
+ ): Promise<AttemptOutcome> {
60
+ const startedAt = Date.now()
61
+ // Retry transient router/judge failures (rate limits, stream drops, 5xx) with
62
+ // backoff; a genuine empty completion still scores a real 0 (verify.py fail-closes).
63
+ // Only after retries are exhausted do we record an EXCLUDED infraError — never a
64
+ // fabricated score, and never a throw that aborts the whole multi-model run.
65
+ let lastErr: unknown
66
+ for (let attempt = 0; attempt < 3; attempt += 1) {
67
+ try {
68
+ const res = await routerChatWithUsage(cfg, [{ role: 'user', content: prompt }])
69
+ const content = typeof res.content === 'string' ? res.content : ''
70
+ const verdict = await adapter.judge(task, content)
71
+ return {
72
+ prompt,
73
+ output: content,
74
+ score: verdict.score,
75
+ resolved: verdict.resolved,
76
+ wallMs: Date.now() - startedAt,
77
+ ...(res.costUsd !== undefined ? { costUsd: res.costUsd } : {}),
78
+ ...(res.usage ? { tokensIn: res.usage.input, tokensOut: res.usage.output } : {}),
79
+ }
80
+ } catch (err) {
81
+ lastErr = err
82
+ if (attempt < 2) await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt))
83
+ }
84
+ }
85
+ console.warn(`[aec-gate] ${task.id}: infra error after 3 tries — excluded: ${(lastErr instanceof Error ? lastErr.message : String(lastErr)).slice(0, 160)}`)
86
+ return { prompt, output: '', score: 0, resolved: false, wallMs: Date.now() - startedAt, infraError: true }
87
+ }
88
+
89
+ function toAttemptRecord(o: AttemptOutcome, round: number): AttemptRecord {
90
+ return {
91
+ round,
92
+ prompt: o.prompt,
93
+ output: o.output,
94
+ // infra-errored attempts carry NO score/valid → corpus-replay skips them.
95
+ ...(o.infraError ? {} : { valid: o.resolved, score: o.score }),
96
+ wallMs: o.wallMs,
97
+ eventCount: 1,
98
+ eventTypes: o.infraError ? { 'router.error': 1 } : { 'router.chat': 1 },
99
+ traceTail: o.output.slice(-600),
100
+ ...(o.costUsd !== undefined ? { costUsd: o.costUsd } : {}),
101
+ ...(o.tokensIn !== undefined ? { tokensIn: o.tokensIn } : {}),
102
+ ...(o.tokensOut !== undefined ? { tokensOut: o.tokensOut } : {}),
103
+ }
104
+ }
105
+
106
+ interface ArmResult {
107
+ /** mean graded score across SCORED (non-infra) attempts */
108
+ meanScore: number
109
+ /** fraction of scored attempts at full credit (score >= 1) */
110
+ fullCreditRate: number
111
+ attemptCount: number
112
+ /** attempts excluded as infra errors (router/judge failed after retries) */
113
+ infraCount: number
114
+ }
115
+
116
+ async function runArm(
117
+ arm: ArmSpec,
118
+ cfg: RouterConfig,
119
+ adapter: BenchmarkAdapter,
120
+ tasks: BenchTask[],
121
+ k: number,
122
+ concurrency: number,
123
+ corpusPath: string,
124
+ ): Promise<ArmResult> {
125
+ // Flatten (task, shot) into one unit of work so the pool bounds TOTAL in-flight
126
+ // router calls across all tasks, not per-task.
127
+ const units = tasks.flatMap((task) => Array.from({ length: k }, (_, i) => ({ task, i })))
128
+ const outcomes = await pool(units, concurrency, (u) => runAttempt(cfg, adapter, u.task, arm.promptFor(u.task, u.i, k)))
129
+
130
+ const scored = outcomes.filter((o) => !o.infraError)
131
+ let scoreSum = 0
132
+ let fullCredit = 0
133
+ for (const o of scored) {
134
+ scoreSum += o.score
135
+ if (o.score >= 1) fullCredit += 1
136
+ }
137
+
138
+ // Group K outcomes back per task → one RunRecord/task (the controller-run shape).
139
+ for (let t = 0; t < tasks.length; t += 1) {
140
+ const task = tasks[t] as BenchTask
141
+ const taskOutcomes = outcomes.slice(t * k, t * k + k)
142
+ const attempts = taskOutcomes.map((o, i) => toAttemptRecord(o, i))
143
+ const record = buildRunRecordFromAttempts(attempts, {
144
+ benchmark: adapter.name,
145
+ instanceId: task.id,
146
+ condition: arm.condition,
147
+ model: cfg.model,
148
+ // k-attempt outcome = any usable attempt resolved (the oracle@k ceiling for
149
+ // this run; the deployable selector is scored separately by corpus-replay).
150
+ resolved: taskOutcomes.some((o) => o.resolved),
151
+ // a task whose every attempt infra-errored is itself infra-errored.
152
+ infraError: taskOutcomes.length > 0 && taskOutcomes.every((o) => o.infraError),
153
+ })
154
+ await appendRunRecord(corpusPath, record)
155
+ }
156
+
157
+ return {
158
+ meanScore: scored.length > 0 ? scoreSum / scored.length : 0,
159
+ fullCreditRate: scored.length > 0 ? fullCredit / scored.length : 0,
160
+ infraCount: outcomes.length - scored.length,
161
+ attemptCount: outcomes.length,
162
+ }
163
+ }
164
+
165
+ async function main(): Promise<void> {
166
+ const n = Number(process.env.N ?? 5)
167
+ const k = Number(process.env.K ?? 4)
168
+ const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
169
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
170
+ const routerKey = must('TANGLE_API_KEY')
171
+ const concurrency = Number(process.env.CONCURRENCY ?? 6)
172
+ const randomCorpus = process.env.RANDOM_CORPUS ?? '/tmp/aec-r.jsonl'
173
+ const diverseCorpus = process.env.DIVERSE_CORPUS ?? '/tmp/aec-d.jsonl'
174
+
175
+ if (!Number.isFinite(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
176
+ if (!Number.isFinite(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
177
+
178
+ const cfg: RouterConfig = { routerBaseUrl, routerKey, model }
179
+ const bench = process.env.BENCH ?? 'aec-bench'
180
+ const adapter = resolveAdapter(bench)
181
+
182
+ console.log(`=== ${bench} router gate · N=${n} K=${k} model=${model} conc=${concurrency} ===`)
183
+ await adapter.preflight()
184
+ const tasks = await adapter.loadTasks({ limit: n })
185
+ console.log(`loaded ${tasks.length} task(s): ${tasks.map((t) => t.id).join(', ')}`)
186
+
187
+ // random arm: the task prompt verbatim, K times (the compute control).
188
+ const randomArm: ArmSpec = {
189
+ condition: `random@${k}`,
190
+ promptFor: (task) => task.prompt,
191
+ }
192
+ // diverse arm: the i-th shot prefixed with the i-th distinct strategy lens.
193
+ const diverseArm: ArmSpec = {
194
+ condition: `diverse@${k}`,
195
+ promptFor: (task, i, kk) => composeStrategies(task.prompt, kk)[i] as string,
196
+ }
197
+
198
+ console.log(`\n▶ random@${k} (control — identical base prompt) → ${randomCorpus}`)
199
+ const r = await runArm(randomArm, cfg, adapter, tasks, k, concurrency, randomCorpus)
200
+ console.log(` random@${k}: mean score ${(r.meanScore * 100).toFixed(1)}% full-credit ${(r.fullCreditRate * 100).toFixed(1)}% (n=${r.attemptCount} attempts${r.infraCount ? `, ${r.infraCount} infra-excluded` : ''})`)
201
+
202
+ console.log(`\n▶ diverse@${k} (K distinct strategy lenses) → ${diverseCorpus}`)
203
+ const d = await runArm(diverseArm, cfg, adapter, tasks, k, concurrency, diverseCorpus)
204
+ console.log(` diverse@${k}: mean score ${(d.meanScore * 100).toFixed(1)}% full-credit ${(d.fullCreditRate * 100).toFixed(1)}% (n=${d.attemptCount} attempts${d.infraCount ? `, ${d.infraCount} infra-excluded` : ''})`)
205
+
206
+ console.log(
207
+ `\n=== next: read the gate ===\n` +
208
+ ` npx tsx src/corpus-replay.mts ${randomCorpus} --selector\n` +
209
+ ` npx tsx src/corpus-replay.mts ${diverseCorpus} --selector --condition=diverse\n` +
210
+ ` npx tsx src/corpus-report.mts ${randomCorpus} ${diverseCorpus}`,
211
+ )
212
+ }
213
+
214
+ main().catch((err) => {
215
+ console.error(`aec-gate: ${err instanceof Error ? err.message : String(err)}`)
216
+ process.exit(1)
217
+ })
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The "useful or BS" verdict: agents-driving-agents on a REAL deployable-checked domain.
3
+ *
4
+ * A `driverAgent` with a REAL router-LLM brain drives, per HumanEval task: it spawns
5
+ * worker agents (each a router LLM that writes the function), every worker GATED by the
6
+ * deterministic local Docker checker (the deliverable — a worker settles `valid` ⟺ its tests
7
+ * pass), and the completion-oracle keeps-best a DELIVERED worker. The supervisor returns a winner
8
+ * ONLY when a worker actually passed the tests (no self-declared done). We measure the driver's
9
+ * delivered rate against a BLIND best-of-K baseline (K independent workers, no orchestration) at
10
+ * the same K — the honest "does the recursion+oracle beat blind compute, or is it BS" question.
11
+ *
12
+ * Run (creds via dotenvx; Docker daemon must be up):
13
+ * DOTENV_PRIVATE_KEY_FILE=~/company/devops/secrets/.env.keys \
14
+ * dotenvx run -f ~/company/devops/secrets/agent-state.env -- \
15
+ * N=5 K=3 WORKER_MODEL=deepseek-v4-flash DRIVER_MODEL=deepseek-v4-flash \
16
+ * npx tsx bench/src/atom-humaneval.mts
17
+ */
18
+
19
+ import {
20
+ type Agent,
21
+ type AgentProfile,
22
+ type AgentSpec,
23
+ contentAddress,
24
+ type DriverAgentOptions,
25
+ driverAgent,
26
+ createExecutorRegistry,
27
+ createSupervisor,
28
+ type Executor,
29
+ type ExecutorResult,
30
+ gateOnDeliverable,
31
+ InMemoryResultBlobStore,
32
+ InMemorySpawnJournal,
33
+ type RouterConfig,
34
+ routerBrain,
35
+ routerChatWithUsage,
36
+ } from '../../src/runtime/index'
37
+ import { basePrompt, extractCode, type HumanEvalTask, loadHumanEval, runChecker } from './benchmarks/humaneval'
38
+
39
+ function must(k: string): string {
40
+ const v = process.env[k]
41
+ if (!v) throw new Error(`missing required env ${k}`)
42
+ return v
43
+ }
44
+
45
+ const N = Number(process.env.N ?? 5)
46
+ const K = Number(process.env.K ?? 3)
47
+ const OFFSET = Number(process.env.OFFSET ?? 0)
48
+ const WORKER_TEMP = Number(process.env.WORKER_TEMP ?? 0.7)
49
+
50
+ const cfg: RouterConfig = {
51
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
52
+ routerKey: must('TANGLE_API_KEY'),
53
+ model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
54
+ }
55
+ const driverCfg: RouterConfig = { ...cfg, model: process.env.DRIVER_MODEL ?? cfg.model }
56
+
57
+ // The driver-LLM brain is the SHARED `routerBrain` (the canonical ToolLoopChat seam) — it forwards
58
+ // usage/costUsd, so this bench's driver arms meter their own inference into the conserved pool.
59
+
60
+ // ── A gated router worker: one router call → candidate code, settled valid ⟺ the tests pass ──
61
+ function humanEvalWorker(task: HumanEvalTask, label: string): Agent<unknown, unknown> {
62
+ let artifact: ExecutorResult<unknown> | undefined
63
+ const inner: Executor<unknown> = {
64
+ runtime: 'router',
65
+ async execute(_t, signal) {
66
+ const res = await routerChatWithUsage(cfg, [{ role: 'user', content: basePrompt(task) }], {
67
+ temperature: WORKER_TEMP,
68
+ ...(signal ? { signal } : {}),
69
+ })
70
+ const code = extractCode(res.content)
71
+ artifact = {
72
+ outRef: contentAddress(code),
73
+ out: code,
74
+ spent: { iterations: 1, tokens: res.usage ?? { input: 0, output: 0 }, usd: res.costUsd ?? 0, ms: 0 },
75
+ }
76
+ return artifact
77
+ },
78
+ teardown: () => Promise.resolve({ destroyed: true }),
79
+ resultArtifact: () => {
80
+ if (!artifact) throw new Error('resultArtifact read before execute')
81
+ return artifact
82
+ },
83
+ }
84
+ const gated = gateOnDeliverable(inner, {
85
+ check: async (out) => (await runChecker(task, String(out))).pass === 1,
86
+ describe: `${task.taskId}: the provided test suite passes`,
87
+ })
88
+ const spec: AgentSpec = { profile: { name: label } as AgentProfile, harness: null, executor: gated }
89
+ return { name: label, act: async () => '', executorSpec: spec } as Agent<unknown, unknown> & {
90
+ executorSpec: AgentSpec
91
+ }
92
+ }
93
+
94
+ const driverSystem = `You are an orchestrator driving worker agents to solve a Python coding task. You do NOT write code yourself. Each worker independently attempts the task and is graded by a deterministic, hidden test suite. Tools: spawn_worker (dispatch one attempt; the "profile" argument may be {} and "task" a short note), await_event (collect the next settled worker — its result tells you valid:true if its tests PASSED, valid:false if they failed), and stopping (reply with NO tool call) once a worker has DELIVERED. Spawn one worker, await it; if it delivered, stop; if not, spawn another, up to ${K} workers total. You cannot declare success yourself — only a delivered (valid:true) worker counts.`
95
+
96
+ interface TaskOutcome {
97
+ taskId: string
98
+ driverDelivered: boolean
99
+ blindDelivered: boolean
100
+ driverSpawns: number
101
+ driverWorkerTokens: number
102
+ }
103
+
104
+ // ── Driver arm: the orchestrated atom ────────────────────────────────────────────────────────
105
+ async function driveTask(
106
+ task: HumanEvalTask,
107
+ ): Promise<{ delivered: boolean; spawns: number; tokens: number }> {
108
+ const blobs = new InMemoryResultBlobStore()
109
+ const journal = new InMemorySpawnJournal()
110
+ let spawns = 0
111
+ const makeWorker = (): Agent<unknown, unknown> => {
112
+ const w = humanEvalWorker(task, `w-${spawns}`)
113
+ spawns += 1
114
+ return w
115
+ }
116
+ const opts: DriverAgentOptions = {
117
+ name: `drv-${task.taskId}`,
118
+ brain: routerBrain(driverCfg),
119
+ blobs,
120
+ makeWorkerAgent: makeWorker,
121
+ perWorker: { maxIterations: 2, maxTokens: 4000 },
122
+ systemPrompt: driverSystem,
123
+ maxTurns: K + 4,
124
+ }
125
+ const root = driverAgent(opts)
126
+ const runId = `he-${task.taskId.replace('/', '-')}`
127
+ const result = await createSupervisor<unknown, unknown>().run(root, basePrompt(task), {
128
+ budget: { maxIterations: 100, maxTokens: 400_000 },
129
+ runId,
130
+ journal,
131
+ blobs,
132
+ executors: createExecutorRegistry(),
133
+ maxDepth: 4,
134
+ now: () => Date.now(),
135
+ })
136
+ const tree = await journal.loadTree(runId)
137
+ const tokens = (tree ?? [])
138
+ .filter((e): e is Extract<NonNullable<typeof tree>[number], { kind: 'settled' }> => e.kind === 'settled')
139
+ .reduce((s, e) => s + e.spent.tokens.input + e.spent.tokens.output, 0)
140
+ return { delivered: result.kind === 'winner', spawns, tokens }
141
+ }
142
+
143
+ // ── Blind arm: K independent workers, best-of-K by the checker (no orchestration) ─────────────
144
+ async function blindTask(task: HumanEvalTask): Promise<boolean> {
145
+ for (let i = 0; i < K; i += 1) {
146
+ // A transient router error is a FAILED attempt, not a crash — the driver arm already types
147
+ // an executor throw into a `down` settlement, so the blind arm must match (fair comparison).
148
+ let res: { content: string }
149
+ try {
150
+ res = await routerChatWithUsage(cfg, [{ role: 'user', content: basePrompt(task) }], {
151
+ temperature: WORKER_TEMP,
152
+ })
153
+ } catch {
154
+ continue
155
+ }
156
+ if ((await runChecker(task, extractCode(res.content))).pass === 1) return true
157
+ }
158
+ return false
159
+ }
160
+
161
+ async function main(): Promise<void> {
162
+ console.log(`atom-humaneval: N=${N} K=${K} offset=${OFFSET} worker=${cfg.model} driver=${driverCfg.model}`)
163
+ const tasks = await loadHumanEval(N, OFFSET)
164
+ const outcomes: TaskOutcome[] = []
165
+ for (const task of tasks) {
166
+ const drv = await driveTask(task)
167
+ const blind = await blindTask(task)
168
+ outcomes.push({
169
+ taskId: task.taskId,
170
+ driverDelivered: drv.delivered,
171
+ blindDelivered: blind,
172
+ driverSpawns: drv.spawns,
173
+ driverWorkerTokens: drv.tokens,
174
+ })
175
+ console.log(
176
+ ` ${task.taskId.padEnd(14)} driver=${drv.delivered ? 'PASS' : 'fail'} (spawns=${drv.spawns}, tok=${drv.tokens}) blind@${K}=${blind ? 'PASS' : 'fail'}`,
177
+ )
178
+ }
179
+ const driverPass = outcomes.filter((o) => o.driverDelivered).length
180
+ const blindPass = outcomes.filter((o) => o.blindDelivered).length
181
+ const avgSpawns = outcomes.reduce((s, o) => s + o.driverSpawns, 0) / Math.max(1, outcomes.length)
182
+ console.log('\n── verdict ──')
183
+ console.log(`driver-orchestrated delivered: ${driverPass}/${outcomes.length} (avg spawns ${avgSpawns.toFixed(1)} of ${K} allowed)`)
184
+ console.log(`blind best-of-${K} delivered: ${blindPass}/${outcomes.length}`)
185
+ console.log(
186
+ driverPass > blindPass
187
+ ? `→ orchestration BEAT blind by +${driverPass - blindPass} tasks`
188
+ : driverPass === blindPass
189
+ ? `→ orchestration TIED blind (the atom delivers, but adds no lift here at this N)`
190
+ : `→ orchestration LOST to blind by ${blindPass - driverPass} tasks`,
191
+ )
192
+ }
193
+
194
+ main().catch((e) => {
195
+ console.error(e)
196
+ process.exit(1)
197
+ })