@tangle-network/agent-bench 0.3.5 → 0.3.7
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/CHANGELOG.md +13 -0
- package/README.md +13 -1
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +43 -0
- package/dist/adapters.js.map +1 -0
- package/dist/benchmarks/_harness.d.ts +125 -0
- package/dist/benchmarks/_harness.js +33 -0
- package/dist/benchmarks/_harness.js.map +1 -0
- package/dist/benchmarks/aec-bench.d.ts +27 -0
- package/dist/benchmarks/aec-bench.js +8 -0
- package/dist/benchmarks/aec-bench.js.map +1 -0
- package/dist/benchmarks/agentbench.d.ts +16 -0
- package/dist/benchmarks/agentbench.js +10 -0
- package/dist/benchmarks/agentbench.js.map +1 -0
- package/dist/benchmarks/appworld.d.ts +37 -0
- package/dist/benchmarks/appworld.js +14 -0
- package/dist/benchmarks/appworld.js.map +1 -0
- package/dist/benchmarks/bfcl.d.ts +18 -0
- package/dist/benchmarks/bfcl.js +10 -0
- package/dist/benchmarks/bfcl.js.map +1 -0
- package/dist/benchmarks/cad-design.d.ts +45 -0
- package/dist/benchmarks/cad-design.js +7 -0
- package/dist/benchmarks/cad-design.js.map +1 -0
- package/dist/benchmarks/cadbench.d.ts +19 -0
- package/dist/benchmarks/cadbench.js +8 -0
- package/dist/benchmarks/cadbench.js.map +1 -0
- package/dist/benchmarks/cadgenbench.d.ts +22 -0
- package/dist/benchmarks/cadgenbench.js +8 -0
- package/dist/benchmarks/cadgenbench.js.map +1 -0
- package/dist/benchmarks/commit0.d.ts +31 -0
- package/dist/benchmarks/commit0.js +10 -0
- package/dist/benchmarks/commit0.js.map +1 -0
- package/dist/benchmarks/crag.d.ts +14 -0
- package/dist/benchmarks/crag.js +9 -0
- package/dist/benchmarks/crag.js.map +1 -0
- package/dist/benchmarks/dabstep.d.ts +18 -0
- package/dist/benchmarks/dabstep.js +10 -0
- package/dist/benchmarks/dabstep.js.map +1 -0
- package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
- package/dist/benchmarks/enterpriseops-gym.js +10 -0
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
- package/dist/benchmarks/finresearchbench.d.ts +15 -0
- package/dist/benchmarks/finresearchbench.js +8 -0
- package/dist/benchmarks/finresearchbench.js.map +1 -0
- package/dist/benchmarks/finsearchcomp.d.ts +49 -0
- package/dist/benchmarks/finsearchcomp.js +7 -0
- package/dist/benchmarks/finsearchcomp.js.map +1 -0
- package/dist/benchmarks/frames.d.ts +59 -0
- package/dist/benchmarks/frames.js +13 -0
- package/dist/benchmarks/frames.js.map +1 -0
- package/dist/benchmarks/hotpotqa.d.ts +48 -0
- package/dist/benchmarks/hotpotqa.js +15 -0
- package/dist/benchmarks/hotpotqa.js.map +1 -0
- package/dist/benchmarks/humaneval.d.ts +62 -0
- package/dist/benchmarks/humaneval.js +17 -0
- package/dist/benchmarks/humaneval.js.map +1 -0
- package/dist/benchmarks/mind2web.d.ts +41 -0
- package/dist/benchmarks/mind2web.js +9 -0
- package/dist/benchmarks/mind2web.js.map +1 -0
- package/dist/benchmarks/nomiracl.d.ts +15 -0
- package/dist/benchmarks/nomiracl.js +9 -0
- package/dist/benchmarks/nomiracl.js.map +1 -0
- package/dist/benchmarks/open-rag-bench.d.ts +14 -0
- package/dist/benchmarks/open-rag-bench.js +9 -0
- package/dist/benchmarks/open-rag-bench.js.map +1 -0
- package/dist/benchmarks/programbench.d.ts +38 -0
- package/dist/benchmarks/programbench.js +10 -0
- package/dist/benchmarks/programbench.js.map +1 -0
- package/dist/benchmarks/rag-shared.d.ts +42 -0
- package/dist/benchmarks/rag-shared.js +39 -0
- package/dist/benchmarks/rag-shared.js.map +1 -0
- package/dist/benchmarks/ragbench.d.ts +16 -0
- package/dist/benchmarks/ragbench.js +9 -0
- package/dist/benchmarks/ragbench.js.map +1 -0
- package/dist/benchmarks/simpleqa.d.ts +64 -0
- package/dist/benchmarks/simpleqa.js +11 -0
- package/dist/benchmarks/simpleqa.js.map +1 -0
- package/dist/benchmarks/swe-bench.d.ts +56 -0
- package/dist/benchmarks/swe-bench.js +14 -0
- package/dist/benchmarks/swe-bench.js.map +1 -0
- package/dist/benchmarks/t2-ragbench.d.ts +14 -0
- package/dist/benchmarks/t2-ragbench.js +9 -0
- package/dist/benchmarks/t2-ragbench.js.map +1 -0
- package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
- package/dist/benchmarks/tau-bench-shared.js +10 -0
- package/dist/benchmarks/tau-bench-shared.js.map +1 -0
- package/dist/benchmarks/tau2-bench.d.ts +7 -0
- package/dist/benchmarks/tau2-bench.js +11 -0
- package/dist/benchmarks/tau2-bench.js.map +1 -0
- package/dist/benchmarks/tau3-banking.d.ts +15 -0
- package/dist/benchmarks/tau3-banking.js +9 -0
- package/dist/benchmarks/tau3-banking.js.map +1 -0
- package/dist/benchmarks/terminal-bench.d.ts +24 -0
- package/dist/benchmarks/terminal-bench.js +8 -0
- package/dist/benchmarks/terminal-bench.js.map +1 -0
- package/dist/benchmarks/toollm.d.ts +16 -0
- package/dist/benchmarks/toollm.js +10 -0
- package/dist/benchmarks/toollm.js.map +1 -0
- package/dist/benchmarks/trata-hedge.d.ts +32 -0
- package/dist/benchmarks/trata-hedge.js +7 -0
- package/dist/benchmarks/trata-hedge.js.map +1 -0
- package/dist/benchmarks/types.d.ts +107 -0
- package/dist/benchmarks/types.js +1 -0
- package/dist/benchmarks/types.js.map +1 -0
- package/dist/benchmarks/webarena-verified.d.ts +16 -0
- package/dist/benchmarks/webarena-verified.js +10 -0
- package/dist/benchmarks/webarena-verified.js.map +1 -0
- package/dist/chunk-2PVVP7GN.js +197 -0
- package/dist/chunk-2PVVP7GN.js.map +1 -0
- package/dist/chunk-2XU6OGEN.js +170 -0
- package/dist/chunk-2XU6OGEN.js.map +1 -0
- package/dist/chunk-53UPUNBZ.js +325 -0
- package/dist/chunk-53UPUNBZ.js.map +1 -0
- package/dist/chunk-5H5XV76F.js +240 -0
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/chunk-7WSD27QQ.js +118 -0
- package/dist/chunk-7WSD27QQ.js.map +1 -0
- package/dist/chunk-C7T7WEK2.js +103 -0
- package/dist/chunk-C7T7WEK2.js.map +1 -0
- package/dist/chunk-CKUVRZ2T.js +251 -0
- package/dist/chunk-CKUVRZ2T.js.map +1 -0
- package/dist/chunk-HBSWHQNJ.js +30 -0
- package/dist/chunk-HBSWHQNJ.js.map +1 -0
- package/dist/chunk-HHXFIHXC.js +116 -0
- package/dist/chunk-HHXFIHXC.js.map +1 -0
- package/dist/chunk-IFAV6KEM.js +276 -0
- package/dist/chunk-IFAV6KEM.js.map +1 -0
- package/dist/chunk-INNOYXCP.js +387 -0
- package/dist/chunk-INNOYXCP.js.map +1 -0
- package/dist/chunk-J3KDJNX2.js +182 -0
- package/dist/chunk-J3KDJNX2.js.map +1 -0
- package/dist/chunk-JRWWGMK7.js +148 -0
- package/dist/chunk-JRWWGMK7.js.map +1 -0
- package/dist/chunk-JTHWEDEW.js +32 -0
- package/dist/chunk-JTHWEDEW.js.map +1 -0
- package/dist/chunk-KDIKRJGB.js +120 -0
- package/dist/chunk-KDIKRJGB.js.map +1 -0
- package/dist/chunk-LRRD7NAG.js +301 -0
- package/dist/chunk-LRRD7NAG.js.map +1 -0
- package/dist/chunk-ODT47UAY.js +221 -0
- package/dist/chunk-ODT47UAY.js.map +1 -0
- package/dist/chunk-PA2ZKHJC.js +230 -0
- package/dist/chunk-PA2ZKHJC.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-PWQVGAJB.js +144 -0
- package/dist/chunk-PWQVGAJB.js.map +1 -0
- package/dist/chunk-R36V2VP7.js +169 -0
- package/dist/chunk-R36V2VP7.js.map +1 -0
- package/dist/chunk-R67DFVLO.js +142 -0
- package/dist/chunk-R67DFVLO.js.map +1 -0
- package/dist/chunk-SEVJPLZC.js +260 -0
- package/dist/chunk-SEVJPLZC.js.map +1 -0
- package/dist/chunk-SYDW647C.js +318 -0
- package/dist/chunk-SYDW647C.js.map +1 -0
- package/dist/chunk-TBKU5XQI.js +228 -0
- package/dist/chunk-TBKU5XQI.js.map +1 -0
- package/dist/chunk-TSWPNOYM.js +147 -0
- package/dist/chunk-TSWPNOYM.js.map +1 -0
- package/dist/chunk-UAIOHCUK.js +27 -0
- package/dist/chunk-UAIOHCUK.js.map +1 -0
- package/dist/chunk-UPAMRDX4.js +233 -0
- package/dist/chunk-UPAMRDX4.js.map +1 -0
- package/dist/chunk-VQRS7VUC.js +342 -0
- package/dist/chunk-VQRS7VUC.js.map +1 -0
- package/dist/chunk-X3BTXCJ4.js +262 -0
- package/dist/chunk-X3BTXCJ4.js.map +1 -0
- package/dist/chunk-X5YKXC6V.js +211 -0
- package/dist/chunk-X5YKXC6V.js.map +1 -0
- package/dist/chunk-Y6O2OCUO.js +130 -0
- package/dist/chunk-Y6O2OCUO.js.map +1 -0
- package/dist/chunk-YCGY7UIZ.js +208 -0
- package/dist/chunk-YCGY7UIZ.js.map +1 -0
- package/dist/chunk-Z7ML6L77.js +162 -0
- package/dist/chunk-Z7ML6L77.js.map +1 -0
- package/dist/chunk-ZEWMTR5M.js +136 -0
- package/dist/chunk-ZEWMTR5M.js.map +1 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +1908 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -9
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +12 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +788 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/calibrate.ts +116 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +405 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +104 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2145 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +489 -0
- package/src/swe-arena/proposer-fanout.test.mts +372 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +361 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +498 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +166 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* META-HARNESS on the SWE scaffold — improve({ surface: 'code' }).
|
|
3
|
+
*
|
|
4
|
+
* A coding agent (Claude Code) REWRITES the scaffold LOGIC under bench/src (the seed prompt/playbook,
|
|
5
|
+
* runAgentic strategy/params, context handling, retry/patch synthesis) with the MODEL (glm worker) +
|
|
6
|
+
* the TOOL surface (list/read/edit[/run]) + the JUDGE held FIXED, judged on the official swebench
|
|
7
|
+
* Docker verdict, gated on a held-out instance split. This is the DGM/meta-harness recipe: let the
|
|
8
|
+
* SYSTEM find the scaffold lever from the RAW failure traces, not hand-build it.
|
|
9
|
+
*
|
|
10
|
+
* Wiring (all verified in this worktree):
|
|
11
|
+
* - improve()/codeProposerFor + rawTraceContext come from the LOCAL agent-runtime build, linked into
|
|
12
|
+
* this bench's node_modules (bench/node_modules/@tangle-network/agent-runtime -> /home/drew/code/agent-runtime).
|
|
13
|
+
* - The candidate proposer is agenticGenerator(harness:'claude'), BUT the shipped runLocalHarness
|
|
14
|
+
* spawns `claude --headless -p` and --headless is an unknown option on the current CLI (exit 1, no
|
|
15
|
+
* edits ever). We pass code.generator with a corrected runHarness that spawns
|
|
16
|
+
* `claude -p <prompt> --dangerously-skip-permissions` so the coding agent can actually edit the
|
|
17
|
+
* worktree. This is a harness-spawn fix, NOT a hand-authored scaffold edit — Claude still finds the
|
|
18
|
+
* lever itself from the traces.
|
|
19
|
+
* - Each candidate is a git worktree the driver forks off baseRef; Claude edits bench/src in place;
|
|
20
|
+
* `verify` (an import smoke of the edited scaffold) gates it before the expensive measurement.
|
|
21
|
+
* - MEASUREMENT: the code-aware agent fn shells into the candidate scaffold's OWN judge-free emit
|
|
22
|
+
* entrypoint (swe-emit-patch.mts) with cwd = the worktree, captures the unified diff, and returns
|
|
23
|
+
* it. improve()'s FIXED swebench judge scores that diff OUTSIDE the candidate, so the scaffold can
|
|
24
|
+
* never game its own axis. Baseline (empty surface) runs the UNEDITED scaffold from the main tree.
|
|
25
|
+
*
|
|
26
|
+
* Run (router WAF: keep example commands OUT of backticks):
|
|
27
|
+
* TANGLE_API_KEY=... dotenvx run --quiet -f .../agent-state.env --
|
|
28
|
+
* TRAIN_IDS=... HOLDOUT_IDS=... WORKER_MODEL=glm-4.6 GENERATIONS=1 POPULATION=2
|
|
29
|
+
* RUN_DIR=/abs/run BASE_REF=meta/swe-scaffold-baseline
|
|
30
|
+
* node_modules/.bin/tsx bench/src/swe-code-improve.mts
|
|
31
|
+
*/
|
|
32
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
33
|
+
import { existsSync, mkdirSync, symlinkSync } from 'node:fs'
|
|
34
|
+
import { join } from 'node:path'
|
|
35
|
+
import { improve, agenticGenerator } from '@tangle-network/agent-runtime'
|
|
36
|
+
import type { AgentProfile } from '@tangle-network/agent-interface'
|
|
37
|
+
import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
|
|
38
|
+
import { createSweBenchAdapter } from './benchmarks/swe-bench'
|
|
39
|
+
import type { BenchTask } from './benchmarks/types'
|
|
40
|
+
|
|
41
|
+
/** The agent-runtime repo the worktree adapter forks candidate checkouts from (agent-runtime-swe is a
|
|
42
|
+
* worktree of it, so bench/src is tracked here). */
|
|
43
|
+
const REPO_ROOT = process.env.REPO_ROOT ?? '/home/drew/code/agent-runtime'
|
|
44
|
+
/** The main working tree = the baseline scaffold (its bench has the linked node_modules). */
|
|
45
|
+
const SWE_MAIN_ROOT = process.env.SWE_MAIN_ROOT ?? '/home/drew/code/agent-runtime-swe'
|
|
46
|
+
const MAIN_BENCH = join(SWE_MAIN_ROOT, 'bench')
|
|
47
|
+
const TSX_BIN = join(MAIN_BENCH, 'node_modules/.bin/tsx')
|
|
48
|
+
|
|
49
|
+
/** A candidate worktree is a fresh checkout with NO node_modules. Symlink the main bench's (already
|
|
50
|
+
* carrying the linked local runtime) so both the verifier and the measurement can run the scaffold. */
|
|
51
|
+
function ensureNodeModules(rootDir: string): void {
|
|
52
|
+
const target = join(rootDir, 'bench', 'node_modules')
|
|
53
|
+
if (existsSync(target)) return
|
|
54
|
+
try {
|
|
55
|
+
symlinkSync(join(MAIN_BENCH, 'node_modules'), target, 'dir')
|
|
56
|
+
} catch {
|
|
57
|
+
/* concurrent create / already exists — fine */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Run the scaffold's judge-free emit entrypoint on ONE instance, from `rootDir`'s bench/src. Returns
|
|
62
|
+
* the unified diff (stdout, kept clean of any banner) + the tokens the worker reported (parsed from
|
|
63
|
+
* the [emit] stderr line). NO judge — the judge is held outside, in this file. */
|
|
64
|
+
function runEmit(
|
|
65
|
+
rootDir: string,
|
|
66
|
+
id: string,
|
|
67
|
+
env: Record<string, string>,
|
|
68
|
+
timeoutMs: number,
|
|
69
|
+
): Promise<{ patch: string; tokIn: number; tokOut: number; usd: number; code: number | null }> {
|
|
70
|
+
ensureNodeModules(rootDir)
|
|
71
|
+
const benchDir = join(rootDir, 'bench')
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const child = spawn(TSX_BIN, [join(benchDir, 'src/swe-emit-patch.mts')], {
|
|
74
|
+
cwd: benchDir,
|
|
75
|
+
env: { ...process.env, ...env, IDS: id },
|
|
76
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
77
|
+
})
|
|
78
|
+
let out = ''
|
|
79
|
+
let err = ''
|
|
80
|
+
child.stdout.on('data', (d) => (out += String(d)))
|
|
81
|
+
child.stderr.on('data', (d) => (err += String(d)))
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
if (!child.killed) child.kill('SIGKILL')
|
|
84
|
+
}, timeoutMs)
|
|
85
|
+
;(timer as { unref?: () => void }).unref?.()
|
|
86
|
+
child.on('error', () => {
|
|
87
|
+
clearTimeout(timer)
|
|
88
|
+
resolve({ patch: '', tokIn: 0, tokOut: 0, usd: 0, code: 1 })
|
|
89
|
+
})
|
|
90
|
+
child.on('close', (code) => {
|
|
91
|
+
clearTimeout(timer)
|
|
92
|
+
const m = /tok=in:(\d+)\/out:(\d+)/.exec(err)
|
|
93
|
+
const u = /usd=([0-9.]+)/.exec(err)
|
|
94
|
+
if (code !== 0) console.error(` [emit ${id}] exit=${code} stderr: ${err.slice(-400).replace(/\n/g, ' ')}`)
|
|
95
|
+
resolve({
|
|
96
|
+
patch: out,
|
|
97
|
+
tokIn: m ? Number(m[1]) : 0,
|
|
98
|
+
tokOut: m ? Number(m[2]) : 0,
|
|
99
|
+
usd: u ? Number(u[1]) : 0,
|
|
100
|
+
code,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main(): Promise<void> {
|
|
107
|
+
const routerKey = process.env.TANGLE_API_KEY
|
|
108
|
+
if (!routerKey) throw new Error('TANGLE_API_KEY required (worker calls the router)')
|
|
109
|
+
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
110
|
+
const workerModel = process.env.WORKER_MODEL ?? 'glm-4.6'
|
|
111
|
+
const reflectModel = process.env.REFLECT_MODEL ?? 'glm-4.6'
|
|
112
|
+
const trainIds = (process.env.TRAIN_IDS ?? 'psf__requests-2931,pallets__flask-5014').split(',').map((s) => s.trim()).filter(Boolean)
|
|
113
|
+
const holdoutIds = (process.env.HOLDOUT_IDS ?? 'psf__requests-1142,psf__requests-1921').split(',').map((s) => s.trim()).filter(Boolean)
|
|
114
|
+
const generations = Number(process.env.GENERATIONS ?? 1)
|
|
115
|
+
const population = Number(process.env.POPULATION ?? 2)
|
|
116
|
+
const innerTurns = Number(process.env.INNER_TURNS ?? 40)
|
|
117
|
+
const maxTokens = Number(process.env.MAX_TOKENS ?? 12000)
|
|
118
|
+
const enableRun = ['1', 'true', 'yes'].includes((process.env.RUN_TOOL ?? '').toLowerCase())
|
|
119
|
+
const baseRef = process.env.BASE_REF ?? 'meta/swe-scaffold-baseline'
|
|
120
|
+
const worktreeDir = process.env.WORKTREE_DIR ?? '/tmp/claude-1000/-home-drew-code-supervisor-lab/9ee6a456-a94f-474c-9888-b4afc9bc26bd/scratchpad/mh-worktrees'
|
|
121
|
+
const runDir = process.env.RUN_DIR ?? '/tmp/claude-1000/-home-drew-code-supervisor-lab/9ee6a456-a94f-474c-9888-b4afc9bc26bd/scratchpad/mh-run'
|
|
122
|
+
const emitTimeoutMs = Number(process.env.EMIT_TIMEOUT_MS ?? 600_000)
|
|
123
|
+
const harnessTimeoutMs = Number(process.env.HARNESS_TIMEOUT_MS ?? 900_000)
|
|
124
|
+
mkdirSync(worktreeDir, { recursive: true })
|
|
125
|
+
mkdirSync(runDir, { recursive: true })
|
|
126
|
+
|
|
127
|
+
const allIds = [...new Set([...trainIds, ...holdoutIds])]
|
|
128
|
+
|
|
129
|
+
console.log('=== META-HARNESS on the SWE scaffold — improve(surface:code) ===')
|
|
130
|
+
console.log(`worker=${workerModel} reflect=${reflectModel} router=${routerBaseUrl} runTool=${enableRun}`)
|
|
131
|
+
console.log(`train=[${trainIds.join(', ')}] holdout=[${holdoutIds.join(', ')}]`)
|
|
132
|
+
console.log(`generations=${generations} population=${population} innerTurns=${innerTurns} maxTokens=${maxTokens}`)
|
|
133
|
+
console.log(`repoRoot=${REPO_ROOT} baseRef=${baseRef}`)
|
|
134
|
+
console.log(`runDir=${runDir} worktreeDir=${worktreeDir}`)
|
|
135
|
+
|
|
136
|
+
// Fixed swebench Docker judge (held OUTSIDE every candidate) + task pool.
|
|
137
|
+
const adapter = createSweBenchAdapter()
|
|
138
|
+
const pool = await adapter.loadTasks({ ids: allIds, split: 'test' })
|
|
139
|
+
const byId = new Map<string, BenchTask>(pool.map((t) => [t.id, t]))
|
|
140
|
+
for (const id of allIds) if (!byId.has(id)) throw new Error(`instance not found in Verified: ${id}`)
|
|
141
|
+
|
|
142
|
+
const workerEnv: Record<string, string> = {
|
|
143
|
+
WORKER_MODEL: workerModel,
|
|
144
|
+
MAX_TOKENS: String(maxTokens),
|
|
145
|
+
INNER_TURNS: String(innerTurns),
|
|
146
|
+
ROUTER_BASE: routerBaseUrl,
|
|
147
|
+
TANGLE_API_KEY: routerKey,
|
|
148
|
+
RUN_TOOL: enableRun ? '1' : '0',
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The code-aware measurement agent. A CodeSurface -> run the candidate scaffold in its worktree; an
|
|
152
|
+
// empty/string surface (the baseline arm) -> run the UNEDITED scaffold from the main tree. Same path,
|
|
153
|
+
// so baseline and candidates are measured identically.
|
|
154
|
+
const agent = async (surface: unknown, scenario: Scenario, ctx: DispatchContext): Promise<string | null> => {
|
|
155
|
+
const isCode = !!surface && typeof surface === 'object' && (surface as { kind?: string }).kind === 'code'
|
|
156
|
+
const worktreeRef = isCode ? String((surface as { worktreeRef?: string }).worktreeRef ?? '') : ''
|
|
157
|
+
const rootDir = isCode && worktreeRef && existsSync(worktreeRef) ? worktreeRef : SWE_MAIN_ROOT
|
|
158
|
+
const t0 = Date.now()
|
|
159
|
+
const r = await runEmit(rootDir, scenario.id, workerEnv, emitTimeoutMs)
|
|
160
|
+
const hasPatch = r.patch.trim().length > 0
|
|
161
|
+
// Report real usage; floor a patch-bearing zero-usage cell so the stub-guard cannot abort on a
|
|
162
|
+
// router telemetry gap (lift is judge-derived, so this only affects cost accounting).
|
|
163
|
+
const zeroUsage = r.tokIn === 0 && r.tokOut === 0
|
|
164
|
+
ctx.cost.observe(zeroUsage && hasPatch ? Math.max(r.usd, 0.0001) : r.usd, workerModel)
|
|
165
|
+
ctx.cost.observeTokens(
|
|
166
|
+
zeroUsage && hasPatch ? { input: Math.max(r.tokIn, 1), output: Math.max(r.tokOut, 1) } : { input: r.tokIn, output: r.tokOut },
|
|
167
|
+
)
|
|
168
|
+
const files = hasPatch ? [...r.patch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
|
|
169
|
+
console.log(
|
|
170
|
+
` [measure] ${isCode ? 'cand' : 'base'} ${scenario.id} patch=${r.patch.length}b files=[${files.join(', ') || 'none'}] ` +
|
|
171
|
+
`tok=in:${r.tokIn}/out:${r.tokOut} ${Math.round((Date.now() - t0) / 1000)}s${isCode ? ` @ ${worktreeRef}` : ''}`,
|
|
172
|
+
)
|
|
173
|
+
return hasPatch ? r.patch : null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const judge: JudgeConfig<string, Scenario> = {
|
|
177
|
+
name: 'swebench-docker',
|
|
178
|
+
dimensions: [{ key: 'resolved', description: 'FAIL_TO_PASS + PASS_TO_PASS resolved by the official swebench Docker harness' }],
|
|
179
|
+
async score({ artifact, scenario }) {
|
|
180
|
+
const patch = String(artifact ?? '')
|
|
181
|
+
if (!patch.trim()) {
|
|
182
|
+
console.log(` [judge] ${scenario.id} resolved=0 (no patch)`)
|
|
183
|
+
return { dimensions: { resolved: 0 }, composite: 0, notes: 'no patch emitted' }
|
|
184
|
+
}
|
|
185
|
+
const bt = byId.get(scenario.id)
|
|
186
|
+
if (!bt) throw new Error(`judge: unknown scenario ${scenario.id}`)
|
|
187
|
+
const s = await adapter.judge(bt, patch)
|
|
188
|
+
console.log(` [judge] ${scenario.id} resolved=${s.resolved ? 1 : 0}`)
|
|
189
|
+
return { dimensions: { resolved: s.resolved ? 1 : 0 }, composite: s.resolved ? 1 : 0, notes: (s.detail ?? '').slice(0, 200) }
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// The corrected coding-harness spawn: `claude -p <prompt> --dangerously-skip-permissions`. The shipped
|
|
194
|
+
// runLocalHarness uses `claude --headless -p` (unknown option on this CLI). agenticGenerator ignores
|
|
195
|
+
// the return value (it reads worktree dirtiness), so a minimal result shape is enough.
|
|
196
|
+
const runHarness = (o: { cwd: string; taskPrompt: string; timeoutMs?: number; signal?: AbortSignal }): Promise<{ exitCode: number | null; stdout: string; stderr: string; killedBySignal: NodeJS.Signals | null; durationMs: number; timedOut: boolean }> => {
|
|
197
|
+
const started = Date.now()
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
const child = spawn('claude', ['-p', o.taskPrompt, '--dangerously-skip-permissions'], {
|
|
200
|
+
cwd: o.cwd,
|
|
201
|
+
env: process.env,
|
|
202
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
203
|
+
})
|
|
204
|
+
let stdout = ''
|
|
205
|
+
let stderr = ''
|
|
206
|
+
let timedOut = false
|
|
207
|
+
child.stdout?.on('data', (d) => (stdout += String(d)))
|
|
208
|
+
child.stderr?.on('data', (d) => (stderr += String(d)))
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
timedOut = true
|
|
211
|
+
if (!child.killed) child.kill('SIGTERM')
|
|
212
|
+
}, o.timeoutMs ?? harnessTimeoutMs)
|
|
213
|
+
;(timer as { unref?: () => void }).unref?.()
|
|
214
|
+
const onAbort = () => {
|
|
215
|
+
if (!child.killed) child.kill('SIGTERM')
|
|
216
|
+
}
|
|
217
|
+
o.signal?.addEventListener('abort', onAbort, { once: true })
|
|
218
|
+
child.on('error', () => {
|
|
219
|
+
clearTimeout(timer)
|
|
220
|
+
resolve({ exitCode: 1, stdout, stderr: `${stderr}\n[spawn error]`, killedBySignal: null, durationMs: Date.now() - started, timedOut })
|
|
221
|
+
})
|
|
222
|
+
child.on('close', (code, signal) => {
|
|
223
|
+
clearTimeout(timer)
|
|
224
|
+
o.signal?.removeEventListener('abort', onAbort)
|
|
225
|
+
console.error(` [proposer:claude] exit=${code} wall=${Math.round((Date.now() - started) / 1000)}s out=${stdout.length}b`)
|
|
226
|
+
resolve({ exitCode: code, stdout, stderr, killedBySignal: signal, durationMs: Date.now() - started, timedOut })
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Domain prompt: name the EDIT BOUNDARY (scaffold logic only) + keep the raw-trace evidence discipline
|
|
232
|
+
// (agenticGenerator discards a raw-trace candidate that doesn't inspect a trace + write the diagnosis).
|
|
233
|
+
const buildPrompt = (args: { report: unknown; findings: Array<{ severity?: string; claim?: string; recommended_action?: string }> }): string => {
|
|
234
|
+
const lines: string[] = [
|
|
235
|
+
'You are improving a SWE-bench coding SCAFFOLD: a harness that drives a FIXED worker model to fix real GitHub bugs via list_files/read_file/edit_file tools. Your job is to rewrite the SCAFFOLD LOGIC so the SAME worker model resolves MORE instances on a held-out split.',
|
|
236
|
+
'',
|
|
237
|
+
'EDIT ONLY the scaffold logic under bench/src:',
|
|
238
|
+
' - the seed prompt / playbook: SWE_SEED_PROMPT and SWE_SEED_PROMPT_WITH_RUN in bench/src/swe-bench-env.ts',
|
|
239
|
+
' - the exploration/context handling in bench/src/swe-bench-env.ts: list_files walk depth and 240-entry cap, the read_file 24000-char truncation, edit_file retry messaging, patch synthesis (git diff)',
|
|
240
|
+
' - the runAgentic strategy/params in bench/src/swe-emit-patch.mts: innerTurns default, budget, how the patch is captured',
|
|
241
|
+
'',
|
|
242
|
+
'DO NOT change (FIXED for this search):',
|
|
243
|
+
' - the TOOL surface: the list_files/read_file/edit_file/run tool NAMES, JSON signatures, or the path jail. Do not add or remove a tool.',
|
|
244
|
+
' - the worker MODEL or MAX_TOKENS (passed via env — never hardcode a different model).',
|
|
245
|
+
' - the swebench Docker JUDGE.',
|
|
246
|
+
' - the swe-emit-patch.mts I/O contract: it still reads IDS + WORKER_MODEL + router env and prints ONLY the unified diff to stdout (diagnostics to stderr). You may change HOW the patch is produced, never this stdin/stdout contract.',
|
|
247
|
+
'',
|
|
248
|
+
'Make the smallest set of edits that addresses the failure evidence below, then stop. Leave changes in the working tree; do NOT commit.',
|
|
249
|
+
'',
|
|
250
|
+
'Failure evidence from the previous generation (its RAW run traces are on disk):',
|
|
251
|
+
]
|
|
252
|
+
for (const f of args.findings) {
|
|
253
|
+
lines.push(`- (${f.severity ?? 'info'}) ${f.claim ?? ''}`)
|
|
254
|
+
if (f.recommended_action) lines.push(` -> ${f.recommended_action}`)
|
|
255
|
+
}
|
|
256
|
+
lines.push(
|
|
257
|
+
'',
|
|
258
|
+
'Raw-trace evidence requirement (enforced — a candidate that skips this is discarded):',
|
|
259
|
+
' - Inspect at least one raw trace path named above (grep/cat/ls it) BEFORE editing.',
|
|
260
|
+
' - Write .improve/raw-trace-diagnosis.md in this worktree containing: the exact trace path(s) you inspected, the failure mechanism you found, and the scaffold-logic change you made.',
|
|
261
|
+
' - A candidate with ONLY that file changed (no real scaffold edit) is discarded.',
|
|
262
|
+
)
|
|
263
|
+
return lines.join('\n')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const verify = (worktreePath: string): { ok: boolean; feedback?: string } => {
|
|
267
|
+
ensureNodeModules(worktreePath)
|
|
268
|
+
const res = spawnSync(TSX_BIN, [join(worktreePath, 'bench/src/swe-emit-patch.mts')], {
|
|
269
|
+
cwd: join(worktreePath, 'bench'),
|
|
270
|
+
env: { ...process.env, SWE_EMIT_SMOKE: '1' },
|
|
271
|
+
encoding: 'utf-8',
|
|
272
|
+
timeout: 180_000,
|
|
273
|
+
})
|
|
274
|
+
if (res.status === 0) return { ok: true }
|
|
275
|
+
const out = `${res.stdout ?? ''}${res.stderr ?? ''}`.trim()
|
|
276
|
+
return { ok: false, feedback: `edited scaffold failed import smoke (tsx swe-emit-patch SWE_EMIT_SMOKE=1):\n${out.slice(0, 3000)}` }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const generator = agenticGenerator({
|
|
280
|
+
harness: 'claude',
|
|
281
|
+
verify,
|
|
282
|
+
timeoutMs: harnessTimeoutMs,
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
284
|
+
buildPrompt: buildPrompt as any,
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
|
+
runHarness: runHarness as any,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
const profile: AgentProfile = { name: 'swe-scaffold', prompt: { systemPrompt: '' } }
|
|
290
|
+
const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
291
|
+
const holdoutScenarios: Scenario[] = holdoutIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
292
|
+
|
|
293
|
+
const out = await improve(profile, [], {
|
|
294
|
+
surface: 'code',
|
|
295
|
+
gate: 'holdout',
|
|
296
|
+
code: { repoRoot: REPO_ROOT, baseRef, worktreeDir, generator },
|
|
297
|
+
rawTraceContext: true,
|
|
298
|
+
runDir,
|
|
299
|
+
scenarios,
|
|
300
|
+
judge,
|
|
301
|
+
agent,
|
|
302
|
+
expectUsage: 'warn',
|
|
303
|
+
budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency: 1, reps: 1 },
|
|
304
|
+
llm: { baseUrl: routerBaseUrl, apiKey: routerKey, model: reflectModel },
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
console.log('\n=== RESULT ===')
|
|
308
|
+
console.log(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
|
|
309
|
+
console.log(`baseline holdout composite = ${out.raw.baseline.compositeMean}`)
|
|
310
|
+
console.log(`winner holdout composite = ${out.raw.winner.compositeMean}`)
|
|
311
|
+
console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
|
|
312
|
+
console.log(`winner per-scenario: ${JSON.stringify(out.raw.winner.perScenario)}`)
|
|
313
|
+
if (out.raw.winner.label) console.log(`winner label: ${out.raw.winner.label}`)
|
|
314
|
+
if (out.raw.winner.summary) console.log(`winner summary: ${out.raw.winner.summary}`)
|
|
315
|
+
for (const gen of out.raw.generations ?? []) {
|
|
316
|
+
console.log(`\n-- generation ${gen.record.generationIndex} candidates --`)
|
|
317
|
+
for (const c of gen.record.candidates) {
|
|
318
|
+
const perScenario = (c as { scenarios?: Array<{ scenarioId: string; composite: number }> }).scenarios ?? []
|
|
319
|
+
const detail = perScenario.map((s) => `${s.scenarioId}=${s.composite}`).join(' ')
|
|
320
|
+
console.log(` candidate ${c.surfaceHash.slice(0, 8)} composite=${c.composite}${c.label ? ` "${c.label}"` : ''} [${detail}]`)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
main().catch((e) => {
|
|
326
|
+
console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
|
|
327
|
+
process.exit(1)
|
|
328
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judge-FREE patch emitter for the SWE scaffold — the FIXED measurement entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Given ONE instance id (IDS) + worker model + router env, this runs the candidate scaffold's OWN
|
|
5
|
+
* `createSweBenchEnvironment` + `runAgentic` IN THIS worktree and prints the unified `git diff` of
|
|
6
|
+
* the agent's edits to STDOUT. It does NOT judge — the swebench Docker judge is held OUTSIDE the
|
|
7
|
+
* candidate (in swe-code-improve.mts) so the scaffold can never grade its own axis.
|
|
8
|
+
*
|
|
9
|
+
* This is the seam the code-aware improve() agent shells into. Its I/O contract is FIXED and the
|
|
10
|
+
* scaffold proposer must NOT change it:
|
|
11
|
+
* IN (env): IDS=<one instance id>, WORKER_MODEL, MAX_TOKENS, ROUTER_BASE, TANGLE_API_KEY,
|
|
12
|
+
* INNER_TURNS, BUDGET, RUN_TOOL
|
|
13
|
+
* OUT (fd1): the unified diff (empty string when the agent made no edit)
|
|
14
|
+
* diagnostics go to STDERR only, so stdout stays a clean patch.
|
|
15
|
+
*
|
|
16
|
+
* SWE_EMIT_SMOKE=1 → import/wiring check only: the module (and swe-bench-env) loaded, print READY on
|
|
17
|
+
* stderr and exit 0 WITHOUT a clone / model call / dataset read. This is what the candidate verifier
|
|
18
|
+
* runs to discard a scaffold edit that no longer imports.
|
|
19
|
+
*
|
|
20
|
+
* Command examples in prompts are kept OUT of backticks on purpose (router WAF 403s backtick-wrapped
|
|
21
|
+
* command text) — see swe-bench-env.ts.
|
|
22
|
+
*/
|
|
23
|
+
import { execFile } from 'node:child_process'
|
|
24
|
+
import { promisify } from 'node:util'
|
|
25
|
+
import type { AgenticSurface, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
|
|
26
|
+
import { refine, runAgentic } from '@tangle-network/agent-runtime/loops'
|
|
27
|
+
import { createSweBenchEnvironment, SWE_SEED_PROMPT, SWE_SEED_PROMPT_WITH_RUN } from './swe-bench-env'
|
|
28
|
+
|
|
29
|
+
const exec = promisify(execFile)
|
|
30
|
+
|
|
31
|
+
async function main(): Promise<void> {
|
|
32
|
+
const smoke = ['1', 'true', 'yes'].includes((process.env.SWE_EMIT_SMOKE ?? '').toLowerCase())
|
|
33
|
+
if (smoke) {
|
|
34
|
+
// The import graph (this file + swe-bench-env + the linked runtime) resolved by the time we get
|
|
35
|
+
// here. That is the whole check — no clone, no model call, no dataset read.
|
|
36
|
+
console.error('SWE_EMIT_SMOKE ok: swe-emit-patch + swe-bench-env import graph loaded')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const routerKey = process.env.TANGLE_API_KEY
|
|
41
|
+
if (!routerKey) throw new Error('TANGLE_API_KEY required (the worker calls the router)')
|
|
42
|
+
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
43
|
+
const model = process.env.WORKER_MODEL ?? 'glm-5.2'
|
|
44
|
+
const ids = (process.env.IDS ?? '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
45
|
+
if (ids.length !== 1) throw new Error(`swe-emit-patch: exactly one IDS required, got [${ids.join(', ')}]`)
|
|
46
|
+
const id = ids[0]!
|
|
47
|
+
const innerTurns = Number(process.env.INNER_TURNS ?? 40)
|
|
48
|
+
const maxTokens = Number(process.env.MAX_TOKENS ?? 12000)
|
|
49
|
+
const budget = Number(process.env.BUDGET ?? 1)
|
|
50
|
+
const enableRun = ['1', 'true', 'yes'].includes((process.env.RUN_TOOL ?? '').toLowerCase())
|
|
51
|
+
|
|
52
|
+
const { environment, adapter } = await createSweBenchEnvironment(1, { ids, enableRun })
|
|
53
|
+
const pool = await adapter.loadTasks({ ids, split: 'test' })
|
|
54
|
+
const bt = pool.find((t) => t.id === id)
|
|
55
|
+
if (!bt) throw new Error(`swe-emit-patch: instance not found in Verified: ${id}`)
|
|
56
|
+
|
|
57
|
+
const task = {
|
|
58
|
+
id: bt.id,
|
|
59
|
+
systemPrompt: enableRun ? SWE_SEED_PROMPT_WITH_RUN : SWE_SEED_PROMPT,
|
|
60
|
+
userPrompt: bt.prompt,
|
|
61
|
+
meta: { instanceId: bt.id },
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Capture the patch from inside score() (called during the refine loop, BEFORE the surface closes
|
|
65
|
+
// and rms the checkout). Keep the LATEST non-empty diff so accumulated refinements win and a later
|
|
66
|
+
// empty read never clobbers a real patch.
|
|
67
|
+
let capturedPatch = ''
|
|
68
|
+
const proxy: AgenticSurface = {
|
|
69
|
+
...environment,
|
|
70
|
+
async score(_t, handle: ArtifactHandle): Promise<SurfaceScore> {
|
|
71
|
+
try {
|
|
72
|
+
const d = await exec('git', ['-C', handle.id, 'diff'], { maxBuffer: 40_000_000, timeout: 60_000 })
|
|
73
|
+
if (d.stdout.trim()) capturedPatch = d.stdout
|
|
74
|
+
} catch {
|
|
75
|
+
/* workspace gone or git error → keep whatever we already captured */
|
|
76
|
+
}
|
|
77
|
+
return { passes: capturedPatch.trim() ? 1 : 0, total: 1, errored: 0 }
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const t0 = Date.now()
|
|
82
|
+
const r = await runAgentic({
|
|
83
|
+
surface: proxy,
|
|
84
|
+
task,
|
|
85
|
+
strategy: refine,
|
|
86
|
+
routerBaseUrl,
|
|
87
|
+
routerKey,
|
|
88
|
+
model,
|
|
89
|
+
maxTokens,
|
|
90
|
+
innerTurns,
|
|
91
|
+
budget,
|
|
92
|
+
})
|
|
93
|
+
const files = capturedPatch ? [...capturedPatch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
|
|
94
|
+
console.error(
|
|
95
|
+
`[emit] ${id} shots=${r.shots} completions=${r.completions} tok=in:${r.tokens.input}/out:${r.tokens.output} ` +
|
|
96
|
+
`patch=${capturedPatch.length}b files=[${files.join(', ') || 'none'}] ${Math.round((Date.now() - t0) / 1000)}s`,
|
|
97
|
+
)
|
|
98
|
+
process.stdout.write(capturedPatch)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
main().catch((e) => {
|
|
102
|
+
console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
|
|
103
|
+
process.exit(1)
|
|
104
|
+
})
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SELF-IMPROVEMENT on the SEE-able LOCAL SWE-bench path — NO tangle sandbox.
|
|
3
|
+
*
|
|
4
|
+
* Composes the three proven pieces into ONE held-out-gated improvement generation:
|
|
5
|
+
* 1. `improve({ surface: 'prompt' })` (agent-runtime) drives the loop: it asks
|
|
6
|
+
* `gepaProposer` to EVOLVE the SWE agent's system prompt, then measures each
|
|
7
|
+
* candidate prompt on real instances and gates the winner on a held-out split.
|
|
8
|
+
* 2. Per candidate + scenario, the `agent` fn runs the LOCAL SWE env
|
|
9
|
+
* (`createSweBenchEnvironment` + `runAgentic`): clone the instance repo to a
|
|
10
|
+
* host tmpdir, run the jailed list/read/edit tool loop with the CANDIDATE
|
|
11
|
+
* prompt as the system prompt, and return the `git diff` as the artifact.
|
|
12
|
+
* 3. The `judge` scores that patch with the OFFICIAL swebench Docker harness
|
|
13
|
+
* (`adapter.judge` → resolved 0/1). The only remote call is the model
|
|
14
|
+
* completion via the router; nothing touches sandbox.tangle.tools.
|
|
15
|
+
*
|
|
16
|
+
* IN-LOOP score is a cheap patch-exists proxy (NOT the Docker judge) so the ONLY
|
|
17
|
+
* Docker run per cell is the improve judge — one deterministic verdict per cell.
|
|
18
|
+
*
|
|
19
|
+
* Cost per run = T·(1 + G·P) + 2·H cells, each = 1 clone + 1 runAgentic + 1 judge.
|
|
20
|
+
*
|
|
21
|
+
* TANGLE_API_KEY=… dotenvx run -f …/agent-state.env -- \
|
|
22
|
+
* TRAIN_IDS=psf__requests-2931 HOLDOUT_IDS=psf__requests-1142 \
|
|
23
|
+
* GENERATIONS=1 POPULATION=1 WORKER_MODEL=glm-4.6 REFLECT_MODEL=glm-4.6 \
|
|
24
|
+
* node_modules/.bin/tsx bench/src/swe-improve.mts
|
|
25
|
+
*/
|
|
26
|
+
import { execFile } from 'node:child_process'
|
|
27
|
+
import { promisify } from 'node:util'
|
|
28
|
+
import { improve } from '@tangle-network/agent-runtime'
|
|
29
|
+
import type { AgentProfile } from '@tangle-network/agent-interface'
|
|
30
|
+
import type { AgenticSurface, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
|
|
31
|
+
import { refine, runAgentic } from '@tangle-network/agent-runtime/loops'
|
|
32
|
+
import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
|
|
33
|
+
import { gepaProposer } from '@tangle-network/agent-eval/campaign'
|
|
34
|
+
import { createSweBenchAdapter } from './benchmarks/swe-bench'
|
|
35
|
+
import type { BenchTask } from './benchmarks/types'
|
|
36
|
+
import { createSweBenchEnvironment, SWE_SEED_PROMPT, SWE_SEED_PROMPT_WITH_RUN } from './swe-bench-env'
|
|
37
|
+
|
|
38
|
+
const exec = promisify(execFile)
|
|
39
|
+
|
|
40
|
+
async function main(): Promise<void> {
|
|
41
|
+
const routerKey = process.env.TANGLE_API_KEY
|
|
42
|
+
if (!routerKey) throw new Error('TANGLE_API_KEY required (the worker + reflection call the router)')
|
|
43
|
+
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
44
|
+
const workerModel = process.env.WORKER_MODEL ?? 'glm-4.6'
|
|
45
|
+
const reflectModel = process.env.REFLECT_MODEL ?? 'glm-4.6'
|
|
46
|
+
const trainIds = (process.env.TRAIN_IDS ?? 'psf__requests-2931').split(',').map((s) => s.trim()).filter(Boolean)
|
|
47
|
+
const holdoutIds = (process.env.HOLDOUT_IDS ?? 'psf__requests-1142').split(',').map((s) => s.trim()).filter(Boolean)
|
|
48
|
+
const generations = Number(process.env.GENERATIONS ?? 1)
|
|
49
|
+
const population = Number(process.env.POPULATION ?? 1)
|
|
50
|
+
const innerTurns = Number(process.env.INNER_TURNS ?? 40)
|
|
51
|
+
const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 8000)
|
|
52
|
+
const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 12000)
|
|
53
|
+
const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 1)
|
|
54
|
+
const budgetShots = Number(process.env.BUDGET ?? 1)
|
|
55
|
+
// WITH-TOOLS arm: RUN_TOOL=1 exposes the jailed `run` tool AND swaps the seed to the run-aware prompt.
|
|
56
|
+
// Default OFF ⇒ reproduces the read/edit-only baseline denominator unchanged.
|
|
57
|
+
const enableRun = ['1', 'true', 'yes'].includes((process.env.RUN_TOOL ?? '').toLowerCase())
|
|
58
|
+
const SEED_PROMPT = enableRun ? SWE_SEED_PROMPT_WITH_RUN : SWE_SEED_PROMPT
|
|
59
|
+
|
|
60
|
+
const allIds = [...new Set([...trainIds, ...holdoutIds])]
|
|
61
|
+
const cellsMax = trainIds.length * (1 + generations * population) + 2 * holdoutIds.length
|
|
62
|
+
|
|
63
|
+
console.log('═══ SWE-bench self-improvement — SEE-able LOCAL (no tangle sandbox) ═══')
|
|
64
|
+
console.log(`worker=${workerModel} reflect=${reflectModel} router=${routerBaseUrl}`)
|
|
65
|
+
console.log(`train=[${trainIds.join(', ')}] holdout=[${holdoutIds.join(', ')}]`)
|
|
66
|
+
console.log(`generations=${generations} population=${population} innerTurns=${innerTurns} workerMaxTokens=${workerMaxTokens} reflectMaxTokens=${reflectMaxTokens} runTool=${enableRun}`)
|
|
67
|
+
console.log(`≈ ${cellsMax} cells max (each = 1 clone + 1 runAgentic + 1 Docker judge)\n`)
|
|
68
|
+
|
|
69
|
+
if (process.env.DRYRUN) {
|
|
70
|
+
// Import + wiring smoke: prove every module resolves and the plan is well-formed
|
|
71
|
+
// WITHOUT paying for a clone / model call / Docker judge.
|
|
72
|
+
console.log(`DRYRUN: imports OK (improve=${typeof improve}, gepaProposer=${typeof gepaProposer}, runAgentic=${typeof runAgentic}, refine=${typeof refine})`)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { environment, adapter } = await createSweBenchEnvironment(allIds.length, { ids: allIds, enableRun })
|
|
77
|
+
const pool = await adapter.loadTasks({ ids: allIds, split: 'test' })
|
|
78
|
+
const byId = new Map<string, BenchTask>(pool.map((t) => [t.id, t]))
|
|
79
|
+
for (const id of allIds) if (!byId.has(id)) throw new Error(`instance not found in Verified: ${id}`)
|
|
80
|
+
|
|
81
|
+
// The agent under improvement: run the LOCAL SWE env with the CANDIDATE prompt on
|
|
82
|
+
// one instance, return the git-diff patch. A per-call proxy captures the patch in
|
|
83
|
+
// score() BEFORE runAgentic closes (rm) the workspace; its score is a cheap
|
|
84
|
+
// patch-exists proxy so the ONLY Docker run per cell is the improve judge.
|
|
85
|
+
const agent = async (surface: unknown, scenario: Scenario, ctx: DispatchContext): Promise<string | null> => {
|
|
86
|
+
const promptText = String(surface)
|
|
87
|
+
const bt = byId.get(scenario.id)
|
|
88
|
+
if (!bt) throw new Error(`agent: unknown scenario ${scenario.id}`)
|
|
89
|
+
const task = { id: bt.id, systemPrompt: promptText, userPrompt: bt.prompt, meta: { instanceId: bt.id } }
|
|
90
|
+
let capturedPatch = ''
|
|
91
|
+
const stats = { list: 0, read: 0, edit_ok: 0, edit_fail: 0, run: 0, run_err: 0 }
|
|
92
|
+
const proxy: AgenticSurface = {
|
|
93
|
+
...environment,
|
|
94
|
+
async call(handle, name, args) {
|
|
95
|
+
const res = await environment.call(handle, name, args)
|
|
96
|
+
const r = String(res)
|
|
97
|
+
if (name === 'list_files') stats.list += 1
|
|
98
|
+
else if (name === 'read_file') stats.read += 1
|
|
99
|
+
else if (name === 'edit_file') r.startsWith('edited ') ? (stats.edit_ok += 1) : (stats.edit_fail += 1)
|
|
100
|
+
else if (name === 'run') r.startsWith('ERROR:') ? (stats.run_err += 1) : (stats.run += 1)
|
|
101
|
+
return res
|
|
102
|
+
},
|
|
103
|
+
async score(_t, handle: ArtifactHandle): Promise<SurfaceScore> {
|
|
104
|
+
try {
|
|
105
|
+
const diff = await exec('git', ['-C', handle.id, 'diff'], { maxBuffer: 40_000_000, timeout: 60_000 })
|
|
106
|
+
if (!capturedPatch.trim() && diff.stdout.trim()) capturedPatch = diff.stdout
|
|
107
|
+
} catch {
|
|
108
|
+
/* workspace gone or git error → treat as no patch */
|
|
109
|
+
}
|
|
110
|
+
return { passes: capturedPatch.trim() ? 1 : 0, total: 1, errored: 0 }
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
const t0 = Date.now()
|
|
114
|
+
const r = await runAgentic({
|
|
115
|
+
surface: proxy,
|
|
116
|
+
task,
|
|
117
|
+
strategy: refine,
|
|
118
|
+
routerBaseUrl,
|
|
119
|
+
routerKey,
|
|
120
|
+
model: workerModel,
|
|
121
|
+
maxTokens: workerMaxTokens,
|
|
122
|
+
innerTurns,
|
|
123
|
+
budget: budgetShots,
|
|
124
|
+
})
|
|
125
|
+
// Report REAL cost/tokens so the backend-integrity guard sees a real backend
|
|
126
|
+
// rather than a silent-zero stub. A glm-5.2 turn occasionally returns a real
|
|
127
|
+
// patch with an UNPOPULATED usage block (a router telemetry gap on some
|
|
128
|
+
// reasoning-model responses — NOT a stub: the cell made real tool calls and
|
|
129
|
+
// produced a patch). In that gap case report a nominal floor so the stub-guard
|
|
130
|
+
// (artifact + zero usage) cannot abort the whole campaign on a telemetry gap.
|
|
131
|
+
// The lift metric is judge-derived, so a floored count does not distort it; only
|
|
132
|
+
// cost accounting undercounts those few cells (disclosed). No-patch cells return
|
|
133
|
+
// null below and are skipped by the guard's own contract, so this floor only
|
|
134
|
+
// ever applies to a cell that genuinely produced a patch.
|
|
135
|
+
const zeroUsage = (r.tokens.input ?? 0) === 0 && (r.tokens.output ?? 0) === 0
|
|
136
|
+
const hasPatch = capturedPatch.trim().length > 0
|
|
137
|
+
ctx.cost.observe(zeroUsage && hasPatch ? Math.max(r.usd ?? 0, 0.0001) : r.usd ?? 0, workerModel)
|
|
138
|
+
ctx.cost.observeTokens(
|
|
139
|
+
zeroUsage && hasPatch
|
|
140
|
+
? { input: Math.max(r.tokens.input ?? 0, 1), output: Math.max(r.tokens.output ?? 0, 1) }
|
|
141
|
+
: { input: r.tokens.input, output: r.tokens.output },
|
|
142
|
+
)
|
|
143
|
+
const files = capturedPatch ? [...capturedPatch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
|
|
144
|
+
console.log(
|
|
145
|
+
` [agent] ${scenario.id} prompt=${promptText.length}c tools(l/r/e+/e-/run/run!)=${stats.list}/${stats.read}/${stats.edit_ok}/${stats.edit_fail}/${stats.run}/${stats.run_err} ` +
|
|
146
|
+
`patch=${capturedPatch.length}b files=[${files.join(', ') || 'none'}] tok=in:${r.tokens.input}/out:${r.tokens.output} usd=${r.usd} ${Math.round((Date.now() - t0) / 1000)}s` +
|
|
147
|
+
`${zeroUsage ? (hasPatch ? ' [zero-usage telemetry gap: patch kept, usage floored]' : ' [zero-usage cell: empty completion — scored as no-patch]') : ''}`,
|
|
148
|
+
)
|
|
149
|
+
// A cell with no patch produced NO artifact. Return null (not '') so the
|
|
150
|
+
// backend-integrity guard's own contract (`artifact == null → skip`) applies:
|
|
151
|
+
// a glm-4.6 empty-content turn is scored 0 like the baseline, instead of
|
|
152
|
+
// aborting the whole campaign as a false-positive "stub cell". Any cell that
|
|
153
|
+
// DOES produce a patch still returns it and must report real usage or fire.
|
|
154
|
+
return capturedPatch.trim() ? capturedPatch : null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The judge: the OFFICIAL swebench Docker harness. Deterministic FAIL_TO_PASS +
|
|
158
|
+
// PASS_TO_PASS → resolved 0/1. This is the held-out gate's scoring axis.
|
|
159
|
+
const judge: JudgeConfig<string, Scenario> = {
|
|
160
|
+
name: 'swebench-docker',
|
|
161
|
+
dimensions: [{ key: 'resolved', description: 'FAIL_TO_PASS + PASS_TO_PASS resolved by the official swebench Docker harness' }],
|
|
162
|
+
async score({ artifact, scenario }) {
|
|
163
|
+
const patch = String(artifact ?? '')
|
|
164
|
+
if (!patch.trim()) {
|
|
165
|
+
console.log(` [judge] ${scenario.id} resolved=0 (no patch)`)
|
|
166
|
+
return { dimensions: { resolved: 0 }, composite: 0, notes: 'no patch emitted' }
|
|
167
|
+
}
|
|
168
|
+
const bt = byId.get(scenario.id)
|
|
169
|
+
if (!bt) throw new Error(`judge: unknown scenario ${scenario.id}`)
|
|
170
|
+
const s = await adapter.judge(bt, patch)
|
|
171
|
+
console.log(` [judge] ${scenario.id} resolved=${s.resolved ? 1 : 0}`)
|
|
172
|
+
// 1500 chars keeps the whole swebench report JSON (a flat summary object —
|
|
173
|
+
// it has no separate failure section to extract); the old 200 clipped it to
|
|
174
|
+
// an uninformative head, leaving GEPA reflection trace-blind.
|
|
175
|
+
return { dimensions: { resolved: s.resolved ? 1 : 0 }, composite: s.resolved ? 1 : 0, notes: (s.detail ?? '').slice(0, 1500) }
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const profile: AgentProfile = { name: 'swe-agent-glm46', prompt: { systemPrompt: SEED_PROMPT } }
|
|
180
|
+
const proposer = gepaProposer({
|
|
181
|
+
llm: { baseUrl: routerBaseUrl, apiKey: routerKey },
|
|
182
|
+
model: reflectModel,
|
|
183
|
+
target: 'the system prompt of a coding agent that fixes real GitHub bugs via list_files/read_file/edit_file tools',
|
|
184
|
+
maxTokens: reflectMaxTokens,
|
|
185
|
+
temperature: 0.7,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
189
|
+
const holdoutScenarios: Scenario[] = holdoutIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
190
|
+
|
|
191
|
+
const out = await improve(profile, [], {
|
|
192
|
+
surface: 'prompt',
|
|
193
|
+
gate: 'holdout',
|
|
194
|
+
generator: proposer,
|
|
195
|
+
scenarios,
|
|
196
|
+
judge,
|
|
197
|
+
agent,
|
|
198
|
+
// glm-5.2 occasionally returns a real patch with an unpopulated usage block
|
|
199
|
+
// (a router telemetry gap on some reasoning-model responses — NOT a stub: the
|
|
200
|
+
// cell made real tool calls and produced a patch). 'assert' would abort the
|
|
201
|
+
// whole campaign on such a cell; 'warn' logs it and continues. The lift metric
|
|
202
|
+
// (resolved) is judge-derived, so a missing token count does not distort it —
|
|
203
|
+
// only the cost accounting undercounts those cells, which is disclosed.
|
|
204
|
+
expectUsage: 'warn',
|
|
205
|
+
budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency, reps: 1 },
|
|
206
|
+
llm: { baseUrl: routerBaseUrl, apiKey: routerKey, model: reflectModel },
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
console.log('\n═══ RESULT ═══')
|
|
210
|
+
console.log(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
|
|
211
|
+
console.log(`baseline holdout composite = ${out.raw.baseline.compositeMean}`)
|
|
212
|
+
console.log(`winner holdout composite = ${out.raw.winner.compositeMean}`)
|
|
213
|
+
console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
|
|
214
|
+
console.log(`winner per-scenario: ${JSON.stringify(out.raw.winner.perScenario)}`)
|
|
215
|
+
if (out.raw.winner.label) console.log(`winner label : ${out.raw.winner.label}`)
|
|
216
|
+
if (out.raw.winner.rationale) console.log(`winner rationale: ${out.raw.winner.rationale}`)
|
|
217
|
+
|
|
218
|
+
// Per-candidate verdicts on the train set (the "real swebench verdict per candidate").
|
|
219
|
+
for (const gen of out.raw.generations ?? []) {
|
|
220
|
+
console.log(`\n── generation ${gen.record.generationIndex} candidates ──`)
|
|
221
|
+
for (const c of gen.record.candidates) {
|
|
222
|
+
const perScenario = (c as { scenarios?: Array<{ scenarioId: string; composite: number }> }).scenarios ?? []
|
|
223
|
+
const detail = perScenario.map((s) => `${s.scenarioId}=${s.composite}`).join(' ')
|
|
224
|
+
console.log(` candidate ${c.surfaceHash.slice(0, 8)} composite=${c.composite}${c.label ? ` "${c.label}"` : ''} [${detail}]`)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
main().catch((e) => {
|
|
230
|
+
console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
|
|
231
|
+
process.exit(1)
|
|
232
|
+
})
|