@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
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
|
+
}
|
package/src/adapters.ts
ADDED
|
@@ -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
|
+
}
|
package/src/aec-gate.mts
ADDED
|
@@ -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
|
+
})
|