@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.
Files changed (262) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -1
  3. package/dist/adapters.d.ts +15 -0
  4. package/dist/adapters.js +43 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/benchmarks/_harness.d.ts +125 -0
  7. package/dist/benchmarks/_harness.js +33 -0
  8. package/dist/benchmarks/_harness.js.map +1 -0
  9. package/dist/benchmarks/aec-bench.d.ts +27 -0
  10. package/dist/benchmarks/aec-bench.js +8 -0
  11. package/dist/benchmarks/aec-bench.js.map +1 -0
  12. package/dist/benchmarks/agentbench.d.ts +16 -0
  13. package/dist/benchmarks/agentbench.js +10 -0
  14. package/dist/benchmarks/agentbench.js.map +1 -0
  15. package/dist/benchmarks/appworld.d.ts +37 -0
  16. package/dist/benchmarks/appworld.js +14 -0
  17. package/dist/benchmarks/appworld.js.map +1 -0
  18. package/dist/benchmarks/bfcl.d.ts +18 -0
  19. package/dist/benchmarks/bfcl.js +10 -0
  20. package/dist/benchmarks/bfcl.js.map +1 -0
  21. package/dist/benchmarks/cad-design.d.ts +45 -0
  22. package/dist/benchmarks/cad-design.js +7 -0
  23. package/dist/benchmarks/cad-design.js.map +1 -0
  24. package/dist/benchmarks/cadbench.d.ts +19 -0
  25. package/dist/benchmarks/cadbench.js +8 -0
  26. package/dist/benchmarks/cadbench.js.map +1 -0
  27. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  28. package/dist/benchmarks/cadgenbench.js +8 -0
  29. package/dist/benchmarks/cadgenbench.js.map +1 -0
  30. package/dist/benchmarks/commit0.d.ts +31 -0
  31. package/dist/benchmarks/commit0.js +10 -0
  32. package/dist/benchmarks/commit0.js.map +1 -0
  33. package/dist/benchmarks/crag.d.ts +14 -0
  34. package/dist/benchmarks/crag.js +9 -0
  35. package/dist/benchmarks/crag.js.map +1 -0
  36. package/dist/benchmarks/dabstep.d.ts +18 -0
  37. package/dist/benchmarks/dabstep.js +10 -0
  38. package/dist/benchmarks/dabstep.js.map +1 -0
  39. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  40. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  41. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  42. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  43. package/dist/benchmarks/finresearchbench.js +8 -0
  44. package/dist/benchmarks/finresearchbench.js.map +1 -0
  45. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  46. package/dist/benchmarks/finsearchcomp.js +7 -0
  47. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  48. package/dist/benchmarks/frames.d.ts +59 -0
  49. package/dist/benchmarks/frames.js +13 -0
  50. package/dist/benchmarks/frames.js.map +1 -0
  51. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  52. package/dist/benchmarks/hotpotqa.js +15 -0
  53. package/dist/benchmarks/hotpotqa.js.map +1 -0
  54. package/dist/benchmarks/humaneval.d.ts +62 -0
  55. package/dist/benchmarks/humaneval.js +17 -0
  56. package/dist/benchmarks/humaneval.js.map +1 -0
  57. package/dist/benchmarks/mind2web.d.ts +41 -0
  58. package/dist/benchmarks/mind2web.js +9 -0
  59. package/dist/benchmarks/mind2web.js.map +1 -0
  60. package/dist/benchmarks/nomiracl.d.ts +15 -0
  61. package/dist/benchmarks/nomiracl.js +9 -0
  62. package/dist/benchmarks/nomiracl.js.map +1 -0
  63. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  64. package/dist/benchmarks/open-rag-bench.js +9 -0
  65. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  66. package/dist/benchmarks/programbench.d.ts +38 -0
  67. package/dist/benchmarks/programbench.js +10 -0
  68. package/dist/benchmarks/programbench.js.map +1 -0
  69. package/dist/benchmarks/rag-shared.d.ts +42 -0
  70. package/dist/benchmarks/rag-shared.js +39 -0
  71. package/dist/benchmarks/rag-shared.js.map +1 -0
  72. package/dist/benchmarks/ragbench.d.ts +16 -0
  73. package/dist/benchmarks/ragbench.js +9 -0
  74. package/dist/benchmarks/ragbench.js.map +1 -0
  75. package/dist/benchmarks/simpleqa.d.ts +64 -0
  76. package/dist/benchmarks/simpleqa.js +11 -0
  77. package/dist/benchmarks/simpleqa.js.map +1 -0
  78. package/dist/benchmarks/swe-bench.d.ts +56 -0
  79. package/dist/benchmarks/swe-bench.js +14 -0
  80. package/dist/benchmarks/swe-bench.js.map +1 -0
  81. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  82. package/dist/benchmarks/t2-ragbench.js +9 -0
  83. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  84. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  85. package/dist/benchmarks/tau-bench-shared.js +10 -0
  86. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  87. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  88. package/dist/benchmarks/tau2-bench.js +11 -0
  89. package/dist/benchmarks/tau2-bench.js.map +1 -0
  90. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  91. package/dist/benchmarks/tau3-banking.js +9 -0
  92. package/dist/benchmarks/tau3-banking.js.map +1 -0
  93. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  94. package/dist/benchmarks/terminal-bench.js +8 -0
  95. package/dist/benchmarks/terminal-bench.js.map +1 -0
  96. package/dist/benchmarks/toollm.d.ts +16 -0
  97. package/dist/benchmarks/toollm.js +10 -0
  98. package/dist/benchmarks/toollm.js.map +1 -0
  99. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  100. package/dist/benchmarks/trata-hedge.js +7 -0
  101. package/dist/benchmarks/trata-hedge.js.map +1 -0
  102. package/dist/benchmarks/types.d.ts +107 -0
  103. package/dist/benchmarks/types.js +1 -0
  104. package/dist/benchmarks/types.js.map +1 -0
  105. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  106. package/dist/benchmarks/webarena-verified.js +10 -0
  107. package/dist/benchmarks/webarena-verified.js.map +1 -0
  108. package/dist/chunk-2PVVP7GN.js +197 -0
  109. package/dist/chunk-2PVVP7GN.js.map +1 -0
  110. package/dist/chunk-2XU6OGEN.js +170 -0
  111. package/dist/chunk-2XU6OGEN.js.map +1 -0
  112. package/dist/chunk-53UPUNBZ.js +325 -0
  113. package/dist/chunk-53UPUNBZ.js.map +1 -0
  114. package/dist/chunk-5H5XV76F.js +240 -0
  115. package/dist/chunk-5H5XV76F.js.map +1 -0
  116. package/dist/chunk-7WSD27QQ.js +118 -0
  117. package/dist/chunk-7WSD27QQ.js.map +1 -0
  118. package/dist/chunk-C7T7WEK2.js +103 -0
  119. package/dist/chunk-C7T7WEK2.js.map +1 -0
  120. package/dist/chunk-CKUVRZ2T.js +251 -0
  121. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  122. package/dist/chunk-HBSWHQNJ.js +30 -0
  123. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  124. package/dist/chunk-HHXFIHXC.js +116 -0
  125. package/dist/chunk-HHXFIHXC.js.map +1 -0
  126. package/dist/chunk-IFAV6KEM.js +276 -0
  127. package/dist/chunk-IFAV6KEM.js.map +1 -0
  128. package/dist/chunk-INNOYXCP.js +387 -0
  129. package/dist/chunk-INNOYXCP.js.map +1 -0
  130. package/dist/chunk-J3KDJNX2.js +182 -0
  131. package/dist/chunk-J3KDJNX2.js.map +1 -0
  132. package/dist/chunk-JRWWGMK7.js +148 -0
  133. package/dist/chunk-JRWWGMK7.js.map +1 -0
  134. package/dist/chunk-JTHWEDEW.js +32 -0
  135. package/dist/chunk-JTHWEDEW.js.map +1 -0
  136. package/dist/chunk-KDIKRJGB.js +120 -0
  137. package/dist/chunk-KDIKRJGB.js.map +1 -0
  138. package/dist/chunk-LRRD7NAG.js +301 -0
  139. package/dist/chunk-LRRD7NAG.js.map +1 -0
  140. package/dist/chunk-ODT47UAY.js +221 -0
  141. package/dist/chunk-ODT47UAY.js.map +1 -0
  142. package/dist/chunk-PA2ZKHJC.js +230 -0
  143. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  144. package/dist/chunk-PUIRNYI7.js +189 -0
  145. package/dist/chunk-PUIRNYI7.js.map +1 -0
  146. package/dist/chunk-PWQVGAJB.js +144 -0
  147. package/dist/chunk-PWQVGAJB.js.map +1 -0
  148. package/dist/chunk-R36V2VP7.js +169 -0
  149. package/dist/chunk-R36V2VP7.js.map +1 -0
  150. package/dist/chunk-R67DFVLO.js +142 -0
  151. package/dist/chunk-R67DFVLO.js.map +1 -0
  152. package/dist/chunk-SEVJPLZC.js +260 -0
  153. package/dist/chunk-SEVJPLZC.js.map +1 -0
  154. package/dist/chunk-SYDW647C.js +318 -0
  155. package/dist/chunk-SYDW647C.js.map +1 -0
  156. package/dist/chunk-TBKU5XQI.js +228 -0
  157. package/dist/chunk-TBKU5XQI.js.map +1 -0
  158. package/dist/chunk-TSWPNOYM.js +147 -0
  159. package/dist/chunk-TSWPNOYM.js.map +1 -0
  160. package/dist/chunk-UAIOHCUK.js +27 -0
  161. package/dist/chunk-UAIOHCUK.js.map +1 -0
  162. package/dist/chunk-UPAMRDX4.js +233 -0
  163. package/dist/chunk-UPAMRDX4.js.map +1 -0
  164. package/dist/chunk-VQRS7VUC.js +342 -0
  165. package/dist/chunk-VQRS7VUC.js.map +1 -0
  166. package/dist/chunk-X3BTXCJ4.js +262 -0
  167. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  168. package/dist/chunk-X5YKXC6V.js +211 -0
  169. package/dist/chunk-X5YKXC6V.js.map +1 -0
  170. package/dist/chunk-Y6O2OCUO.js +130 -0
  171. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  172. package/dist/chunk-YCGY7UIZ.js +208 -0
  173. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  174. package/dist/chunk-Z7ML6L77.js +162 -0
  175. package/dist/chunk-Z7ML6L77.js.map +1 -0
  176. package/dist/chunk-ZEWMTR5M.js +136 -0
  177. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  178. package/dist/index.d.ts +355 -0
  179. package/dist/index.js +1908 -0
  180. package/dist/index.js.map +1 -0
  181. package/package.json +26 -9
  182. package/scripts/run-package-tests.mjs +30 -8
  183. package/scripts/verify-packed-consumer.mjs +12 -1
  184. package/scripts/verify-pier-agent.mts +1 -0
  185. package/src/benchmarks/humaneval.test.mts +122 -0
  186. package/src/benchmarks/humaneval.ts +100 -27
  187. package/src/david-attribution.mts +78 -0
  188. package/src/david-goliath.mts +149 -0
  189. package/src/hev-improve.mts +25 -6
  190. package/src/humaneval-object-ablation.mts +201 -0
  191. package/src/live-improve-campaign-mbpp.mts +641 -0
  192. package/src/live-improve-campaign.mts +500 -0
  193. package/src/mbpp-structural.mts +12 -7
  194. package/src/stream-observe.py +45 -0
  195. package/src/stream-observe.tpl.html +247 -0
  196. package/src/supervisor-arena.mts +816 -0
  197. package/src/swe-arena/analyze.ts +211 -0
  198. package/src/swe-arena/arms.ts +788 -0
  199. package/src/swe-arena/bootstrap-meta.mts +188 -0
  200. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  201. package/src/swe-arena/calibrate.ts +116 -0
  202. package/src/swe-arena/capabilities.mts +76 -0
  203. package/src/swe-arena/capabilities.test.mts +57 -0
  204. package/src/swe-arena/capacity.ts +194 -0
  205. package/src/swe-arena/cell-evidence.mts +405 -0
  206. package/src/swe-arena/cell-evidence.test.mts +248 -0
  207. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  208. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  209. package/src/swe-arena/execution.test.mts +1170 -0
  210. package/src/swe-arena/fixtures/analyze.py +80 -0
  211. package/src/swe-arena/fixtures/excludes.txt +8 -0
  212. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  213. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  214. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  215. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  216. package/src/swe-arena/fixtures/holdout.json +44 -0
  217. package/src/swe-arena/fixtures/instances.json +146 -0
  218. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  219. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  220. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  221. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  222. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  223. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  224. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  225. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  226. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  227. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  228. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  229. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  230. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  231. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  232. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  233. package/src/swe-arena/fixtures.ts +104 -0
  234. package/src/swe-arena/holdout-certify.mts +408 -0
  235. package/src/swe-arena/holdout-certify.test.mts +160 -0
  236. package/src/swe-arena/judge-child.mts +37 -0
  237. package/src/swe-arena/manifest.mts +293 -0
  238. package/src/swe-arena/manifest.test.mts +169 -0
  239. package/src/swe-arena/materialize.ts +142 -0
  240. package/src/swe-arena/outer-loop.mts +2145 -0
  241. package/src/swe-arena/outer-loop.test.mts +696 -0
  242. package/src/swe-arena/parity.test.mts +87 -0
  243. package/src/swe-arena/proc.test.mts +174 -0
  244. package/src/swe-arena/proc.ts +260 -0
  245. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  246. package/src/swe-arena/proposer-fanout.mts +489 -0
  247. package/src/swe-arena/proposer-fanout.test.mts +372 -0
  248. package/src/swe-arena/reconcile.ts +0 -0
  249. package/src/swe-arena/replay.mts +183 -0
  250. package/src/swe-arena/replay.test.mts +300 -0
  251. package/src/swe-arena/run-experiment.mts +361 -0
  252. package/src/swe-arena/run-supervisor.mjs +297 -0
  253. package/src/swe-arena/run-supervisor.test.mts +498 -0
  254. package/src/swe-arena/serialized-judge.ts +414 -0
  255. package/src/swe-arena/types.ts +166 -0
  256. package/src/swe-code-improve.mts +328 -0
  257. package/src/swe-emit-patch.mts +104 -0
  258. package/src/swe-improve.mts +232 -0
  259. package/src/swe-jail.ts +2 -2
  260. package/src/swe-local-proof.mts +169 -0
  261. package/src/swe-repro-calibrate.mts +446 -0
  262. package/src/swe-stream.mts +1497 -0
@@ -0,0 +1,122 @@
1
+ import assert from 'node:assert/strict'
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { describe, it } from 'node:test'
6
+ import { runPythonProgram } from './humaneval'
7
+
8
+ describe('HumanEval Python isolation', () => {
9
+ it('runs the exact program through the resource-capped networkless container', async () => {
10
+ const dir = mkdtempSync(join(tmpdir(), 'humaneval-docker-test-'))
11
+ const fakeDocker = join(dir, 'docker')
12
+ const capture = join(dir, 'capture.jsonl')
13
+ writeFileSync(
14
+ fakeDocker,
15
+ `#!/usr/bin/env node
16
+ const fs = require('node:fs')
17
+ const path = require('node:path')
18
+ const args = process.argv.slice(2)
19
+ if (args[0] === 'rm') process.exit(0)
20
+ if (process.env.FAKE_DOCKER_MISSING === '1') {
21
+ process.stderr.write('docker: Error response from daemon: No such image: python:3.12-slim')
22
+ process.exit(125)
23
+ }
24
+ const mount = args[args.indexOf('-v') + 1]
25
+ const hostDir = mount.slice(0, -':/w:ro'.length)
26
+ fs.appendFileSync(process.env.FAKE_DOCKER_CAPTURE, JSON.stringify({
27
+ args,
28
+ program: fs.readFileSync(path.join(hostDir, 'p.py'), 'utf8'),
29
+ }) + '\\n')
30
+ if (process.env.FAKE_DOCKER_NO_START === '1') {
31
+ process.stderr.write('docker: Error response from daemon: unable to start container process')
32
+ process.exit(125)
33
+ }
34
+ process.stdout.write(args[args.length - 2] + '\\n')
35
+ if (process.env.FAKE_DOCKER_CANDIDATE === 'daemon-text') {
36
+ process.stderr.write('Cannot connect to the Docker daemon')
37
+ process.exit(1)
38
+ }
39
+ if (process.env.FAKE_DOCKER_CANDIDATE === 'exit-125') process.exit(125)
40
+ process.stdout.write('CONTAINER_OK\\n')
41
+ `,
42
+ { mode: 0o755 },
43
+ )
44
+ const originalPath = process.env.PATH
45
+ const originalCapture = process.env.FAKE_DOCKER_CAPTURE
46
+ const originalMissing = process.env.FAKE_DOCKER_MISSING
47
+ const originalCandidate = process.env.FAKE_DOCKER_CANDIDATE
48
+ const originalNoStart = process.env.FAKE_DOCKER_NO_START
49
+ process.env.PATH = `${dir}:${originalPath ?? ''}`
50
+ process.env.FAKE_DOCKER_CAPTURE = capture
51
+ try {
52
+ const program = 'print("exact bytes")\n'
53
+ const result = await runPythonProgram(program, 2_000)
54
+ assert.equal(result.exitCode, 0)
55
+ assert.match(result.stdout, /CONTAINER_OK/)
56
+
57
+ const firstCall = JSON.parse(readFileSync(capture, 'utf8').trim().split('\n')[0]!) as {
58
+ args: string[]
59
+ program: string
60
+ }
61
+ assert.equal(firstCall.program, program)
62
+ assert.deepEqual(firstCall.args.slice(0, 2), ['run', '--rm'])
63
+ assert.ok(firstCall.args.includes('--network=none'))
64
+ assert.ok(firstCall.args.includes('--cpus=1'))
65
+ assert.ok(firstCall.args.includes('--memory=512m'))
66
+ assert.ok(firstCall.args.includes('--pids-limit=64'))
67
+ assert.ok(firstCall.args.includes('--cap-drop=ALL'))
68
+ assert.ok(firstCall.args.includes('--security-opt=no-new-privileges'))
69
+ assert.ok(firstCall.args.includes('--pull=never'))
70
+ assert.ok(firstCall.args.includes('--read-only'))
71
+ assert.equal(
72
+ firstCall.args[firstCall.args.indexOf('--tmpfs') + 1],
73
+ '/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',
74
+ )
75
+ assert.equal(firstCall.args[firstCall.args.indexOf('--user') + 1], '65534:65534')
76
+ assert.match(firstCall.args[firstCall.args.indexOf('-v') + 1] ?? '', /:\/w:ro$/)
77
+ const command = firstCall.args.slice(firstCall.args.indexOf('python:3.12-slim'))
78
+ assert.equal(command[0], 'python:3.12-slim')
79
+ assert.equal(command[1], 'sh')
80
+ assert.equal(command[2], '-c')
81
+ assert.match(command[3] ?? '', /command -v timeout/)
82
+ assert.equal(command[4], 'agent-runtime-checker')
83
+ assert.match(command[5] ?? '', /^__AGENT_RUNTIME_CANDIDATE_STARTED_hev-/)
84
+ assert.equal(command[6], '2s')
85
+
86
+ process.env.FAKE_DOCKER_MISSING = '1'
87
+ await assert.rejects(
88
+ runPythonProgram('print("must not score")\n', 2_000),
89
+ /docker image python:3\.12-slim unavailable/,
90
+ )
91
+
92
+ delete process.env.FAKE_DOCKER_MISSING
93
+ process.env.FAKE_DOCKER_CANDIDATE = 'daemon-text'
94
+ const daemonText = await runPythonProgram('raise SystemExit(1)\n', 2_000)
95
+ assert.equal(daemonText.exitCode, 1)
96
+ assert.match(daemonText.stderr, /Cannot connect to the Docker daemon/)
97
+
98
+ process.env.FAKE_DOCKER_CANDIDATE = 'exit-125'
99
+ const exit125 = await runPythonProgram('raise SystemExit(125)\n', 2_000)
100
+ assert.equal(exit125.exitCode, 125)
101
+
102
+ delete process.env.FAKE_DOCKER_CANDIDATE
103
+ process.env.FAKE_DOCKER_NO_START = '1'
104
+ await assert.rejects(
105
+ runPythonProgram('print("never started")\n', 2_000),
106
+ /did not start the candidate/,
107
+ )
108
+ } finally {
109
+ if (originalPath === undefined) delete process.env.PATH
110
+ else process.env.PATH = originalPath
111
+ if (originalCapture === undefined) delete process.env.FAKE_DOCKER_CAPTURE
112
+ else process.env.FAKE_DOCKER_CAPTURE = originalCapture
113
+ if (originalMissing === undefined) delete process.env.FAKE_DOCKER_MISSING
114
+ else process.env.FAKE_DOCKER_MISSING = originalMissing
115
+ if (originalCandidate === undefined) delete process.env.FAKE_DOCKER_CANDIDATE
116
+ else process.env.FAKE_DOCKER_CANDIDATE = originalCandidate
117
+ if (originalNoStart === undefined) delete process.env.FAKE_DOCKER_NO_START
118
+ else process.env.FAKE_DOCKER_NO_START = originalNoStart
119
+ rmSync(dir, { recursive: true, force: true })
120
+ }
121
+ })
122
+ })
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { execFile } from 'node:child_process'
20
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
20
+ import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
21
21
  import { tmpdir } from 'node:os'
22
22
  import { join } from 'node:path'
23
23
  import { gunzipSync } from 'node:zlib'
@@ -111,19 +111,36 @@ export interface CheckResult {
111
111
  detail?: string
112
112
  }
113
113
 
114
+ export interface PythonProgramResult {
115
+ exitCode: number
116
+ stdout: string
117
+ stderr: string
118
+ }
119
+
114
120
  /** Run one candidate's deployable test program in an isolated container:
115
- * `docker run --rm --network=none -v <tmp>:/w -w /w <img> python /w/p.py`.
121
+ * read-only root, bounded writable `/tmp`, no network, one CPU, and 512 MiB.
116
122
  * Exit 0 → pass. A docker invocation error (binary missing, daemon down, image
117
- * pull failure) is NOT a test failure — it throws so the harness fails loud rather
123
+ * unavailable) is NOT a test failure — it throws so the harness fails loud rather
118
124
  * than scoring every candidate 0 from a broken checker. */
119
125
  let dockerRunSeq = 0
120
126
 
121
- export function runChecker(task: HumanEvalTask, candidate: string): Promise<CheckResult> {
127
+ /** Run arbitrary Python in the same isolated container used by the HumanEval
128
+ * checker. This is also the execution-tool primitive for experiments that let
129
+ * a model test its own snippets; model-written code must never run on the host. */
130
+ export function runPythonProgram(
131
+ program: string,
132
+ timeoutMs = dockerTimeoutMs,
133
+ ): Promise<PythonProgramResult> {
122
134
  const dir = mkdtempSync(join(tmpdir(), 'hev-'))
123
- writeFileSync(join(dir, 'p.py'), buildProgram(task, candidate))
135
+ // Rootless Docker maps container root to a subordinate host uid that cannot
136
+ // traverse mkdtemp's default 0700 directory. The directory contains only the
137
+ // disposable candidate script and is mounted read-only into the container.
138
+ chmodSync(dir, 0o755)
139
+ writeFileSync(join(dir, 'p.py'), program, { mode: 0o644 })
124
140
  // Unique container name so we can force-reap it regardless of the docker client's state.
125
141
  const name = `hev-${process.pid}-${dockerRunSeq++}`
126
- return new Promise<CheckResult>((resolvePromise, reject) => {
142
+ const startMarker = `__AGENT_RUNTIME_CANDIDATE_STARTED_${name}__`
143
+ return new Promise<PythonProgramResult>((resolvePromise, reject) => {
127
144
  let settled = false
128
145
  const cleanup = () => {
129
146
  rmSync(dir, { recursive: true, force: true })
@@ -132,7 +149,7 @@ export function runChecker(task: HumanEvalTask, candidate: string): Promise<Chec
132
149
  // the name is unique, so no reuse race).
133
150
  execFile('docker', ['rm', '-f', name], () => {})
134
151
  }
135
- const finish = (res: CheckResult) => {
152
+ const finish = (res: PythonProgramResult) => {
136
153
  if (settled) return
137
154
  settled = true
138
155
  clearTimeout(backstop)
@@ -146,56 +163,106 @@ export function runChecker(task: HumanEvalTask, candidate: string): Promise<Chec
146
163
  cleanup()
147
164
  reject(e)
148
165
  }
149
- // A hung container can leave the docker client stuck forwarding SIGTERM, so the
150
- // execFile callback never fires. This guarantees resolution (and reap) after the
151
- // timeout, independent of the callback.
152
- const backstop = setTimeout(() => finish({ pass: 0 }), dockerTimeoutMs + 3000)
166
+ // Candidate timeouts are enforced inside the container. If the outer Docker
167
+ // client or daemon misses this larger deadline, that is infrastructure failure,
168
+ // not a wrong answer.
169
+ const outerTimeoutMs = timeoutMs + 3_000
170
+ const backstop = setTimeout(
171
+ () => fail(new Error(`docker checker did not return within ${outerTimeoutMs + 3_000}ms`)),
172
+ outerTimeoutMs + 3_000,
173
+ )
174
+ const inContainerSeconds = Math.max(1, Math.ceil(timeoutMs / 1_000))
153
175
  execFile(
154
176
  'docker',
155
177
  [
156
178
  'run',
157
179
  '--rm',
180
+ '--pull=never',
158
181
  '--name',
159
182
  name,
160
183
  '--network=none',
161
184
  '--cpus=1',
162
185
  '--memory=512m',
186
+ '--pids-limit=64',
187
+ '--cap-drop=ALL',
188
+ '--security-opt=no-new-privileges',
189
+ '--read-only',
190
+ '--tmpfs',
191
+ '/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',
192
+ '--user',
193
+ '65534:65534',
163
194
  '-v',
164
195
  `${dir}:/w:ro`,
165
196
  '-w',
166
197
  '/w',
167
198
  dockerImage,
168
- 'python',
169
- '/w/p.py',
199
+ 'sh',
200
+ '-c',
201
+ 'command -v timeout >/dev/null 2>&1 && command -v python >/dev/null 2>&1 && [ -r /w/p.py ] || exit 126; printf "%s\\n" "$1"; exec timeout -s KILL "$2" python /w/p.py',
202
+ 'agent-runtime-checker',
203
+ startMarker,
204
+ `${inContainerSeconds}s`,
170
205
  ],
171
- { timeout: dockerTimeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
172
- (err, _stdout, stderr) => {
206
+ { timeout: outerTimeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
207
+ (err, stdout, stderr) => {
208
+ const markerLine = `${startMarker}\n`
209
+ const candidateStarted = stdout?.startsWith(markerLine) === true
210
+ const candidateStdout = candidateStarted ? stdout.slice(markerLine.length) : (stdout ?? '')
173
211
  if (err) {
174
- const e = err as NodeJS.ErrnoException & { killed?: boolean; code?: number | string }
212
+ const e = err as Error & { killed?: boolean; code?: number | string }
175
213
  if (e.code === 'ENOENT') {
176
214
  fail(new Error('docker binary not found on PATH — cannot run the deployable checker'))
177
215
  return
178
216
  }
179
- if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(stderr)) {
180
- fail(new Error(`docker daemon unreachable: ${stderr.slice(0, 200)}`))
217
+ if (e.killed) {
218
+ fail(new Error(`docker checker exceeded its ${outerTimeoutMs}ms outer timeout`))
181
219
  return
182
220
  }
183
- if (/(unable to find image|pull access denied|manifest unknown|error response from daemon).*(pull|repository|registry)/i.test(stderr)) {
184
- fail(new Error(`docker image ${dockerImage} unavailable: ${stderr.slice(0, 200)}`))
221
+ if (!candidateStarted) {
222
+ if (
223
+ /cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(
224
+ stderr,
225
+ )
226
+ ) {
227
+ fail(new Error(`docker daemon unreachable: ${stderr.slice(0, 200)}`))
228
+ return
229
+ }
230
+ if (/unable to find image|no such image|pull access denied|manifest unknown/i.test(stderr)) {
231
+ fail(new Error(`docker image ${dockerImage} unavailable: ${stderr.slice(0, 200)}`))
232
+ return
233
+ }
234
+ fail(
235
+ new Error(
236
+ `docker checker did not start the candidate: ${stderr.slice(0, 200) || e.message}`,
237
+ ),
238
+ )
185
239
  return
186
240
  }
187
- // killed-by-timeout or a non-zero exit (assert failure / error) are genuine
188
- // test FAILURES — score 0, do not throw. Carry the stderr tail as the
189
- // execution-grounded failure detail (empty ⇒ timeout/SIGKILL left no output).
190
- finish({ pass: 0, detail: (stderr || '').slice(-600) || 'timed out (no output)' })
241
+ const exitCode = typeof e.code === 'number' ? e.code : 1
242
+ finish({
243
+ exitCode,
244
+ stdout: candidateStdout,
245
+ stderr: stderr ?? '',
246
+ })
191
247
  return
192
248
  }
193
- finish({ pass: 1 })
249
+ if (!candidateStarted) {
250
+ fail(new Error('docker checker exited without starting the candidate'))
251
+ return
252
+ }
253
+ finish({ exitCode: 0, stdout: candidateStdout, stderr: stderr ?? '' })
194
254
  },
195
255
  )
196
256
  })
197
257
  }
198
258
 
259
+ export async function runChecker(task: HumanEvalTask, candidate: string): Promise<CheckResult> {
260
+ const result = await runPythonProgram(buildProgram(task, candidate))
261
+ return result.exitCode === 0
262
+ ? { pass: 1 }
263
+ : { pass: 0, detail: result.stderr.slice(-600) || 'timed out (no output)' }
264
+ }
265
+
199
266
  /** A HumanEval task carries its checker inputs in metadata so the deterministic
200
267
  * judge can rebuild the deployable program from a `BenchTask` alone. */
201
268
  interface HumanEvalMeta extends Record<string, unknown> {
@@ -229,14 +296,20 @@ export function createHumanEvalAdapter(): BenchmarkAdapter {
229
296
  return {
230
297
  name: 'humaneval',
231
298
  async preflight() {
232
- // The judge is the only hard dependency; it fails loud on a missing/broken
233
- // docker, so a cheap presence check here gives an earlier, clearer signal.
299
+ // Prove the daemon and exact local image before any model call. Scoring uses
300
+ // --pull=never so a registry or cold pull cannot become a candidate outcome.
234
301
  await new Promise<void>((resolve, reject) => {
235
302
  execFile('docker', ['version', '--format', '{{.Server.Version}}'], (err) => {
236
303
  if (err) reject(new Error('HumanEval judge needs a running Docker daemon (python:3.12-slim, --network=none)'))
237
304
  else resolve()
238
305
  })
239
306
  })
307
+ await new Promise<void>((resolve, reject) => {
308
+ execFile('docker', ['image', 'inspect', dockerImage], (err) => {
309
+ if (err) reject(new Error(`HumanEval judge needs the cached Docker image ${dockerImage}`))
310
+ else resolve()
311
+ })
312
+ })
240
313
  },
241
314
  async loadTasks(opts?: LoadOptions) {
242
315
  const offset = Number(process.env.OFFSET ?? 0)
@@ -0,0 +1,78 @@
1
+ /**
2
+ * DAVID mechanism attribution — decompose the cheap-model harness's held-out
3
+ * accuracy into what SAMPLING buys vs what VERIFICATION-SELECTION buys, so a
4
+ * David-Goliath win is credited to the right lever (not just best-of-N luck).
5
+ *
6
+ * For each task, generate N candidate solutions + the model's own tests, then
7
+ * report four numbers on the HIDDEN test:
8
+ * pass@1 — first candidate (no harness).
9
+ * mean-cand — expected accuracy of a RANDOM candidate (sampling floor).
10
+ * oracle@N — a correct candidate exists among the N (ceiling of selection).
11
+ * verify-select — the candidate the self-tests picked (the actual David).
12
+ * verify-select − mean-cand = what VERIFICATION adds over blind sampling;
13
+ * oracle@N − verify-select = the selection gap left on the table.
14
+ *
15
+ * Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant N=8 T=5 NTASKS=60 \
16
+ * node_modules/.bin/tsx src/david-attribution.mts
17
+ */
18
+ import { execFile } from 'node:child_process'
19
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
20
+ import { tmpdir } from 'node:os'
21
+ import { join } from 'node:path'
22
+ import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
23
+
24
+ const KEY = process.env.TANGLE_API_KEY!
25
+ const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
26
+ const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
27
+ const N = Number(process.env.N ?? 8)
28
+ const T = Number(process.env.T ?? 5)
29
+ const NTASKS = Number(process.env.NTASKS ?? 60)
30
+ const CONC = Number(process.env.CONCURRENCY ?? 6)
31
+
32
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
33
+ async function chat(messages: { role: string; content: string }[], temp: number): Promise<string> {
34
+ for (let a = 0; ; a++) {
35
+ try {
36
+ const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model: DAVID, messages, temperature: temp, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
37
+ if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
38
+ if (!r.ok) return ''
39
+ return (((await r.json()) as { choices?: { message?: { content?: string } }[] }).choices?.[0]?.message?.content) ?? ''
40
+ } catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
41
+ }
42
+ }
43
+ const exec = (f: string, a: string[], o: object) => new Promise<number>((res) => execFile(f, a, { ...o, maxBuffer: 8e6 }, (e) => res((e as { code?: number } | null)?.code ?? (e ? 1 : 0))))
44
+ async function runPy(p: string): Promise<boolean> { const d = mkdtempSync(join(tmpdir(), 'da-')); try { writeFileSync(join(d, 'p.py'), p); return (await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: 6000 })) === 0 } finally { rmSync(d, { recursive: true, force: true }) } }
45
+ const SOLVE = 'Expert Python. Output the COMPLETE function in one ```python block, no prose, no tests.'
46
+ const genSol = async (t: HumanEvalTask, temp: number) => extractCode(await chat([{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp))
47
+ async function genTests(t: HumanEvalTask): Promise<string[]> {
48
+ const b = extractCode(await chat([{ role: 'system', content: 'Write Python assert unit tests. Output ONLY a ```python block of `assert <entry>(...) == ...` lines. No function, no prose.' }, { role: 'user', content: `entry: ${t.entryPoint}\n\n\`\`\`python\n${t.prompt}\`\`\`` }], 0.4))
49
+ return b.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
50
+ }
51
+ const judge = async (t: HumanEvalTask, code: string) => code.trim() ? runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`) : false
52
+ async function scoreTests(code: string, tests: string[]): Promise<number> { if (!code.trim() || !tests.length) return 0; let p = 0; for (const a of tests) if (await runPy(`${code}\n\n${a}\n`)) p++; return p }
53
+ async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2) => Promise<R>): Promise<R[]> { const o = new Array<R>(xs.length); let i = 0; await Promise.all(Array.from({ length: n }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!) } })); return o }
54
+
55
+ async function main(): Promise<void> {
56
+ const tasks = await loadHumanEval(NTASKS, 0)
57
+ console.error(`=== ATTRIBUTION · ${DAVID} · N=${N} sols + ${T} tests · n=${tasks.length} ===`)
58
+ let done = 0
59
+ const rows = await pool(tasks, CONC, async (t) => {
60
+ const cands = (await Promise.all(Array.from({ length: N }, () => genSol(t, 0.7)))).filter((c) => c.trim())
61
+ if (!cands.length) return { p1: 0, mean: 0, oracle: 0, sel: 0 }
62
+ const tests = await genTests(t)
63
+ const hidden = await Promise.all(cands.map((c) => judge(t, c))) // hidden-test pass per candidate (for attribution only)
64
+ const selScores = tests.length ? await Promise.all(cands.map((c) => scoreTests(c, tests))) : cands.map(() => 0)
65
+ let bi = 0; for (let i = 1; i < cands.length; i++) if (selScores[i]! > selScores[bi]! || (selScores[i]! === selScores[bi]! && cands[i]!.length > cands[bi]!.length)) bi = i
66
+ if (++done % 15 === 0) console.error(` ${done}/${tasks.length}`)
67
+ return { p1: hidden[0] ? 1 : 0, mean: hidden.filter(Boolean).length / cands.length, oracle: hidden.some(Boolean) ? 1 : 0, sel: hidden[bi] ? 1 : 0 }
68
+ })
69
+ const n = rows.length, avg = (f: (r: typeof rows[number]) => number) => (rows.reduce((s, r) => s + f(r), 0) / n) * 100
70
+ console.log('\n=== ATTRIBUTION (held-out) ===')
71
+ console.log(` pass@1 (no harness) : ${avg((r) => r.p1).toFixed(1)}%`)
72
+ console.log(` mean random candidate : ${avg((r) => r.mean).toFixed(1)}% (sampling floor)`)
73
+ console.log(` verify-select (DAVID) : ${avg((r) => r.sel).toFixed(1)}%`)
74
+ console.log(` oracle@N (a correct exists): ${avg((r) => r.oracle).toFixed(1)}% (selection ceiling)`)
75
+ console.log(` --> verification adds over random sampling: +${(avg((r) => r.sel) - avg((r) => r.mean)).toFixed(1)}pp`)
76
+ console.log(` --> selection gap still on table (oracle-select): ${(avg((r) => r.oracle) - avg((r) => r.sel)).toFixed(1)}pp`)
77
+ }
78
+ main().catch((e) => { console.error('MAIN:', e instanceof Error ? e.stack : e); process.exit(1) })
@@ -0,0 +1,149 @@
1
+ /**
2
+ * DAVID vs GOLIATH — the program's northstar, tested head-on: does a CHEAP model
3
+ * with a self-verification harness beat a FRONTIER model running solo, at EQUAL OR
4
+ * LOWER dollar cost, on held-out code?
5
+ *
6
+ * GOLIATH — a strong model, ONE shot. The "just use the big model" baseline.
7
+ * DAVID — a cheap/weak model + test-time compute: generate N candidate
8
+ * solutions AND M of its own unit tests, EXECUTE every candidate
9
+ * against the generated tests, and submit the candidate that passes
10
+ * the most (CodeT-style execution self-selection). No ground-truth
11
+ * test is ever used to select — only the model's own generated tests.
12
+ *
13
+ * Both are graded by the HIDDEN HumanEval test (never shown). Cost is the real
14
+ * token spend × the router-reported/priced rate per arm. The win condition is a
15
+ * Pareto beat: David's held-out pass rate >= Goliath's AND David's $ <= Goliath's.
16
+ * Mechanism under test: EXECUTION-BASED VERIFICATION is the lever that lets a weak
17
+ * generator punch above its solo weight — the standing "verification is live" claim
18
+ * at its most dramatic. Paired McNemar on per-task discordant pairs for significance.
19
+ *
20
+ * Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant GOLIATH=anthropic/claude-haiku-4-5-20251001 \
21
+ * N=8 T=5 NTASKS=164 REPS=2 node_modules/.bin/tsx src/david-goliath.mts
22
+ */
23
+ import { execFile } from 'node:child_process'
24
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
25
+ import { tmpdir } from 'node:os'
26
+ import { join } from 'node:path'
27
+ import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
28
+
29
+ const KEY = process.env.TANGLE_API_KEY
30
+ if (!KEY) throw new Error('TANGLE_API_KEY required')
31
+ const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
32
+ const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
33
+ const GOLIATH = process.env.GOLIATH ?? 'anthropic/claude-haiku-4-5-20251001'
34
+ const N = Number(process.env.N ?? 8) // David candidate solutions
35
+ const T = Number(process.env.T ?? 5) // David generated tests
36
+ const NTASKS = Number(process.env.NTASKS ?? 164)
37
+ const REPS = Number(process.env.REPS ?? 2)
38
+ const CONC = Number(process.env.CONCURRENCY ?? 6)
39
+ const EXEC_TIMEOUT = Number(process.env.EXEC_TIMEOUT_MS ?? 6000)
40
+
41
+ // Approx $/1M tokens (in,out) for cost accounting — the router does not price
42
+ // every model inline, so use public rates; a cheap/frontier gap of ~20-30x is the
43
+ // point, and the ratio is robust to small rate errors. Override via PRICES env.
44
+ const PRICES: Record<string, [number, number]> = {
45
+ 'groq/llama-3.1-8b-instant': [0.05, 0.08],
46
+ 'google/gemini-2.5-flash-lite': [0.10, 0.40],
47
+ 'openai/gpt-4o-mini': [0.15, 0.60],
48
+ 'anthropic/claude-haiku-4-5-20251001': [1.0, 5.0],
49
+ 'glm-5.2': [0.60, 2.20],
50
+ }
51
+ const priceOf = (m: string) => PRICES[m] ?? [0.5, 1.5]
52
+
53
+ interface Usage { in: number; out: number }
54
+ const zero = (): Usage => ({ in: 0, out: 0 })
55
+ const addU = (a: Usage, b: Usage) => { a.in += b.in; a.out += b.out }
56
+ const usd = (m: string, u: Usage) => { const [pi, po] = priceOf(m); return (u.in * pi + u.out * po) / 1e6 }
57
+
58
+ async function chat(model: string, messages: { role: string; content: string }[], temperature: number, usage: Usage): Promise<string> {
59
+ for (let a = 0; ; a++) {
60
+ try {
61
+ const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model, messages, temperature, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
62
+ if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
63
+ if (!r.ok) return ''
64
+ const j = (await r.json()) as { choices?: { message?: { content?: string } }[]; usage?: { prompt_tokens?: number; completion_tokens?: number } }
65
+ addU(usage, { in: j.usage?.prompt_tokens ?? 0, out: j.usage?.completion_tokens ?? 0 })
66
+ return j.choices?.[0]?.message?.content ?? ''
67
+ } catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
68
+ }
69
+ }
70
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
71
+ const exec = (file: string, args: string[], o: object) => new Promise<{ code: number; stdout: string }>((res) => execFile(file, args, { ...o, maxBuffer: 8 * 1024 * 1024 }, (e, stdout) => res({ code: (e as { code?: number } | null)?.code ?? (e ? 1 : 0), stdout: String(stdout) })))
72
+ async function runPy(program: string): Promise<{ ok: boolean }> {
73
+ const d = mkdtempSync(join(tmpdir(), 'dg-'))
74
+ try { writeFileSync(join(d, 'p.py'), program); const r = await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: EXEC_TIMEOUT }); return { ok: r.code === 0 } } finally { rmSync(d, { recursive: true, force: true }) }
75
+ }
76
+
77
+ const SOLVE = 'You are an expert Python programmer. Output the COMPLETE function (signature + body + imports) in a single ```python block. No prose, no tests.'
78
+ async function genSolution(model: string, t: HumanEvalTask, temp: number, u: Usage): Promise<string> {
79
+ return extractCode(await chat(model, [{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp, u))
80
+ }
81
+ // David writes its OWN tests (never sees the hidden test). Parse assert lines.
82
+ async function genTests(model: string, t: HumanEvalTask, u: Usage): Promise<string[]> {
83
+ const reply = await chat(model, [
84
+ { role: 'system', content: 'Write Python assert-based unit tests for the described function. Output ONLY a ```python block of standalone `assert <entry>(...) == ...` lines (at least a few, covering normal + edge cases). No function definition, no prose.' },
85
+ { role: 'user', content: `Function to test (entry point: ${t.entryPoint}):\n\n\`\`\`python\n${t.prompt}\`\`\`` },
86
+ ], 0.4, u)
87
+ const block = extractCode(reply) || reply
88
+ return block.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
89
+ }
90
+ // Hidden held-out judge — the truth. Never used for selection.
91
+ async function judge(t: HumanEvalTask, code: string): Promise<boolean> {
92
+ if (!code.trim()) return false
93
+ return (await runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`)).ok
94
+ }
95
+ // David's self-selection: score each candidate by how many of ITS OWN tests it passes.
96
+ async function scoreOnTests(code: string, tests: string[]): Promise<number> {
97
+ if (!code.trim() || tests.length === 0) return 0
98
+ let pass = 0
99
+ // one program per test keeps a crash on one test from voiding the rest
100
+ for (const a of tests) if ((await runPy(`${code}\n\n${a}\n`)).ok) pass++
101
+ return pass
102
+ }
103
+
104
+ async function davidArm(t: HumanEvalTask, u: Usage): Promise<string> {
105
+ const cands = (await Promise.all(Array.from({ length: N }, () => genSolution(DAVID, t, 0.7, u)))).filter((c) => c.trim())
106
+ if (cands.length === 0) return ''
107
+ const tests = await genTests(DAVID, t, u)
108
+ if (tests.length === 0) return cands[0]! // no verifier signal → first sample
109
+ const scored = await Promise.all(cands.map(async (c) => ({ c, s: await scoreOnTests(c, tests) })))
110
+ scored.sort((a, b) => b.s - a.s || b.c.length - a.c.length)
111
+ return scored[0]!.c
112
+ }
113
+ const goliathArm = (t: HumanEvalTask, u: Usage) => genSolution(GOLIATH, t, 0.2, u)
114
+
115
+ async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2, i: number) => Promise<R>): Promise<R[]> {
116
+ const o = new Array<R>(xs.length); let i = 0
117
+ await Promise.all(Array.from({ length: Math.min(n, xs.length) }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!, k) } }))
118
+ return o
119
+ }
120
+ function mcnemar(b: number, c: number): number { const n = b + c; if (n === 0) return 1; const k = Math.min(b, c); const lf = (x: number) => { let s = 0; for (let i = 2; i <= x; i++) s += Math.log(i); return s }; let tl = 0; for (let i = 0; i <= k; i++) tl += Math.exp(lf(n) - lf(i) - lf(n - i) - n * Math.log(2)); return Math.min(1, 2 * tl) }
121
+
122
+ async function main(): Promise<void> {
123
+ if (['1', 'true'].includes((process.env.SMOKE ?? '').toLowerCase())) { console.error('SMOKE ok: david-goliath loaded'); return }
124
+ const tasks = await loadHumanEval(NTASKS, 0)
125
+ console.error(`=== DAVID(${DAVID}, N=${N} sols + ${T} self-tests) vs GOLIATH(${GOLIATH}, 1 shot) · HumanEval n=${tasks.length} · reps=${REPS} ===`)
126
+ const dU = zero(), gU = zero()
127
+ const units = tasks.flatMap((task) => Array.from({ length: REPS }, () => task))
128
+ let done = 0
129
+ const res = await pool(units, CONC, async (task) => {
130
+ const safe = async (fn: () => Promise<string>) => { try { return await fn() } catch { return '' } }
131
+ const [dCode, gCode] = await Promise.all([safe(() => davidArm(task, dU)), safe(() => goliathArm(task, gU))])
132
+ const [d, g] = await Promise.all([judge(task, dCode), judge(task, gCode)])
133
+ if (++done % 20 === 0) console.error(` ${done}/${units.length} units`)
134
+ return { d, g }
135
+ })
136
+ const n = res.length, dPass = res.filter((r) => r.d).length, gPass = res.filter((r) => r.g).length
137
+ const b = res.filter((r) => r.d && !r.g).length, c = res.filter((r) => !r.d && r.g).length
138
+ const p = mcnemar(b, c)
139
+ const dCost = usd(DAVID, dU), gCost = usd(GOLIATH, gU)
140
+ console.log('\n=== RESULT (held-out HumanEval) ===')
141
+ console.log(` GOLIATH ${GOLIATH} solo : ${gPass}/${n} = ${(gPass / n * 100).toFixed(1)}% $${gCost.toFixed(4)}`)
142
+ console.log(` DAVID ${DAVID} + verify: ${dPass}/${n} = ${(dPass / n * 100).toFixed(1)}% $${dCost.toFixed(4)}`)
143
+ console.log(` accuracy: David ${dPass >= gPass ? '>=' : '<'} Goliath (${(dPass / n * 100).toFixed(1)} vs ${(gPass / n * 100).toFixed(1)}); paired McNemar David-only=${b} Goliath-only=${c} p=${p.toFixed(4)}`)
144
+ console.log(` cost: David is ${(gCost / Math.max(dCost, 1e-9)).toFixed(1)}x CHEAPER ($${dCost.toFixed(4)} vs $${gCost.toFixed(4)})`)
145
+ const paretoBeat = dPass >= gPass && dCost <= gCost
146
+ const sigBeat = dPass > gPass && p < 0.05
147
+ console.log(` VERDICT: ${sigBeat ? 'DAVID SIGNIFICANTLY BEATS GOLIATH' : paretoBeat ? 'DAVID PARETO-DOMINATES (>= accuracy, <= cost)' : dPass >= gPass ? 'David matches accuracy (check cost)' : 'Goliath wins accuracy'}`)
148
+ }
149
+ main().catch((e) => { console.error('MAIN:', e instanceof Error ? (e.stack ?? e.message) : e); process.exit(1) })
@@ -65,8 +65,11 @@ async function main(): Promise<void> {
65
65
  const trainN = Number(process.env.TRAIN_N ?? 12)
66
66
  const holdoutN = Number(process.env.HOLDOUT_N ?? 12)
67
67
  const offset = Number(process.env.OFFSET ?? 80)
68
- const generations = Number(process.env.GENERATIONS ?? 1)
69
- const population = Number(process.env.POPULATION ?? 2)
68
+ // generations=1 never exercises the GEPA Pareto/combine path (the frontier
69
+ // needs >=1 completed generation before combine can fire) — default to a
70
+ // multi-generation budget so the default run measures the full loop.
71
+ const generations = Number(process.env.GENERATIONS ?? 6)
72
+ const population = Number(process.env.POPULATION ?? 4)
70
73
  const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 6000)
71
74
  const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 8000)
72
75
  const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 4)
@@ -115,9 +118,21 @@ async function main(): Promise<void> {
115
118
  console.log(` [judge] ${scenario.id} pass=0 (empty)`)
116
119
  return { dimensions: { pass: 0 }, composite: 0, notes: 'empty' }
117
120
  }
118
- const { pass } = await runChecker(t, code)
121
+ const { pass, detail } = await runChecker(t, code)
119
122
  console.log(` [judge] ${scenario.id} pass=${pass}`)
120
- return { dimensions: { pass }, composite: pass, notes: pass === 1 ? 'passed' : 'failed' }
123
+ if (pass === 1) return { dimensions: { pass }, composite: pass, notes: 'passed' }
124
+ // Trajectory-grounded failure note: the checker's traceback/assertion tail
125
+ // plus the model's own emitted code, so GEPA reflection sees WHAT failed and
126
+ // WHAT the model wrote — not just the word 'failed'. The candidate's full
127
+ // raw reply additionally reaches the proposer via the campaign breakdown's
128
+ // `emitted` field (carried automatically from the string artifact).
129
+ const traceback = (detail ?? 'checker produced no output (timeout or silent non-zero exit)').slice(-800)
130
+ const excerpt = code.slice(0, 700)
131
+ return {
132
+ dimensions: { pass },
133
+ composite: pass,
134
+ notes: `${traceback}\n--- emitted code (first 700 chars) ---\n${excerpt}`,
135
+ }
121
136
  },
122
137
  }
123
138
 
@@ -146,12 +161,17 @@ async function main(): Promise<void> {
146
161
  judge,
147
162
  agent,
148
163
  expectUsage: 'warn',
164
+ // rawTraceContext stays OFF deliberately: it swaps the distilled findings for
165
+ // filesystem paths + grep/cat instructions (rawTraceDistiller), which only a
166
+ // coding harness can execute. This run's proposer is prompt-tier (gepaProposer
167
+ // — a single LLM call that cannot run grep), so the trace evidence arrives via
168
+ // the judge's traceback notes + the breakdown's `emitted` excerpt instead.
149
169
  budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency, reps: 1 },
150
170
  llm: { baseUrl: reflectBase, apiKey: reflectKey, model: reflectModel },
151
171
  })
152
172
 
153
173
  console.log('\n═══ RESULT ═══')
154
- console.log(`decision=${out.decision} lift=${out.lift}`)
174
+ console.log(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
155
175
  console.log(`baseline holdout pass-rate = ${out.raw.baseline.compositeMean}`)
156
176
  console.log(`winner holdout pass-rate = ${out.raw.winner.compositeMean}`)
157
177
  console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
@@ -160,7 +180,6 @@ async function main(): Promise<void> {
160
180
  if ((out.raw.winner as { surface?: unknown }).surface) {
161
181
  console.log(`winner instruction:\n${String((out.raw.winner as { surface?: unknown }).surface).slice(0, 1200)}`)
162
182
  }
163
- console.log(`live instruction unchanged: ${profile.prompt?.systemPrompt}`)
164
183
  }
165
184
 
166
185
  main().catch((e) => {