@tangle-network/agent-bench 0.3.6 → 0.3.8

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 (265) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/dist/adapters.js +24 -24
  4. package/dist/benchmarks/_harness.d.ts +1 -1
  5. package/dist/benchmarks/_harness.js +1 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/humaneval.d.ts +10 -1
  16. package/dist/benchmarks/humaneval.js +5 -3
  17. package/dist/benchmarks/nomiracl.js +2 -2
  18. package/dist/benchmarks/open-rag-bench.js +2 -2
  19. package/dist/benchmarks/programbench.js +2 -2
  20. package/dist/benchmarks/ragbench.js +2 -2
  21. package/dist/benchmarks/swe-bench.js +2 -2
  22. package/dist/benchmarks/t2-ragbench.js +2 -2
  23. package/dist/benchmarks/tau-bench-shared.js +2 -2
  24. package/dist/benchmarks/tau2-bench.js +3 -3
  25. package/dist/benchmarks/tau3-banking.js +3 -3
  26. package/dist/benchmarks/terminal-bench.js +2 -2
  27. package/dist/benchmarks/toollm.js +2 -2
  28. package/dist/benchmarks/webarena-verified.js +2 -2
  29. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  30. package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
  31. package/dist/chunk-5H5XV76F.js.map +1 -0
  32. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  33. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  34. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  35. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  36. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  37. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  38. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  39. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  40. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  41. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  42. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  43. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  44. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  45. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  46. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  47. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  48. package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
  49. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  50. package/dist/chunk-WSKWVEQB.js.map +1 -0
  51. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  52. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  53. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  54. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  55. package/dist/index.js +24 -24
  56. package/package.json +6 -5
  57. package/scripts/run-package-tests.mjs +30 -8
  58. package/scripts/verify-packed-consumer.mjs +1 -1
  59. package/scripts/verify-pier-agent.mts +1 -0
  60. package/src/benchmarks/_harness.ts +20 -2
  61. package/src/benchmarks/humaneval.test.mts +122 -0
  62. package/src/benchmarks/humaneval.ts +100 -27
  63. package/src/david-attribution.mts +78 -0
  64. package/src/david-goliath.mts +149 -0
  65. package/src/hev-improve.mts +25 -6
  66. package/src/humaneval-object-ablation.mts +201 -0
  67. package/src/live-improve-campaign-mbpp.mts +641 -0
  68. package/src/live-improve-campaign.mts +500 -0
  69. package/src/mbpp-structural.mts +12 -7
  70. package/src/quant-arena/README.md +144 -0
  71. package/src/quant-arena/backtest.test.mts +135 -0
  72. package/src/quant-arena/backtest.ts +218 -0
  73. package/src/quant-arena/data.test.mts +44 -0
  74. package/src/quant-arena/data.ts +141 -0
  75. package/src/quant-arena/driver.test.mts +253 -0
  76. package/src/quant-arena/driver.ts +219 -0
  77. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  78. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  83. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  84. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  85. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  86. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  87. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  88. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  89. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  94. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  95. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  96. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  97. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  98. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  99. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  100. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  101. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  102. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  103. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  104. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  105. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  106. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  107. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  108. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  109. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  110. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  111. package/src/quant-arena/holdout-certify.mts +206 -0
  112. package/src/quant-arena/holdout-certify.test.mts +82 -0
  113. package/src/quant-arena/leak-audit.test.mts +79 -0
  114. package/src/quant-arena/leak-audit.ts +95 -0
  115. package/src/quant-arena/make-fixtures.mts +161 -0
  116. package/src/quant-arena/multiplicity.test.mts +68 -0
  117. package/src/quant-arena/multiplicity.ts +87 -0
  118. package/src/quant-arena/nautilus-certify.ts +31 -0
  119. package/src/quant-arena/oms.ts +90 -0
  120. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  121. package/src/quant-arena/python/pyproject.toml +8 -0
  122. package/src/quant-arena/python/uv.lock +1297 -0
  123. package/src/quant-arena/python/vbt-worker.py +192 -0
  124. package/src/quant-arena/quant-loop.mts +813 -0
  125. package/src/quant-arena/quant-loop.test.mts +75 -0
  126. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  127. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  128. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  129. package/src/quant-arena/types.ts +133 -0
  130. package/src/quant-arena/vbt-client.ts +321 -0
  131. package/src/quant-arena/vbt-parity.test.mts +183 -0
  132. package/src/quant-arena/windows.test.mts +45 -0
  133. package/src/quant-arena/windows.ts +54 -0
  134. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  135. package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
  136. package/src/rollout-ledger/settle-capture.mts +442 -0
  137. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  138. package/src/stream-observe.py +45 -0
  139. package/src/stream-observe.tpl.html +247 -0
  140. package/src/supervisor-arena.mts +816 -0
  141. package/src/swe-arena/activation.mts +228 -0
  142. package/src/swe-arena/activation.test.mts +303 -0
  143. package/src/swe-arena/analyze.ts +211 -0
  144. package/src/swe-arena/arms.ts +804 -0
  145. package/src/swe-arena/bootstrap-meta.mts +188 -0
  146. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  147. package/src/swe-arena/briefing.mts +217 -0
  148. package/src/swe-arena/briefing.test.mts +178 -0
  149. package/src/swe-arena/calibrate.ts +217 -0
  150. package/src/swe-arena/capabilities.mts +76 -0
  151. package/src/swe-arena/capabilities.test.mts +57 -0
  152. package/src/swe-arena/capacity.ts +194 -0
  153. package/src/swe-arena/cell-evidence.mts +437 -0
  154. package/src/swe-arena/cell-evidence.test.mts +248 -0
  155. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  156. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  157. package/src/swe-arena/execution.test.mts +1170 -0
  158. package/src/swe-arena/factory-command-container.ts +284 -0
  159. package/src/swe-arena/factory-judge-child.mts +228 -0
  160. package/src/swe-arena/factory.test.mts +643 -0
  161. package/src/swe-arena/fixtures/analyze.py +80 -0
  162. package/src/swe-arena/fixtures/excludes.txt +8 -0
  163. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  164. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  165. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  166. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  167. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  168. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  169. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  170. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  171. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  172. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  173. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  174. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  175. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  176. package/src/swe-arena/fixtures/holdout.json +44 -0
  177. package/src/swe-arena/fixtures/instances.json +146 -0
  178. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  179. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  180. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  181. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  182. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  183. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  184. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  185. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  186. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  187. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  188. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  189. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  190. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  191. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  192. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  193. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  194. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  195. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  196. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  197. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  198. package/src/swe-arena/fixtures.ts +237 -0
  199. package/src/swe-arena/gepa-seat.mts +583 -0
  200. package/src/swe-arena/gepa-seat.test.mts +635 -0
  201. package/src/swe-arena/holdout-certify.mts +408 -0
  202. package/src/swe-arena/holdout-certify.test.mts +160 -0
  203. package/src/swe-arena/judge-child.mts +37 -0
  204. package/src/swe-arena/ledger-orphans.mts +77 -0
  205. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  206. package/src/swe-arena/lineage-record.mts +164 -0
  207. package/src/swe-arena/lineage-record.test.mts +115 -0
  208. package/src/swe-arena/manifest.mts +293 -0
  209. package/src/swe-arena/manifest.test.mts +169 -0
  210. package/src/swe-arena/materialize.ts +142 -0
  211. package/src/swe-arena/outer-loop.mts +2795 -0
  212. package/src/swe-arena/outer-loop.test.mts +696 -0
  213. package/src/swe-arena/parity.test.mts +87 -0
  214. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  215. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  216. package/src/swe-arena/proc.test.mts +174 -0
  217. package/src/swe-arena/proc.ts +260 -0
  218. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  219. package/src/swe-arena/proposer-fanout.mts +770 -0
  220. package/src/swe-arena/proposer-fanout.test.mts +619 -0
  221. package/src/swe-arena/proposer-provenance.mts +177 -0
  222. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  223. package/src/swe-arena/reconcile.ts +0 -0
  224. package/src/swe-arena/replay.mts +183 -0
  225. package/src/swe-arena/replay.test.mts +300 -0
  226. package/src/swe-arena/run-experiment.mts +727 -0
  227. package/src/swe-arena/run-report.mts +75 -0
  228. package/src/swe-arena/run-supervisor.mjs +297 -0
  229. package/src/swe-arena/run-supervisor.test.mts +500 -0
  230. package/src/swe-arena/score-split.mts +140 -0
  231. package/src/swe-arena/score-split.test.mts +123 -0
  232. package/src/swe-arena/serialized-judge.ts +414 -0
  233. package/src/swe-arena/types.ts +218 -0
  234. package/src/swe-code-improve.mts +328 -0
  235. package/src/swe-emit-patch.mts +104 -0
  236. package/src/swe-improve.mts +232 -0
  237. package/src/swe-jail.ts +2 -2
  238. package/src/swe-local-proof.mts +169 -0
  239. package/src/swe-repro-calibrate.mts +446 -0
  240. package/src/swe-stream.mts +1497 -0
  241. package/src/swe-structural.mts +245 -837
  242. package/dist/chunk-LRRD7NAG.js.map +0 -1
  243. package/dist/chunk-PPYSEKFM.js.map +0 -1
  244. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  245. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  246. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  247. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  248. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  249. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  250. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  251. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  252. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  253. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  254. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  255. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  256. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  257. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  258. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  259. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  260. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  261. /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
  262. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  263. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  264. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  265. /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,47 +1,47 @@
1
1
  import {
2
2
  ADAPTERS,
3
3
  resolveAdapter
4
- } from "./chunk-5SBJCB6W.js";
5
- import "./chunk-2PVVP7GN.js";
4
+ } from "./chunk-V7AEBY6U.js";
5
+ import "./chunk-XKEFIFIC.js";
6
6
  import "./chunk-VQRS7VUC.js";
7
- import "./chunk-Z7ML6L77.js";
7
+ import "./chunk-HWST3SED.js";
8
8
  import {
9
9
  createRagBenchAdapter
10
- } from "./chunk-R67DFVLO.js";
10
+ } from "./chunk-IFVINJ4B.js";
11
11
  import "./chunk-SEVJPLZC.js";
12
12
  import {
13
13
  createSweBenchAdapter,
14
14
  scoreSweReport,
15
15
  sweEvaluationArgv,
16
16
  swePatchOutput
17
- } from "./chunk-CKUVRZ2T.js";
17
+ } from "./chunk-3U5TXJZS.js";
18
18
  import {
19
19
  createT2RagBenchAdapter
20
- } from "./chunk-Y6O2OCUO.js";
21
- import "./chunk-HBSWHQNJ.js";
22
- import "./chunk-UAIOHCUK.js";
23
- import "./chunk-R36V2VP7.js";
24
- import "./chunk-JRWWGMK7.js";
20
+ } from "./chunk-SHM6MRRF.js";
21
+ import "./chunk-RCYQEFNX.js";
22
+ import "./chunk-SFLA7OH3.js";
23
+ import "./chunk-IZ5M6OAC.js";
24
+ import "./chunk-XYA4XSNU.js";
25
25
  import "./chunk-INNOYXCP.js";
26
26
  import "./chunk-PA2ZKHJC.js";
27
- import "./chunk-PPYSEKFM.js";
27
+ import "./chunk-5H5XV76F.js";
28
28
  import "./chunk-TBKU5XQI.js";
29
29
  import {
30
30
  createNoMiraclAdapter
31
- } from "./chunk-TSWPNOYM.js";
31
+ } from "./chunk-NQG5XDSB.js";
32
32
  import {
33
33
  createOpenRagBenchAdapter
34
- } from "./chunk-KDIKRJGB.js";
35
- import "./chunk-ZEWMTR5M.js";
34
+ } from "./chunk-SHYIRB7I.js";
35
+ import "./chunk-MQMRLGOG.js";
36
36
  import "./chunk-PUIRNYI7.js";
37
37
  import "./chunk-C7T7WEK2.js";
38
38
  import {
39
39
  runRefineLoop
40
40
  } from "./chunk-JTHWEDEW.js";
41
- import "./chunk-2XU6OGEN.js";
41
+ import "./chunk-Z4TZ76N7.js";
42
42
  import {
43
43
  createCragAdapter
44
- } from "./chunk-HHXFIHXC.js";
44
+ } from "./chunk-SVR2LKYI.js";
45
45
  import {
46
46
  FINAL_ANSWER_SENTINEL,
47
47
  answerScoreToBenchScore,
@@ -54,18 +54,18 @@ import {
54
54
  scoreAnswerArtifact,
55
55
  tokenF1
56
56
  } from "./chunk-X3BTXCJ4.js";
57
- import "./chunk-J3KDJNX2.js";
58
- import "./chunk-X5YKXC6V.js";
59
- import "./chunk-YCGY7UIZ.js";
57
+ import "./chunk-RH5F53JT.js";
58
+ import "./chunk-YSMEKBTD.js";
59
+ import "./chunk-7GRVHU22.js";
60
60
  import "./chunk-UPAMRDX4.js";
61
- import "./chunk-ODT47UAY.js";
62
- import "./chunk-7WSD27QQ.js";
63
- import "./chunk-SYDW647C.js";
64
- import "./chunk-IFAV6KEM.js";
61
+ import "./chunk-K3BQGZCT.js";
62
+ import "./chunk-PB64GYIG.js";
63
+ import "./chunk-IA2FBTWC.js";
64
+ import "./chunk-KP5KD6EN.js";
65
65
  import {
66
66
  StagedJudgeError,
67
67
  runStagedJudge
68
- } from "./chunk-LRRD7NAG.js";
68
+ } from "./chunk-WSKWVEQB.js";
69
69
  import "./chunk-53UPUNBZ.js";
70
70
 
71
71
  // src/run-benchmarks.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-bench",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "type": "module",
5
5
  "description": "The unified benchmark suite for agent-runtime agents: 31 adapters (commit0, enterpriseops-gym, ragbench, crag, nomiracl, open-rag-bench, t2-ragbench, tau3-banking, bfcl, finresearchbench, …) behind one resolveAdapter registry, each with a real judge or fail-loud unsupported scorer. Score any profile/skill/prompt change against them. Map: bench/HARNESS.md.",
6
6
  "repository": {
@@ -25,10 +25,11 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@tangle-network/agent-eval": "0.122.8",
29
- "@tangle-network/agent-interface": "0.31.0",
30
- "@tangle-network/sandbox": "^0.11.1",
31
- "@tangle-network/agent-runtime": "0.97.0"
28
+ "@tangle-network/agent-eval": "0.125.0",
29
+ "@tangle-network/agent-interface": "0.32.0",
30
+ "@tangle-network/agent-knowledge": "^4.1.0",
31
+ "@tangle-network/sandbox": "^0.12.0",
32
+ "@tangle-network/agent-runtime": "0.104.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^25.9.3",
@@ -1,5 +1,5 @@
1
1
  import { execFile } from 'node:child_process'
2
- import { access, readdir } from 'node:fs/promises'
2
+ import { access, readdir, readFile } from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { promisify } from 'node:util'
@@ -43,14 +43,36 @@ try {
43
43
  }
44
44
 
45
45
  const tests = await collectTests(sourceDir)
46
- const relativeTests = tests.map((file) => path.relative(benchDir, file))
47
- if (relativeTests.length === 0) throw new Error('no package tests found under src/')
46
+ if (tests.length === 0) throw new Error('no package tests found under src/')
48
47
 
49
- await run(process.execPath, ['--test', '--import', 'tsx', ...relativeTests], {
50
- ...process.env,
51
- TSX_TSCONFIG_PATH: 'tsconfig.public.json',
52
- })
48
+ // Two test runtimes coexist under src/: node:test files run under `node --test`;
49
+ // vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
50
+ // worker), so partition by the framework each file actually imports.
51
+ const nodeTests = []
52
+ const vitestTests = []
53
+ for (const file of tests) {
54
+ const body = await readFile(file, 'utf8')
55
+ if (/from\s+['"]vitest['"]/.test(body)) vitestTests.push(file)
56
+ else nodeTests.push(file)
57
+ }
58
+
59
+ if (nodeTests.length > 0) {
60
+ await run(
61
+ process.execPath,
62
+ ['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
63
+ {
64
+ ...process.env,
65
+ TSX_TSCONFIG_PATH: 'tsconfig.public.json',
66
+ },
67
+ )
68
+ }
69
+
70
+ if (vitestTests.length > 0) {
71
+ await run('npx', ['vitest', 'run', ...vitestTests.map((file) => path.relative(benchDir, file))])
72
+ }
53
73
 
54
74
  await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
55
75
 
56
- console.log(`package tests passed: ${tests.length}/${tests.length} TypeScript files + Pier bridge`)
76
+ console.log(
77
+ `package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
78
+ )
@@ -120,7 +120,7 @@ try {
120
120
  )
121
121
  await writeFile(
122
122
  path.join(consumerDir, 'index.mjs'),
123
- "import { resolveAdapter, runBenchmarks } from '@tangle-network/agent-bench'\nimport { ADAPTERS } from '@tangle-network/agent-bench/adapters'\nimport { createCragAdapter } from '@tangle-network/agent-bench/benchmarks/crag'\n\nif (typeof resolveAdapter !== 'function' || typeof runBenchmarks !== 'function') throw new Error('root exports are not executable')\nif (typeof ADAPTERS !== 'object' || typeof createCragAdapter !== 'function') throw new Error('subpath exports are not executable')\nif (resolveAdapter('crag').name !== 'crag') throw new Error('compiled adapter registry returned the wrong adapter')\n",
123
+ "import { resolveAdapter, runBenchmarks } from '@tangle-network/agent-bench'\nimport { ADAPTERS } from '@tangle-network/agent-bench/adapters'\nimport { createCragAdapter } from '@tangle-network/agent-bench/benchmarks/crag'\n\nif (typeof resolveAdapter !== 'function' || typeof runBenchmarks !== 'function') throw new Error('root exports are not executable')\nif (typeof ADAPTERS !== 'object' || typeof createCragAdapter !== 'function') throw new Error('subpath exports are not executable')\nif (resolveAdapter('crag').name !== 'crag') throw new Error('compiled adapter registry returned the wrong adapter')\nprocess.env.TOOLLM_FIXTURES = '1'\nconst toolLlmTasks = await resolveAdapter('toollm').loadTasks({ limit: 1 })\nif (toolLlmTasks.length !== 1 || toolLlmTasks[0]?.id !== '1') throw new Error('packed ToolLLM fixture loading failed')\n",
124
124
  )
125
125
  await writeFile(
126
126
  path.join(consumerDir, 'tsconfig.json'),
@@ -370,6 +370,7 @@ ${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=
370
370
  },
371
371
  instructionDelivery: { kind: 'argv-append' as const },
372
372
  cwd: { workspace: 'task' as const, path: '.' },
373
+ env: { PATH: { kind: 'public' as const, value: '/usr/local/bin:/usr/bin:/bin' } },
373
374
  environment: { kind: 'evaluator-task-container' as const },
374
375
  workspace: candidateWorkspace,
375
376
  isolation: {
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { execFile, spawn } from 'node:child_process'
20
20
  import { createHash } from 'node:crypto'
21
+ import { existsSync, readFileSync } from 'node:fs'
21
22
  import {
22
23
  cp,
23
24
  lstat,
@@ -42,8 +43,25 @@ import type {
42
43
 
43
44
  const execFileAsync = promisify(execFile)
44
45
 
45
- /** Repo root for the bench package (…/bench), so `.venv` and `fixtures` resolve. */
46
- export const benchRoot = fileURLToPath(new URL('../..', import.meta.url))
46
+ /** Locate the package by identity because source files and compiled chunks have different depths. */
47
+ function resolveBenchRoot(moduleUrl: string): string {
48
+ let current = dirname(fileURLToPath(moduleUrl))
49
+ while (true) {
50
+ const manifestPath = join(current, 'package.json')
51
+ if (existsSync(manifestPath)) {
52
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { name?: unknown }
53
+ if (manifest.name === '@tangle-network/agent-bench') return current
54
+ }
55
+ const parent = dirname(current)
56
+ if (parent === current) {
57
+ throw new Error(`Unable to locate @tangle-network/agent-bench from ${moduleUrl}`)
58
+ }
59
+ current = parent
60
+ }
61
+ }
62
+
63
+ /** Package root for agent-bench, so `.venv`, scripts, and fixtures resolve. */
64
+ export const benchRoot = resolveBenchRoot(import.meta.url)
47
65
 
48
66
  /** Resolve the shared interpreter without requiring an installed package to contain a venv. */
49
67
  export function resolveBenchPython(
@@ -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
+ import * as fs from 'node:fs'
17
+ import * as path from '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) })