@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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic self-check for the driver-agnostic browser harness — run with
|
|
3
|
+
* `tsx src/browser/verify.ts` (bench convention: a verify script, not a live run).
|
|
4
|
+
*
|
|
5
|
+
* The regression it defends: the verdict is ATTESTABLE — derived from the run's
|
|
6
|
+
* final observable state, NEVER the driver's self-reported success. A driver can
|
|
7
|
+
* claim success=true; if the deterministic criteria fail, resolved MUST be false.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { judgeBrowserRun, type BrowserTask } from './agent-adapter'
|
|
11
|
+
import { badReportToRun } from './adapters/bad'
|
|
12
|
+
|
|
13
|
+
let failures = 0
|
|
14
|
+
function check(name: string, cond: boolean, detail = '') {
|
|
15
|
+
if (cond) {
|
|
16
|
+
console.log(` ✓ ${name}`)
|
|
17
|
+
} else {
|
|
18
|
+
failures += 1
|
|
19
|
+
console.log(` ✗ ${name} ${detail}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A bad report.json shaped exactly like the real one, with the OpenSCAD page reached.
|
|
24
|
+
const successReport = {
|
|
25
|
+
results: [
|
|
26
|
+
{
|
|
27
|
+
testCase: { id: 'wiki-openscad' },
|
|
28
|
+
agentResult: {
|
|
29
|
+
success: true,
|
|
30
|
+
result: "The 'OpenSCAD' article is open on Wikipedia.",
|
|
31
|
+
turns: [
|
|
32
|
+
{ turn: 1, state: { url: 'https://en.wikipedia.org/wiki/Main_Page', snapshot: 'search box' }, action: { action: 'type', text: 'OpenSCAD' } },
|
|
33
|
+
{ turn: 2, state: { url: 'https://en.wikipedia.org/wiki/Main_Page' }, action: { action: 'click', selector: '@result' } },
|
|
34
|
+
{ turn: 3, state: { url: 'https://en.wikipedia.org/wiki/OpenSCAD', snapshot: 'OpenSCAD - Wikipedia. Constructive Solid Geometry modeller.' }, action: { action: 'complete' } },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The SAME driver claiming success, but the browser never left Main_Page.
|
|
42
|
+
const liarReport = {
|
|
43
|
+
results: [
|
|
44
|
+
{
|
|
45
|
+
testCase: { id: 'wiki-openscad' },
|
|
46
|
+
agentResult: {
|
|
47
|
+
success: true, // self-reported — must be IGNORED
|
|
48
|
+
result: 'Done!',
|
|
49
|
+
turns: [{ turn: 1, state: { url: 'https://en.wikipedia.org/wiki/Main_Page', snapshot: 'main page' }, action: { action: 'complete' } }],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const task: BrowserTask = {
|
|
56
|
+
id: 'wiki-openscad',
|
|
57
|
+
goal: 'Open the OpenSCAD article',
|
|
58
|
+
startUrl: 'https://en.wikipedia.org',
|
|
59
|
+
success: [{ type: 'url-contains', value: 'OpenSCAD' }],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('browser harness verify:')
|
|
63
|
+
|
|
64
|
+
const okRun = badReportToRun(successReport, 'wiki-openscad')
|
|
65
|
+
check('mapper extracts final url from last turn', okRun.finalUrl === 'https://en.wikipedia.org/wiki/OpenSCAD', okRun.finalUrl)
|
|
66
|
+
check('mapper records all steps', okRun.steps.length === 3)
|
|
67
|
+
check('mapper carries selfReportedSuccess', okRun.selfReportedSuccess === true)
|
|
68
|
+
const okV = judgeBrowserRun(task, okRun)
|
|
69
|
+
check('genuine success attests resolved=true score=1', okV.resolved === true && okV.score === 1, okV.detail)
|
|
70
|
+
|
|
71
|
+
const liarRun = badReportToRun(liarReport, 'wiki-openscad')
|
|
72
|
+
const liarV = judgeBrowserRun(task, liarRun)
|
|
73
|
+
check('SELF-REPORTED success is IGNORED — failed criteria → resolved=false', liarV.resolved === false && liarV.score === 0, liarV.detail)
|
|
74
|
+
|
|
75
|
+
// Partial credit + dom check: 2 criteria, only url passes (snapshot lacks the phrase).
|
|
76
|
+
const twoCrit: BrowserTask = {
|
|
77
|
+
...task,
|
|
78
|
+
success: [{ type: 'url-contains', value: 'OpenSCAD' }, { type: 'dom-contains', value: 'Marching Cubes' }],
|
|
79
|
+
}
|
|
80
|
+
const partialV = judgeBrowserRun(twoCrit, okRun)
|
|
81
|
+
check('partial credit: 1 of 2 criteria → score=0.5 resolved=false', partialV.score === 0.5 && partialV.resolved === false, partialV.detail)
|
|
82
|
+
|
|
83
|
+
// Fail loud: no criteria is a harness error, not a silent pass.
|
|
84
|
+
let threw = false
|
|
85
|
+
try {
|
|
86
|
+
judgeBrowserRun({ ...task, success: [] }, okRun)
|
|
87
|
+
} catch {
|
|
88
|
+
threw = true
|
|
89
|
+
}
|
|
90
|
+
check('empty success spec throws (fail-loud, never silent-attest)', threw)
|
|
91
|
+
|
|
92
|
+
if (failures > 0) {
|
|
93
|
+
console.log(`\n❌ ${failures} check(s) failed`)
|
|
94
|
+
process.exit(1)
|
|
95
|
+
}
|
|
96
|
+
console.log('\n✅ browser harness verified — verdict is attestable, self-report ignored')
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CL-Bench (Continual) Codebase Adaptation — verifier-grounded selector gate.
|
|
3
|
+
*
|
|
4
|
+
* Of CL-Bench's six domains, codebase_adaptation is the ONLY deployable checker:
|
|
5
|
+
* its scorer applies the instance's provided `test_patch` and runs pytest in the
|
|
6
|
+
* instance's Docker image, keying off the exit code (the rest grade against realized
|
|
7
|
+
* outcomes the agent never has = oracles). That makes it the clean analogue of the
|
|
8
|
+
* HumanEval gate — an INDEPENDENT deployable check (tests ≠ the answer), unlike the
|
|
9
|
+
* CL-bench Context gate where the rubric judge IS the metric.
|
|
10
|
+
*
|
|
11
|
+
* Each instance is SWE-bench format (repo @ base_commit + a GitHub issue). The worker
|
|
12
|
+
* is a real sandbox rollout (opencode clones the repo, fixes the source, writes a diff
|
|
13
|
+
* to a file we read back — a large diff truncates in the chat stream). We score each
|
|
14
|
+
* candidate patch with CL-Bench's OWN scorer via `clbench_codebase_judge.py` (run in
|
|
15
|
+
* CL-Bench's venv) — verified to self-check (gold patch passes, empty fails). Two
|
|
16
|
+
* paired arms over the same instances:
|
|
17
|
+
* random@K — K identical-issue rollouts (the compute control)
|
|
18
|
+
* diverse@K — K rollouts, the i-th with a strategy lens prepended (composeStrategies)
|
|
19
|
+
* verifierGroundedSelect picks by pytest-pass; we report blind / random@k / diverse@k /
|
|
20
|
+
* oracle@k with paired-bootstrap CIs, and write a corpus RunRecord/task the existing
|
|
21
|
+
* `corpus-replay --selector=verifier` + `corpus-report` consume unchanged. Fail loud:
|
|
22
|
+
* an infra-errored rollout is excluded (infraError), never scored 0.
|
|
23
|
+
*
|
|
24
|
+
* dotenvx run -f … -- env N=4 K=3 WORKER_MODEL=deepseek-chat CONCURRENCY=2 \
|
|
25
|
+
* CLBENCH_DIR=/tmp/clbench-continual CORPUS=/tmp/clbench-codebase.jsonl \
|
|
26
|
+
* tsx src/clbench-codebase-gate.mts
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { execFile } from 'node:child_process'
|
|
30
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
31
|
+
import { tmpdir } from 'node:os'
|
|
32
|
+
import { join } from 'node:path'
|
|
33
|
+
import { promisify } from 'node:util'
|
|
34
|
+
import { acquireSandbox } from '@tangle-network/agent-runtime/loops'
|
|
35
|
+
import { Sandbox } from '@tangle-network/sandbox'
|
|
36
|
+
import { composeStrategies } from './directives'
|
|
37
|
+
import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
|
|
38
|
+
import { verifierGroundedSelect } from './selector'
|
|
39
|
+
import { type PairedLift, pairedLift, pool } from './stats.mts'
|
|
40
|
+
|
|
41
|
+
const execFileAsync = promisify(execFile)
|
|
42
|
+
const PATCH_PATH = '/tmp/solution.patch'
|
|
43
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10)
|
|
44
|
+
|
|
45
|
+
function must(name: string): string {
|
|
46
|
+
const v = process.env[name]
|
|
47
|
+
if (!v) throw new Error(`env ${name} is required`)
|
|
48
|
+
return v
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface Instance {
|
|
52
|
+
instanceId: string
|
|
53
|
+
repo: string
|
|
54
|
+
baseCommit: string
|
|
55
|
+
problemStatement: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Load CL-Bench codebase_adaptation instances (SWE-bench format) from the cloned
|
|
59
|
+
* repo's final-dataset.jsonl. Fail loud on a malformed/short record. */
|
|
60
|
+
function loadInstances(clbenchDir: string, limit: number, offset: number): Instance[] {
|
|
61
|
+
const path = join(clbenchDir, 'data/codebase_adaptation/final-dataset.jsonl')
|
|
62
|
+
const text = readFileSync(path, 'utf8')
|
|
63
|
+
const out: Instance[] = []
|
|
64
|
+
for (const line of text.split('\n')) {
|
|
65
|
+
if (line.trim() === '') continue
|
|
66
|
+
const d = JSON.parse(line) as { instance_id?: string; repo?: string; base_commit?: string; problem_statement?: string }
|
|
67
|
+
if (!d.instance_id || !d.repo || !d.base_commit || !d.problem_statement) {
|
|
68
|
+
throw new Error(`malformed codebase_adaptation record: ${line.slice(0, 120)}`)
|
|
69
|
+
}
|
|
70
|
+
out.push({ instanceId: d.instance_id, repo: d.repo, baseCommit: d.base_commit, problemStatement: d.problem_statement })
|
|
71
|
+
}
|
|
72
|
+
if (out.length === 0) throw new Error('codebase_adaptation parsed to 0 instances')
|
|
73
|
+
if (offset >= out.length) throw new Error(`OFFSET ${offset} >= dataset size ${out.length}`)
|
|
74
|
+
return out.slice(offset, offset + limit)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function rolloutPrompt(inst: Instance, lens: string | undefined): string {
|
|
78
|
+
return [
|
|
79
|
+
lens ? `${lens}\n` : '',
|
|
80
|
+
`Clone https://github.com/${inst.repo} into /work, then \`cd /work && git checkout ${inst.baseCommit}\`.`,
|
|
81
|
+
'',
|
|
82
|
+
'Resolve this GitHub issue by editing the SOURCE only (never the tests — the evaluation re-runs its own hidden tests on a fresh clone):',
|
|
83
|
+
'',
|
|
84
|
+
inst.problemStatement,
|
|
85
|
+
'',
|
|
86
|
+
'Work iteratively: install the package editable (`pip install -e .`), reproduce the issue, implement the fix, and re-run the existing tests until they pass.',
|
|
87
|
+
`When done, from /work run EXACTLY:`,
|
|
88
|
+
` git add -A && git diff --cached -- . ':(exclude)*/test*' > ${PATCH_PATH}`,
|
|
89
|
+
`Then stop. The patch file is the only deliverable — do NOT paste the diff in your reply.`,
|
|
90
|
+
].filter((s) => s !== '').join('\n')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface ShotCfg {
|
|
94
|
+
sandboxBaseUrl: string
|
|
95
|
+
routerBaseUrl: string
|
|
96
|
+
routerKey: string
|
|
97
|
+
model: string
|
|
98
|
+
/** in-box opencode provider. `openai-compat` (default) is the generic passthrough —
|
|
99
|
+
* it does NOT validate the model against opencode's registry, so router-served cheap
|
|
100
|
+
* models (deepseek-chat, moonshotai/kimi-k2.6, glm) work; `openai`/`anthropic` only
|
|
101
|
+
* accept their registered model names (e.g. gpt-4.1). */
|
|
102
|
+
provider: string
|
|
103
|
+
timeoutMs: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface Shot {
|
|
107
|
+
patch: string
|
|
108
|
+
/** rollout completed and produced a (possibly empty) patch; false ⇒ infra error (excluded). */
|
|
109
|
+
ran: boolean
|
|
110
|
+
detail?: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** One fault-isolated sandbox rollout → a patch (read from the box FS). ANY rollout
|
|
114
|
+
* error becomes a recorded infra failure (ran=false), never a throw that kills the pool. */
|
|
115
|
+
async function runRollout(inst: Instance, lens: string | undefined, cfg: ShotCfg): Promise<Shot> {
|
|
116
|
+
const client = new Sandbox({ baseUrl: cfg.sandboxBaseUrl, apiKey: cfg.routerKey })
|
|
117
|
+
let box: Awaited<ReturnType<typeof acquireSandbox>> | undefined
|
|
118
|
+
try {
|
|
119
|
+
box = await acquireSandbox(client, {
|
|
120
|
+
name: `clbench-cb-${inst.instanceId}-${randomSuffix()}`.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 60),
|
|
121
|
+
environment: 'universal',
|
|
122
|
+
// backend.model pins provider/model/baseUrl only — in-box model auth is the
|
|
123
|
+
// box-provisioned OPENCODE_MODEL_API_KEY (foreign keys are 403'd at egress).
|
|
124
|
+
backend: { type: 'opencode', model: { provider: cfg.provider, model: cfg.model, baseUrl: cfg.routerBaseUrl } },
|
|
125
|
+
})
|
|
126
|
+
const signal = cfg.timeoutMs > 0 ? AbortSignal.timeout(cfg.timeoutMs) : undefined
|
|
127
|
+
for await (const _ev of box.streamPrompt(rolloutPrompt(inst, lens), signal ? { signal } : {})) {
|
|
128
|
+
// drain; the deliverable is the patch FILE, not the stream
|
|
129
|
+
}
|
|
130
|
+
let patch = ''
|
|
131
|
+
try {
|
|
132
|
+
patch = await box.fs.read(PATCH_PATH)
|
|
133
|
+
} catch {
|
|
134
|
+
patch = '' // missing patch file ⇒ the agent produced nothing (a real empty, ran=true)
|
|
135
|
+
}
|
|
136
|
+
return { patch, ran: true }
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return { patch: '', ran: false, detail: `rollout error: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}` }
|
|
139
|
+
} finally {
|
|
140
|
+
try {
|
|
141
|
+
if (box) await box.delete()
|
|
142
|
+
} catch {
|
|
143
|
+
// staging reaps on expiry
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Score one candidate patch with CL-Bench's deployable pytest checker via the bridge
|
|
149
|
+
* (run in CL-Bench's venv, cwd = its repo root so `src.tasks...` resolves). Returns
|
|
150
|
+
* pass (0/1) or null on an infra/judge failure (excluded, never scored 0). */
|
|
151
|
+
async function judgePatch(inst: Instance, patch: string, clbenchDir: string): Promise<number | null> {
|
|
152
|
+
if (patch.trim() === '') return 0 // empty patch is a legitimate fail, not an infra error
|
|
153
|
+
const dir = mkdtempSync(join(tmpdir(), 'clbench-cb-'))
|
|
154
|
+
const patchFile = join(dir, 'candidate.patch')
|
|
155
|
+
writeFileSync(patchFile, patch)
|
|
156
|
+
try {
|
|
157
|
+
const { stdout } = await execFileAsync(
|
|
158
|
+
join(clbenchDir, '.venv/bin/python'),
|
|
159
|
+
[
|
|
160
|
+
join(process.cwd(), 'scripts/clbench_codebase_judge.py'),
|
|
161
|
+
'--dataset',
|
|
162
|
+
join(clbenchDir, 'data/codebase_adaptation/final-dataset.jsonl'),
|
|
163
|
+
'--instance-id',
|
|
164
|
+
inst.instanceId,
|
|
165
|
+
'--patch-file',
|
|
166
|
+
patchFile,
|
|
167
|
+
],
|
|
168
|
+
{ cwd: clbenchDir, maxBuffer: 8 * 1024 * 1024, timeout: 600_000 },
|
|
169
|
+
)
|
|
170
|
+
const last = stdout.trim().split('\n').at(-1) ?? '{}'
|
|
171
|
+
const verdict = JSON.parse(last) as { success?: boolean }
|
|
172
|
+
return verdict.success ? 1 : 0
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(` judge infra error ${inst.instanceId}: ${(err instanceof Error ? err.message : String(err)).slice(0, 160)}`)
|
|
175
|
+
return null
|
|
176
|
+
} finally {
|
|
177
|
+
rmSync(dir, { recursive: true, force: true })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const pct = (x: number) => `${(x * 100).toFixed(1)}%`
|
|
182
|
+
const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
|
|
183
|
+
|
|
184
|
+
async function main(): Promise<void> {
|
|
185
|
+
const n = Number(process.env.N ?? 4)
|
|
186
|
+
const k = Number(process.env.K ?? 3)
|
|
187
|
+
const offset = Number(process.env.OFFSET ?? 0)
|
|
188
|
+
const model = process.env.WORKER_MODEL ?? 'deepseek-chat'
|
|
189
|
+
// openai-compat = generic passthrough so cheap router models resolve in-box (see ShotCfg).
|
|
190
|
+
const provider = process.env.WORKER_PROVIDER ?? 'openai-compat'
|
|
191
|
+
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
192
|
+
const routerKey = must('TANGLE_API_KEY')
|
|
193
|
+
const sandboxBaseUrl = process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools'
|
|
194
|
+
const clbenchDir = process.env.CLBENCH_DIR ?? '/tmp/clbench-continual'
|
|
195
|
+
const rolloutConc = Number(process.env.CONCURRENCY ?? 3)
|
|
196
|
+
const judgeConc = Number(process.env.JUDGE_CONCURRENCY ?? 2)
|
|
197
|
+
const timeoutMs = process.env.SHOT_TIMEOUT_MS ? Number(process.env.SHOT_TIMEOUT_MS) : 900_000
|
|
198
|
+
const corpusPath = process.env.CORPUS ?? '/tmp/clbench-codebase.jsonl'
|
|
199
|
+
if (!Number.isInteger(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
|
|
200
|
+
if (!Number.isInteger(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
|
|
201
|
+
|
|
202
|
+
const cfg: ShotCfg = { sandboxBaseUrl, routerBaseUrl, routerKey, model, provider, timeoutMs }
|
|
203
|
+
console.log(`=== CL-Bench Codebase Adaptation selector gate · N=${n} K=${k} offset=${offset} model=${provider}/${model} ===`)
|
|
204
|
+
console.log(` sandbox=${sandboxBaseUrl} judge=CL-Bench pytest-in-Docker (deployable) clbench=${clbenchDir}`)
|
|
205
|
+
|
|
206
|
+
const instances = loadInstances(clbenchDir, n, offset)
|
|
207
|
+
console.log(`loaded ${instances.length} instance(s): ${instances.map((i) => i.instanceId).join(', ')}`)
|
|
208
|
+
|
|
209
|
+
type Unit = { instIdx: number; arm: 'random' | 'diverse'; shot: number; lens: string | undefined }
|
|
210
|
+
const units: Unit[] = []
|
|
211
|
+
for (let ii = 0; ii < instances.length; ii += 1) {
|
|
212
|
+
const lenses = composeStrategies('', k) // lens prefixes only ('' base ⇒ "<lens>\n\n")
|
|
213
|
+
for (let s = 0; s < k; s += 1) {
|
|
214
|
+
units.push({ instIdx: ii, arm: 'random', shot: s, lens: undefined })
|
|
215
|
+
units.push({ instIdx: ii, arm: 'diverse', shot: s, lens: (lenses[s] as string).trim() })
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log(`\n▶ phase 1: ${units.length} rollouts (${instances.length}×${k}×2 arms) via sandbox, conc=${rolloutConc}`)
|
|
219
|
+
const shots = await pool(units, rolloutConc, async (u) => {
|
|
220
|
+
const inst = instances[u.instIdx] as Instance
|
|
221
|
+
const s = await runRollout(inst, u.lens, cfg)
|
|
222
|
+
console.log(` rollout ${inst.instanceId} ${u.arm}#${u.shot}: ${s.ran ? `patch ${s.patch.length}B` : `INFRA (${s.detail})`}`)
|
|
223
|
+
return s
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
console.log(`\n▶ phase 2: judging ${shots.length} patches with the deployable checker, conc=${judgeConc}`)
|
|
227
|
+
const passes = await pool(units, judgeConc, async (u, i) => {
|
|
228
|
+
const shot = shots[i] as Shot
|
|
229
|
+
if (!shot.ran) return null // infra error ⇒ excluded
|
|
230
|
+
const inst = instances[u.instIdx] as Instance
|
|
231
|
+
const p = await judgePatch(inst, shot.patch, clbenchDir)
|
|
232
|
+
console.log(` judge ${inst.instanceId} ${u.arm}#${u.shot}: ${p === null ? 'INFRA' : p ? 'PASS' : 'fail'}`)
|
|
233
|
+
return p
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Regroup; an attempt with a null pass (infra) is dropped from its arm.
|
|
237
|
+
const byInst = instances.map(() => ({ random: [] as (number | null)[], diverse: [] as (number | null)[], rPatch: [] as string[], dPatch: [] as string[] }))
|
|
238
|
+
units.forEach((u, i) => {
|
|
239
|
+
const grp = byInst[u.instIdx] as { random: (number | null)[]; diverse: (number | null)[]; rPatch: string[]; dPatch: string[] }
|
|
240
|
+
const pass = passes[i] as number | null
|
|
241
|
+
const patch = (shots[i] as Shot).patch
|
|
242
|
+
if (u.arm === 'random') { grp.random[u.shot] = pass; grp.rPatch[u.shot] = patch } else { grp.diverse[u.shot] = pass; grp.dPatch[u.shot] = patch }
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Per-instance {0,1} outcomes; instances with no valid attempt in BOTH arms are excluded.
|
|
246
|
+
const blind: number[] = []
|
|
247
|
+
const randomAtK: number[] = []
|
|
248
|
+
const diverseAtK: number[] = []
|
|
249
|
+
const oracleAtK: number[] = []
|
|
250
|
+
let excluded = 0
|
|
251
|
+
for (const grp of byInst) {
|
|
252
|
+
const rValid = grp.random.filter((p): p is number => p !== null && p !== undefined)
|
|
253
|
+
const dValid = grp.diverse.filter((p): p is number => p !== null && p !== undefined)
|
|
254
|
+
if (rValid.length === 0 || dValid.length === 0) { excluded += 1; continue }
|
|
255
|
+
blind.push(rValid[0] as number)
|
|
256
|
+
randomAtK.push(rValid[verifierGroundedSelect(rValid)] as number)
|
|
257
|
+
diverseAtK.push(dValid[verifierGroundedSelect(dValid)] as number)
|
|
258
|
+
oracleAtK.push(dValid.some((p) => p > 0) ? 1 : 0)
|
|
259
|
+
}
|
|
260
|
+
const rate = (xs: number[]) => (xs.length === 0 ? 0 : xs.reduce((s, x) => s + x, 0) / xs.length)
|
|
261
|
+
|
|
262
|
+
console.log(`\n${'='.repeat(78)}`)
|
|
263
|
+
console.log(`RESULTS · CL-Bench Codebase Adaptation · n=${blind.length} scored (${excluded} excluded) · k=${k} · model=${model}`)
|
|
264
|
+
console.log('='.repeat(78))
|
|
265
|
+
console.log(` blind pass@1 ${pct(rate(blind))}`)
|
|
266
|
+
console.log(` random@k (verifier-pick) ${pct(rate(randomAtK))}`)
|
|
267
|
+
console.log(` diverse@k (verifier-pick) ${pct(rate(diverseAtK))}`)
|
|
268
|
+
console.log(` oracle@k (diverse, any) ${pct(rate(oracleAtK))}`)
|
|
269
|
+
if (blind.length >= 2) {
|
|
270
|
+
const row = (label: string, l: PairedLift) =>
|
|
271
|
+
console.log(` ${label.padEnd(34)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] (paired ${l.pairs}, discordant ${l.discordant})`)
|
|
272
|
+
console.log(`\n PAIRED LIFTS (95% bootstrap CI):`)
|
|
273
|
+
row('random@k − blind (compute)', pairedLift(blind, randomAtK))
|
|
274
|
+
row('diverse@k − random@k (verifier)', pairedLift(randomAtK, diverseAtK))
|
|
275
|
+
row('diverse@k − blind (total)', pairedLift(blind, diverseAtK))
|
|
276
|
+
} else {
|
|
277
|
+
console.log('\n (n<2 scored — paired CIs need ≥2; this is a plumbing smoke, not a signal)')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Corpus: random@k arm, ranked by the deployable pytest verifier.
|
|
281
|
+
for (let ii = 0; ii < instances.length; ii += 1) {
|
|
282
|
+
const inst = instances[ii] as Instance
|
|
283
|
+
const grp = byInst[ii] as { random: (number | null)[]; rPatch: string[] }
|
|
284
|
+
const attempts: AttemptRecord[] = grp.random.map((p, round) => ({
|
|
285
|
+
round,
|
|
286
|
+
prompt: 'clbench-codebase-rollout',
|
|
287
|
+
output: (grp.rPatch[round] ?? '').slice(0, 4000),
|
|
288
|
+
...(p === null ? { error: 'infra' } : { valid: p > 0, score: p }),
|
|
289
|
+
eventCount: 1,
|
|
290
|
+
eventTypes: { 'sandbox.stream': 1 },
|
|
291
|
+
traceTail: (grp.rPatch[round] ?? '').slice(-600),
|
|
292
|
+
}))
|
|
293
|
+
const validPasses = grp.random.filter((p): p is number => p !== null && p !== undefined)
|
|
294
|
+
const record = buildRunRecordFromAttempts(attempts, {
|
|
295
|
+
benchmark: 'clbench-codebase',
|
|
296
|
+
instanceId: inst.instanceId,
|
|
297
|
+
condition: `random@${k}`,
|
|
298
|
+
model,
|
|
299
|
+
// blindResolved reads off the FIRST non-null score (== full pass), not
|
|
300
|
+
// attempts[0].valid — a partial-credit first shot is valid but not "blind
|
|
301
|
+
// resolved". Pass it explicitly so the helper preserves the exact value.
|
|
302
|
+
blindResolved: validPasses[0] === 1,
|
|
303
|
+
resolved: validPasses.some((p) => p > 0),
|
|
304
|
+
infraError: validPasses.length === 0,
|
|
305
|
+
})
|
|
306
|
+
await appendRunRecord(corpusPath, record)
|
|
307
|
+
}
|
|
308
|
+
console.log(`\n=== wrote ${instances.length} task(s) → ${corpusPath} · gate: tsx src/corpus-replay.mts ${corpusPath} --selector=verifier ===`)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main().catch((err) => {
|
|
312
|
+
console.error(`clbench-codebase-gate: ${err instanceof Error ? err.message : String(err)}`)
|
|
313
|
+
process.exit(1)
|
|
314
|
+
})
|