@tangle-network/agent-bench 0.1.0 → 0.3.6

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 (317) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +39 -2
  4. package/dist/adapters.d.ts +15 -0
  5. package/dist/adapters.js +43 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/benchmarks/_harness.d.ts +125 -0
  8. package/dist/benchmarks/_harness.js +33 -0
  9. package/dist/benchmarks/_harness.js.map +1 -0
  10. package/dist/benchmarks/aec-bench.d.ts +27 -0
  11. package/dist/benchmarks/aec-bench.js +8 -0
  12. package/dist/benchmarks/aec-bench.js.map +1 -0
  13. package/dist/benchmarks/agentbench.d.ts +16 -0
  14. package/dist/benchmarks/agentbench.js +10 -0
  15. package/dist/benchmarks/agentbench.js.map +1 -0
  16. package/dist/benchmarks/appworld.d.ts +37 -0
  17. package/dist/benchmarks/appworld.js +14 -0
  18. package/dist/benchmarks/appworld.js.map +1 -0
  19. package/dist/benchmarks/bfcl.d.ts +18 -0
  20. package/dist/benchmarks/bfcl.js +10 -0
  21. package/dist/benchmarks/bfcl.js.map +1 -0
  22. package/dist/benchmarks/cad-design.d.ts +45 -0
  23. package/dist/benchmarks/cad-design.js +7 -0
  24. package/dist/benchmarks/cad-design.js.map +1 -0
  25. package/dist/benchmarks/cadbench.d.ts +19 -0
  26. package/dist/benchmarks/cadbench.js +8 -0
  27. package/dist/benchmarks/cadbench.js.map +1 -0
  28. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  29. package/dist/benchmarks/cadgenbench.js +8 -0
  30. package/dist/benchmarks/cadgenbench.js.map +1 -0
  31. package/dist/benchmarks/commit0.d.ts +31 -0
  32. package/dist/benchmarks/commit0.js +10 -0
  33. package/dist/benchmarks/commit0.js.map +1 -0
  34. package/dist/benchmarks/crag.d.ts +14 -0
  35. package/dist/benchmarks/crag.js +9 -0
  36. package/dist/benchmarks/crag.js.map +1 -0
  37. package/dist/benchmarks/dabstep.d.ts +18 -0
  38. package/dist/benchmarks/dabstep.js +10 -0
  39. package/dist/benchmarks/dabstep.js.map +1 -0
  40. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  41. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  42. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  43. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  44. package/dist/benchmarks/finresearchbench.js +8 -0
  45. package/dist/benchmarks/finresearchbench.js.map +1 -0
  46. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  47. package/dist/benchmarks/finsearchcomp.js +7 -0
  48. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  49. package/dist/benchmarks/frames.d.ts +59 -0
  50. package/dist/benchmarks/frames.js +13 -0
  51. package/dist/benchmarks/frames.js.map +1 -0
  52. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  53. package/dist/benchmarks/hotpotqa.js +15 -0
  54. package/dist/benchmarks/hotpotqa.js.map +1 -0
  55. package/dist/benchmarks/humaneval.d.ts +53 -0
  56. package/dist/benchmarks/humaneval.js +15 -0
  57. package/dist/benchmarks/humaneval.js.map +1 -0
  58. package/dist/benchmarks/mind2web.d.ts +41 -0
  59. package/dist/benchmarks/mind2web.js +9 -0
  60. package/dist/benchmarks/mind2web.js.map +1 -0
  61. package/dist/benchmarks/nomiracl.d.ts +15 -0
  62. package/dist/benchmarks/nomiracl.js +9 -0
  63. package/dist/benchmarks/nomiracl.js.map +1 -0
  64. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  65. package/dist/benchmarks/open-rag-bench.js +9 -0
  66. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  67. package/dist/benchmarks/programbench.d.ts +38 -0
  68. package/dist/benchmarks/programbench.js +10 -0
  69. package/dist/benchmarks/programbench.js.map +1 -0
  70. package/dist/benchmarks/rag-shared.d.ts +42 -0
  71. package/dist/benchmarks/rag-shared.js +39 -0
  72. package/dist/benchmarks/rag-shared.js.map +1 -0
  73. package/dist/benchmarks/ragbench.d.ts +16 -0
  74. package/dist/benchmarks/ragbench.js +9 -0
  75. package/dist/benchmarks/ragbench.js.map +1 -0
  76. package/dist/benchmarks/simpleqa.d.ts +64 -0
  77. package/dist/benchmarks/simpleqa.js +11 -0
  78. package/dist/benchmarks/simpleqa.js.map +1 -0
  79. package/dist/benchmarks/swe-bench.d.ts +56 -0
  80. package/dist/benchmarks/swe-bench.js +14 -0
  81. package/dist/benchmarks/swe-bench.js.map +1 -0
  82. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  83. package/dist/benchmarks/t2-ragbench.js +9 -0
  84. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  85. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  86. package/dist/benchmarks/tau-bench-shared.js +10 -0
  87. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  88. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  89. package/dist/benchmarks/tau2-bench.js +11 -0
  90. package/dist/benchmarks/tau2-bench.js.map +1 -0
  91. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  92. package/dist/benchmarks/tau3-banking.js +9 -0
  93. package/dist/benchmarks/tau3-banking.js.map +1 -0
  94. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  95. package/dist/benchmarks/terminal-bench.js +8 -0
  96. package/dist/benchmarks/terminal-bench.js.map +1 -0
  97. package/dist/benchmarks/toollm.d.ts +16 -0
  98. package/dist/benchmarks/toollm.js +10 -0
  99. package/dist/benchmarks/toollm.js.map +1 -0
  100. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  101. package/dist/benchmarks/trata-hedge.js +7 -0
  102. package/dist/benchmarks/trata-hedge.js.map +1 -0
  103. package/dist/benchmarks/types.d.ts +107 -0
  104. package/dist/benchmarks/types.js +1 -0
  105. package/dist/benchmarks/types.js.map +1 -0
  106. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  107. package/dist/benchmarks/webarena-verified.js +10 -0
  108. package/dist/benchmarks/webarena-verified.js.map +1 -0
  109. package/dist/chunk-2PVVP7GN.js +197 -0
  110. package/dist/chunk-2PVVP7GN.js.map +1 -0
  111. package/dist/chunk-2XU6OGEN.js +170 -0
  112. package/dist/chunk-2XU6OGEN.js.map +1 -0
  113. package/dist/chunk-53UPUNBZ.js +325 -0
  114. package/dist/chunk-53UPUNBZ.js.map +1 -0
  115. package/dist/chunk-5SBJCB6W.js +144 -0
  116. package/dist/chunk-5SBJCB6W.js.map +1 -0
  117. package/dist/chunk-7WSD27QQ.js +118 -0
  118. package/dist/chunk-7WSD27QQ.js.map +1 -0
  119. package/dist/chunk-C7T7WEK2.js +103 -0
  120. package/dist/chunk-C7T7WEK2.js.map +1 -0
  121. package/dist/chunk-CKUVRZ2T.js +251 -0
  122. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  123. package/dist/chunk-HBSWHQNJ.js +30 -0
  124. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  125. package/dist/chunk-HHXFIHXC.js +116 -0
  126. package/dist/chunk-HHXFIHXC.js.map +1 -0
  127. package/dist/chunk-IFAV6KEM.js +276 -0
  128. package/dist/chunk-IFAV6KEM.js.map +1 -0
  129. package/dist/chunk-INNOYXCP.js +387 -0
  130. package/dist/chunk-INNOYXCP.js.map +1 -0
  131. package/dist/chunk-J3KDJNX2.js +182 -0
  132. package/dist/chunk-J3KDJNX2.js.map +1 -0
  133. package/dist/chunk-JRWWGMK7.js +148 -0
  134. package/dist/chunk-JRWWGMK7.js.map +1 -0
  135. package/dist/chunk-JTHWEDEW.js +32 -0
  136. package/dist/chunk-JTHWEDEW.js.map +1 -0
  137. package/dist/chunk-KDIKRJGB.js +120 -0
  138. package/dist/chunk-KDIKRJGB.js.map +1 -0
  139. package/dist/chunk-LRRD7NAG.js +301 -0
  140. package/dist/chunk-LRRD7NAG.js.map +1 -0
  141. package/dist/chunk-ODT47UAY.js +221 -0
  142. package/dist/chunk-ODT47UAY.js.map +1 -0
  143. package/dist/chunk-PA2ZKHJC.js +230 -0
  144. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  145. package/dist/chunk-PPYSEKFM.js +182 -0
  146. package/dist/chunk-PPYSEKFM.js.map +1 -0
  147. package/dist/chunk-PUIRNYI7.js +189 -0
  148. package/dist/chunk-PUIRNYI7.js.map +1 -0
  149. package/dist/chunk-R36V2VP7.js +169 -0
  150. package/dist/chunk-R36V2VP7.js.map +1 -0
  151. package/dist/chunk-R67DFVLO.js +142 -0
  152. package/dist/chunk-R67DFVLO.js.map +1 -0
  153. package/dist/chunk-SEVJPLZC.js +260 -0
  154. package/dist/chunk-SEVJPLZC.js.map +1 -0
  155. package/dist/chunk-SYDW647C.js +318 -0
  156. package/dist/chunk-SYDW647C.js.map +1 -0
  157. package/dist/chunk-TBKU5XQI.js +228 -0
  158. package/dist/chunk-TBKU5XQI.js.map +1 -0
  159. package/dist/chunk-TSWPNOYM.js +147 -0
  160. package/dist/chunk-TSWPNOYM.js.map +1 -0
  161. package/dist/chunk-UAIOHCUK.js +27 -0
  162. package/dist/chunk-UAIOHCUK.js.map +1 -0
  163. package/dist/chunk-UPAMRDX4.js +233 -0
  164. package/dist/chunk-UPAMRDX4.js.map +1 -0
  165. package/dist/chunk-VQRS7VUC.js +342 -0
  166. package/dist/chunk-VQRS7VUC.js.map +1 -0
  167. package/dist/chunk-X3BTXCJ4.js +262 -0
  168. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  169. package/dist/chunk-X5YKXC6V.js +211 -0
  170. package/dist/chunk-X5YKXC6V.js.map +1 -0
  171. package/dist/chunk-Y6O2OCUO.js +130 -0
  172. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  173. package/dist/chunk-YCGY7UIZ.js +208 -0
  174. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  175. package/dist/chunk-Z7ML6L77.js +162 -0
  176. package/dist/chunk-Z7ML6L77.js.map +1 -0
  177. package/dist/chunk-ZEWMTR5M.js +136 -0
  178. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  179. package/dist/index.d.ts +355 -0
  180. package/dist/index.js +1908 -0
  181. package/dist/index.js.map +1 -0
  182. package/fixtures/aec-bench.json +18 -0
  183. package/fixtures/agentbench-dbbench.json +22 -0
  184. package/fixtures/bfcl.json +45 -0
  185. package/fixtures/commit0.json +72 -0
  186. package/fixtures/crag.json +10 -0
  187. package/fixtures/dabstep.json +22 -0
  188. package/fixtures/enterpriseops-gym.json +103 -0
  189. package/fixtures/finresearchbench.json +21 -0
  190. package/fixtures/finsearchcomp.json +66 -0
  191. package/fixtures/frames.json +26 -0
  192. package/fixtures/hotpotqa.json +182 -0
  193. package/fixtures/nomiracl.json +26 -0
  194. package/fixtures/open-rag-bench.json +16 -0
  195. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  196. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  197. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  198. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  199. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  200. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  201. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  202. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  203. package/fixtures/programbench.json +17 -0
  204. package/fixtures/ragbench.json +21 -0
  205. package/fixtures/simpleqa.json +121 -0
  206. package/fixtures/t2-ragbench.json +13 -0
  207. package/fixtures/tau2-bench.json +16 -0
  208. package/fixtures/tau3-banking.json +16 -0
  209. package/fixtures/toollm.json +28 -0
  210. package/fixtures/webarena-verified.json +20 -0
  211. package/package.json +60 -20
  212. package/pier_agents/__init__.py +18 -0
  213. package/pier_agents/candidate_contract.py +755 -0
  214. package/pier_agents/process_boundary.py +321 -0
  215. package/pier_agents/tangle_candidate.py +907 -0
  216. package/pier_agents/workspace_boundary.py +368 -0
  217. package/scripts/appworld_driver.py +359 -0
  218. package/scripts/cadbench_prepare.py +22 -0
  219. package/scripts/cadgenbench_hard_parts.py +48 -0
  220. package/scripts/clbench_codebase_judge.py +73 -0
  221. package/scripts/commit0_judge.py +170 -0
  222. package/scripts/dabstep_judge.py +42 -0
  223. package/scripts/enterpriseops_gym_judge.py +281 -0
  224. package/scripts/programbench_judge.py +120 -0
  225. package/scripts/render-gate-chart.mjs +176 -0
  226. package/scripts/run-package-tests.mjs +56 -0
  227. package/scripts/terminate-pier-trial.mts +66 -0
  228. package/scripts/trata-hedge/README.md +56 -0
  229. package/scripts/trata-hedge/run.sh +60 -0
  230. package/scripts/trata-hedge/solve.py +83 -0
  231. package/scripts/verify-packed-consumer.mjs +235 -0
  232. package/scripts/verify-pier-agent.mts +715 -0
  233. package/scripts/verify-pier-pair.mts +74 -0
  234. package/scripts/verify-pier-recovery.mts +139 -0
  235. package/src/adapters.ts +26 -0
  236. package/src/benchmarks/_harness.test.mts +178 -0
  237. package/src/benchmarks/_harness.ts +239 -16
  238. package/src/benchmarks/agentbench.ts +163 -0
  239. package/src/benchmarks/appworld.test.mts +15 -9
  240. package/src/benchmarks/bfcl.ts +346 -0
  241. package/src/benchmarks/crag.ts +137 -0
  242. package/src/benchmarks/dabstep.test.mts +70 -0
  243. package/src/benchmarks/dabstep.ts +212 -0
  244. package/src/benchmarks/external-adapters.test.mts +150 -0
  245. package/src/benchmarks/finresearchbench.ts +269 -0
  246. package/src/benchmarks/humaneval.ts +20 -8
  247. package/src/benchmarks/nomiracl.ts +180 -0
  248. package/src/benchmarks/open-rag-bench.ts +153 -0
  249. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  250. package/src/benchmarks/rag-shared.ts +327 -0
  251. package/src/benchmarks/ragbench.ts +171 -0
  252. package/src/benchmarks/swe-bench.test.mts +61 -0
  253. package/src/benchmarks/swe-bench.ts +201 -19
  254. package/src/benchmarks/t2-ragbench.ts +166 -0
  255. package/src/benchmarks/tau-bench-shared.ts +214 -0
  256. package/src/benchmarks/tau2-bench.ts +30 -0
  257. package/src/benchmarks/tau3-banking.ts +29 -0
  258. package/src/benchmarks/terminal-bench.test.mts +33 -0
  259. package/src/benchmarks/terminal-bench.ts +23 -8
  260. package/src/benchmarks/toollm.ts +254 -0
  261. package/src/benchmarks/types.ts +42 -0
  262. package/src/benchmarks/webarena-verified.ts +200 -0
  263. package/src/commit0-prereqs.sh +0 -0
  264. package/src/coordination-mcp-container-reach.mts +181 -0
  265. package/src/decoder-live.mts +1 -1
  266. package/src/examples/README.md +103 -39
  267. package/src/examples/benchmark-matrix.mts +101 -0
  268. package/src/examples/lean-proof-gate.README.md +77 -0
  269. package/src/examples/lean-proof-gate.mts +162 -0
  270. package/src/examples/lean-verify.ts +95 -0
  271. package/src/examples/lean.Dockerfile +12 -0
  272. package/src/examples/math-demo.mts +9 -7
  273. package/src/examples/strategy-demo.mts +10 -12
  274. package/src/gate.ts +3 -2
  275. package/src/hev-eval.mts +69 -0
  276. package/src/hev-improve.mts +169 -0
  277. package/src/hev-structural.mts +688 -0
  278. package/src/index.ts +73 -0
  279. package/src/mbpp-structural.mts +662 -0
  280. package/src/pier-agent.test-fixtures.mts +19 -0
  281. package/src/pier-agent.test.mts +363 -0
  282. package/src/pier-agent.ts +657 -0
  283. package/src/pier-result-grader.mjs +30 -0
  284. package/src/pier-result-grader.test.mts +62 -0
  285. package/src/pier-result-grader.ts +108 -0
  286. package/src/pier-task-outcome.test.mts +117 -0
  287. package/src/pier-task-outcome.ts +240 -0
  288. package/src/pier-trial-controller.test.mts +412 -0
  289. package/src/pier-trial-controller.ts +858 -0
  290. package/src/pier-trial-supervisor.mjs +352 -0
  291. package/src/resolve-client.ts +25 -2
  292. package/src/run-benchmarks-cli.mts +72 -0
  293. package/src/run-benchmarks-report.ts +66 -0
  294. package/src/run-benchmarks.test.mts +231 -0
  295. package/src/run-benchmarks.ts +589 -0
  296. package/src/smoke-structural-rollout.mts +393 -0
  297. package/src/swe-bench-env.test.ts +207 -0
  298. package/src/swe-bench-env.ts +554 -0
  299. package/src/swe-jail.ts +293 -0
  300. package/src/swe-self-improve.mts +84 -0
  301. package/src/swe-structural-judge-policy.test.ts +117 -0
  302. package/src/swe-structural-judge-policy.ts +133 -0
  303. package/src/swe-structural-policy.test.ts +124 -0
  304. package/src/swe-structural-policy.ts +132 -0
  305. package/src/swe-structural-provenance.test.ts +93 -0
  306. package/src/swe-structural-provenance.ts +138 -0
  307. package/src/swe-structural.mts +1260 -0
  308. package/src/swe-temp.ts +14 -0
  309. package/src/tb-container-executor.mts +234 -0
  310. package/src/tb-container-executor.test.mts +99 -0
  311. package/src/tb-supervisor-sidecar.mts +222 -0
  312. package/src/trata-gepa.mts +1 -1
  313. package/steerers/eops-itsm-population.json +1 -0
  314. package/tb_agents/opencode_refine_agent.py +117 -0
  315. package/tb_agents/opencode_router_agent.py +406 -0
  316. package/tb_agents/opencode_supervisor_agent.py +239 -0
  317. package/tb_agents/script_agent.py +66 -0
@@ -0,0 +1,124 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import { assertNoHiddenLeak } from './swe-jail'
4
+ import {
5
+ assertExactCompletedWorkerSessions,
6
+ continuationDisposition,
7
+ continuationStateNotice,
8
+ preferLaterCandidate,
9
+ resolveExperimentArm,
10
+ resolveExperimentTemperature,
11
+ shouldAcceptContinuation,
12
+ shouldRunContinuation,
13
+ } from './swe-structural-policy'
14
+
15
+ describe('structural experiment arms', () => {
16
+ it('derives exactly two independent sessions without repair', () => {
17
+ assert.deepEqual(resolveExperimentArm('independent-2'), {
18
+ arm: 'independent-2',
19
+ k: 2,
20
+ repairs: 0,
21
+ alwaysRunContinuation: false,
22
+ persistent: false,
23
+ workerSessions: 2,
24
+ })
25
+ })
26
+
27
+ it('derives exactly one initial plus one persistent continuation session', () => {
28
+ const preset = resolveExperimentArm('persistent-refine-2')
29
+ assert.deepEqual(preset, {
30
+ arm: 'persistent-refine-2',
31
+ k: 1,
32
+ repairs: 1,
33
+ alwaysRunContinuation: true,
34
+ persistent: true,
35
+ workerSessions: 2,
36
+ })
37
+ assert.equal(shouldRunContinuation({ round: 1, preset }), true)
38
+ assert.equal(shouldRunContinuation({ round: 2, preset }), false)
39
+ })
40
+
41
+ it('rejects every untyped arm instead of accepting free-form K/repair settings', () => {
42
+ assert.throws(() => resolveExperimentArm('system'), /independent-2\|persistent-refine-2/)
43
+ assert.throws(() => resolveExperimentArm(''), /independent-2\|persistent-refine-2/)
44
+ })
45
+
46
+ it('uses TEMPERATURE without repurposing Node TEMP as a model knob', () => {
47
+ assert.equal(resolveExperimentTemperature({}), 0.8)
48
+ assert.equal(resolveExperimentTemperature({ TEMPERATURE: '0.35' }), 0.35)
49
+ assert.equal(resolveExperimentTemperature({ TEMP: '/tmp', TEMPERATURE: '0.35' }), 0.35)
50
+ assert.throws(
51
+ () => resolveExperimentTemperature({ TEMP: '0.8' }),
52
+ /TEMP is the operating-system temporary-directory root.*TEMPERATURE/,
53
+ )
54
+ assert.throws(() => resolveExperimentTemperature({ TEMPERATURE: 'hot' }), /finite number/)
55
+ })
56
+ })
57
+
58
+ describe('shared later-on-visible-tie policy', () => {
59
+ it('prefers session two on a visible tie in both arm shapes', () => {
60
+ assert.equal(preferLaterCandidate(0, 0), true)
61
+ assert.equal(preferLaterCandidate(1, 1), true)
62
+ assert.equal(shouldAcceptContinuation(0, 0), true)
63
+ assert.equal(shouldAcceptContinuation(1, 1), true)
64
+ })
65
+
66
+ it('prefers a visible improvement and rejects a visible regression', () => {
67
+ assert.equal(preferLaterCandidate(1, 0), true)
68
+ assert.equal(preferLaterCandidate(0, 1), false)
69
+ assert.equal(shouldAcceptContinuation(1, 0), true)
70
+ assert.equal(shouldAcceptContinuation(0, 1), false)
71
+ })
72
+
73
+ it('never labels an identical second patch as a refinement', () => {
74
+ assert.deepEqual(continuationDisposition(true, false), {
75
+ finalFrom: 'session:2-identical',
76
+ stop: 'continuation-accepted-identical',
77
+ })
78
+ assert.equal(continuationDisposition(true, true).finalFrom, 'session:2-refinement')
79
+ })
80
+
81
+ it('inherits parent state without echoing parent patch bytes into session two', () => {
82
+ const parent = 'diff --git a/a.py b/a.py\n+gold-like-added-line\n'
83
+ const notice = continuationStateNotice(parent)
84
+ assert.match(notice, /already applied to this checkout/)
85
+ assert.equal(notice.includes('gold-like-added-line'), false)
86
+ assert.doesNotThrow(() => assertNoHiddenLeak(['+gold-like-added-line'], [{ role: 'user', content: notice }]))
87
+ })
88
+ })
89
+
90
+ describe('exact completed worker compute', () => {
91
+ const complete = { calls: 1, completions: 1, attemptError: null }
92
+
93
+ it('admits exactly two completed model-backed sessions', () => {
94
+ assert.doesNotThrow(() =>
95
+ assertExactCompletedWorkerSessions({
96
+ started: 2,
97
+ completed: 2,
98
+ sessions: [complete, complete],
99
+ context: 'row',
100
+ }),
101
+ )
102
+ })
103
+
104
+ it('rejects an attempted-but-failed or zero-call session', () => {
105
+ assert.throws(() =>
106
+ assertExactCompletedWorkerSessions({
107
+ started: 2,
108
+ completed: 1,
109
+ sessions: [complete, { calls: 0, completions: 0, attemptError: 'timeout' }],
110
+ context: 'row',
111
+ }),
112
+ /expected exactly 2 started and completed/,
113
+ )
114
+ assert.throws(() =>
115
+ assertExactCompletedWorkerSessions({
116
+ started: 2,
117
+ completed: 2,
118
+ sessions: [complete, { calls: 0, completions: 1, attemptError: null }],
119
+ context: 'row',
120
+ }),
121
+ /session 2 did not complete successfully/,
122
+ )
123
+ })
124
+ })
@@ -0,0 +1,132 @@
1
+ import { isAbsolute } from 'node:path'
2
+
3
+ export type ExperimentArm = 'independent-2' | 'persistent-refine-2'
4
+
5
+ export interface ExperimentArmPreset {
6
+ arm: ExperimentArm
7
+ k: 1 | 2
8
+ repairs: 0 | 1
9
+ alwaysRunContinuation: boolean
10
+ persistent: boolean
11
+ workerSessions: 2
12
+ }
13
+
14
+ export interface WorkerSessionReceipt {
15
+ calls: number
16
+ completions: number
17
+ attemptError: string | null
18
+ }
19
+
20
+ const PRESETS: Record<ExperimentArm, ExperimentArmPreset> = {
21
+ 'independent-2': {
22
+ arm: 'independent-2',
23
+ k: 2,
24
+ repairs: 0,
25
+ alwaysRunContinuation: false,
26
+ persistent: false,
27
+ workerSessions: 2,
28
+ },
29
+ 'persistent-refine-2': {
30
+ arm: 'persistent-refine-2',
31
+ k: 1,
32
+ repairs: 1,
33
+ alwaysRunContinuation: true,
34
+ persistent: true,
35
+ workerSessions: 2,
36
+ },
37
+ }
38
+
39
+ export function resolveExperimentArm(value: string): ExperimentArmPreset {
40
+ const preset = PRESETS[value as ExperimentArm]
41
+ if (!preset) {
42
+ throw new Error(
43
+ `EXPERIMENT_ARM must be independent-2|persistent-refine-2, got "${value}"`,
44
+ )
45
+ }
46
+ return preset
47
+ }
48
+
49
+ /** Keep model sampling configuration disjoint from Node's TEMP-controlled filesystem root. */
50
+ export function resolveExperimentTemperature(
51
+ env: Readonly<Record<string, string | undefined>>,
52
+ ): number {
53
+ if (env.TEMP?.trim() && !isAbsolute(env.TEMP)) {
54
+ throw new Error(
55
+ `TEMP is the operating-system temporary-directory root and must be absolute, got "${env.TEMP}"; ` +
56
+ 'use TEMPERATURE for model sampling',
57
+ )
58
+ }
59
+ const value = Number(env.TEMPERATURE ?? 0.8)
60
+ if (!Number.isFinite(value)) {
61
+ throw new Error(`TEMPERATURE must be a finite number, got "${env.TEMPERATURE}"`)
62
+ }
63
+ return value
64
+ }
65
+
66
+ /** Both arms use one selection rule: lower visible severity wins and a tie prefers session two. */
67
+ export function preferLaterCandidate(currentSeverity: number, laterSeverity: number): boolean {
68
+ return laterSeverity <= currentSeverity
69
+ }
70
+
71
+ /** The persistent arm always starts its one continuation, including after a visible pass. */
72
+ export function shouldRunContinuation(input: {
73
+ round: number
74
+ preset: ExperimentArmPreset
75
+ }): boolean {
76
+ return input.preset.alwaysRunContinuation && input.preset.repairs === 1 && input.round === 1
77
+ }
78
+
79
+ /** A continuation replaces its parent under the same later-on-tie rule as independent selection. */
80
+ export function shouldAcceptContinuation(currentSeverity: number, continuationSeverity: number): boolean {
81
+ return preferLaterCandidate(currentSeverity, continuationSeverity)
82
+ }
83
+
84
+ export function continuationDisposition(accepted: boolean, changedFromParent: boolean): {
85
+ finalFrom: 'session:2-refinement' | 'session:2-identical' | 'session:1-visible-better'
86
+ stop: 'continuation-accepted-changed' | 'continuation-accepted-identical' | 'continuation-rejected-visible-regression'
87
+ } {
88
+ if (!accepted) {
89
+ return { finalFrom: 'session:1-visible-better', stop: 'continuation-rejected-visible-regression' }
90
+ }
91
+ return changedFromParent
92
+ ? { finalFrom: 'session:2-refinement', stop: 'continuation-accepted-changed' }
93
+ : { finalFrom: 'session:2-identical', stop: 'continuation-accepted-identical' }
94
+ }
95
+
96
+ /** Describe inherited workspace state without echoing model-authored patch bytes back through the
97
+ * outbound prompt. The checkout already contains the parent state; repeating its diff can collide
98
+ * with hidden-patch leak marks when session one independently finds the correct line. */
99
+ export function continuationStateNotice(parentDiff: string): string {
100
+ return parentDiff.trim()
101
+ ? '--- PREVIOUS FIX STATE ---\nSession one changes are already applied to this checkout. Inspect the current files directly.\n\n'
102
+ : '--- NO FIX APPLIED YET (session one produced no change) ---\n\n'
103
+ }
104
+
105
+ /** Admit only two genuinely completed model-backed sessions, never two attempted dispatches. */
106
+ export function assertExactCompletedWorkerSessions(input: {
107
+ started: number
108
+ completed: number
109
+ sessions: readonly WorkerSessionReceipt[]
110
+ context: string
111
+ }): void {
112
+ if (input.started !== 2 || input.completed !== 2 || input.sessions.length !== 2) {
113
+ throw new Error(
114
+ `${input.context}: expected exactly 2 started and completed worker sessions ` +
115
+ `(started=${input.started}, completed=${input.completed}, receipts=${input.sessions.length})`,
116
+ )
117
+ }
118
+ input.sessions.forEach((session, index) => {
119
+ if (
120
+ session.attemptError !== null ||
121
+ !Number.isInteger(session.calls) ||
122
+ session.calls < 1 ||
123
+ !Number.isInteger(session.completions) ||
124
+ session.completions < 1
125
+ ) {
126
+ throw new Error(
127
+ `${input.context}: session ${index + 1} did not complete successfully ` +
128
+ `(error=${session.attemptError ?? 'none'}, calls=${session.calls}, completions=${session.completions})`,
129
+ )
130
+ }
131
+ })
132
+ }
@@ -0,0 +1,93 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import {
4
+ assertFingerprintsEqual,
5
+ createExecutionReceipt,
6
+ createFingerprints,
7
+ diffChanged,
8
+ diffFingerprint,
9
+ fingerprint,
10
+ runtimeImplementationFingerprint,
11
+ } from './swe-structural-provenance'
12
+
13
+ const inputs = {
14
+ source: { files: ['runner', 'environment'] },
15
+ config: { arm: 'independent-2', k: 2 },
16
+ commonConfig: { model: 'worker' },
17
+ repro: { script: 'raise SystemExit(1)' },
18
+ prompt: 'prompt',
19
+ tools: [{ name: 'run' }],
20
+ task: { id: 'repo__repo-1' },
21
+ }
22
+
23
+ describe('structural provenance', () => {
24
+ it('hashes objects independent of key insertion order', () => {
25
+ assert.equal(fingerprint({ b: 2, a: 1 }), fingerprint({ a: 1, b: 2 }))
26
+ })
27
+
28
+ it('rejects a changed resume input by its named fingerprint', () => {
29
+ const expected = createFingerprints(inputs)
30
+ const actual = createFingerprints({ ...inputs, prompt: 'changed prompt' })
31
+ assert.throws(() => assertFingerprintsEqual(actual, expected, 'resume row'), /prompt fingerprint mismatch/)
32
+ })
33
+
34
+ it('records identical and changed continuation patches exactly', () => {
35
+ const parent = 'diff --git a/a.py b/a.py\n+x\n'
36
+ assert.match(diffFingerprint(parent), /^sha256:[0-9a-f]{64}$/)
37
+ assert.equal(diffChanged(parent, parent), false)
38
+ assert.equal(diffChanged(parent, `${parent}+y\n`), true)
39
+ })
40
+
41
+ it('binds actual runtime callables, run settings, scorer version, and immutable image identity', () => {
42
+ const runtime = runtimeImplementationFingerprint({
43
+ runAgentic: function runAgenticA() { return 'a' },
44
+ refine: { name: 'refine', driver: function refineA() { return 'a' } },
45
+ })
46
+ const changedRuntime = runtimeImplementationFingerprint({
47
+ runAgentic: function runAgenticB() { return 'b' },
48
+ refine: { name: 'refine', driver: function refineA() { return 'a' } },
49
+ })
50
+ assert.notEqual(changedRuntime, runtime)
51
+
52
+ const shared = {
53
+ runTool: { timeoutS: 120, outputLimit: 10_000 },
54
+ runtimeImplementationFingerprint: runtime,
55
+ runtimeTreeFingerprint: fingerprint({ runtime: 'tree-a' }),
56
+ officialScorer: {
57
+ package: 'swebench' as const,
58
+ version: '4.0.0',
59
+ cacheLevel: 'instance' as const,
60
+ namespacePolicy: 'phase-a-image' as const,
61
+ },
62
+ }
63
+ const receipt = createExecutionReceipt(shared, {
64
+ tag: 'sweb.eval.example:latest',
65
+ namespace: 'swebench',
66
+ identity: { id: 'sha256:image-a', repoDigests: ['sweb.eval.example@sha256:digest-a'] },
67
+ })
68
+ const movedTag = createExecutionReceipt(shared, {
69
+ tag: 'sweb.eval.example:latest',
70
+ namespace: 'swebench',
71
+ identity: { id: 'sha256:image-b', repoDigests: ['sweb.eval.example@sha256:digest-b'] },
72
+ })
73
+ const movedRuntimeTree = createExecutionReceipt(
74
+ { ...shared, runtimeTreeFingerprint: fingerprint({ runtime: 'tree-b' }) },
75
+ {
76
+ tag: 'sweb.eval.example:latest',
77
+ namespace: 'swebench',
78
+ identity: { id: 'sha256:image-a', repoDigests: ['sweb.eval.example@sha256:digest-a'] },
79
+ },
80
+ )
81
+ const movedScorer = createExecutionReceipt(
82
+ { ...shared, officialScorer: { ...shared.officialScorer, version: '4.0.1' } },
83
+ {
84
+ tag: 'sweb.eval.example:latest',
85
+ namespace: 'swebench',
86
+ identity: { id: 'sha256:image-a', repoDigests: ['sweb.eval.example@sha256:digest-a'] },
87
+ },
88
+ )
89
+ assert.notEqual(fingerprint(receipt), fingerprint(movedTag))
90
+ assert.notEqual(fingerprint(receipt), fingerprint(movedRuntimeTree))
91
+ assert.notEqual(fingerprint(receipt), fingerprint(movedScorer))
92
+ })
93
+ })
@@ -0,0 +1,138 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ export const FINGERPRINT_SCHEMA = 'swe-structural-v2' as const
4
+
5
+ export interface Fingerprints {
6
+ schema: typeof FINGERPRINT_SCHEMA
7
+ source: string
8
+ config: string
9
+ commonConfig: string
10
+ repro: string
11
+ prompt: string
12
+ tools: string
13
+ task: string
14
+ composite: string
15
+ }
16
+
17
+ export interface FingerprintInputs {
18
+ source: unknown
19
+ config: unknown
20
+ commonConfig: unknown
21
+ repro: unknown
22
+ prompt: unknown
23
+ tools: unknown
24
+ task: unknown
25
+ }
26
+
27
+ export interface SharedExecutionReceipt {
28
+ runTool: {
29
+ timeoutS: number
30
+ outputLimit: number
31
+ }
32
+ runtimeImplementationFingerprint: string
33
+ runtimeTreeFingerprint: string
34
+ officialScorer: {
35
+ package: 'swebench'
36
+ version: string
37
+ cacheLevel: 'instance'
38
+ namespacePolicy: 'phase-a-image'
39
+ }
40
+ }
41
+
42
+ export interface ExecutionReceipt extends SharedExecutionReceipt {
43
+ image: {
44
+ tag: string
45
+ namespace: 'swebench' | 'none'
46
+ id: string
47
+ repoDigests: string[]
48
+ }
49
+ }
50
+
51
+ function canonicalize(value: unknown): unknown {
52
+ if (Array.isArray(value)) return value.map(canonicalize)
53
+ if (value && typeof value === 'object') {
54
+ return Object.fromEntries(
55
+ Object.entries(value as Record<string, unknown>)
56
+ .sort(([a], [b]) => a.localeCompare(b))
57
+ .map(([key, child]) => [key, canonicalize(child)]),
58
+ )
59
+ }
60
+ return value
61
+ }
62
+
63
+ export function canonicalJson(value: unknown): string {
64
+ return JSON.stringify(canonicalize(value))
65
+ }
66
+
67
+ export function fingerprint(value: unknown): string {
68
+ return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`
69
+ }
70
+
71
+ export function createFingerprints(inputs: FingerprintInputs): Fingerprints {
72
+ const parts = {
73
+ schema: FINGERPRINT_SCHEMA,
74
+ source: fingerprint(inputs.source),
75
+ config: fingerprint(inputs.config),
76
+ commonConfig: fingerprint(inputs.commonConfig),
77
+ repro: fingerprint(inputs.repro),
78
+ prompt: fingerprint(inputs.prompt),
79
+ tools: fingerprint(inputs.tools),
80
+ task: fingerprint(inputs.task),
81
+ }
82
+ return { ...parts, composite: fingerprint(parts) }
83
+ }
84
+
85
+ export function assertFingerprintsEqual(
86
+ actual: Fingerprints | undefined,
87
+ expected: Fingerprints,
88
+ context: string,
89
+ ): void {
90
+ if (!actual) throw new Error(`${context}: missing ${FINGERPRINT_SCHEMA} fingerprints`)
91
+ for (const key of Object.keys(expected) as Array<keyof Fingerprints>) {
92
+ if (actual[key] !== expected[key]) {
93
+ throw new Error(`${context}: ${key} fingerprint mismatch (${actual[key]} != ${expected[key]})`)
94
+ }
95
+ }
96
+ }
97
+
98
+ export function diffFingerprint(diff: string): string {
99
+ return fingerprint({ diff })
100
+ }
101
+
102
+ export function diffChanged(parent: string, candidate: string): boolean {
103
+ return diffFingerprint(parent) !== diffFingerprint(candidate)
104
+ }
105
+
106
+ /** Hash the actual imported callables, not merely the source file expected to contain them. */
107
+ export function runtimeImplementationFingerprint(input: {
108
+ runAgentic: unknown
109
+ refine: { name: string; driver: unknown }
110
+ }): string {
111
+ if (typeof input.runAgentic !== 'function' || typeof input.refine.driver !== 'function') {
112
+ throw new Error('runtime implementation receipt requires callable runAgentic and refine.driver')
113
+ }
114
+ return fingerprint({
115
+ runAgentic: Function.prototype.toString.call(input.runAgentic),
116
+ refine: {
117
+ name: input.refine.name,
118
+ driver: Function.prototype.toString.call(input.refine.driver),
119
+ },
120
+ })
121
+ }
122
+
123
+ export function createExecutionReceipt(
124
+ shared: SharedExecutionReceipt,
125
+ image: { tag: string; namespace: 'swebench' | 'none'; identity: { id: string; repoDigests: string[] } },
126
+ ): ExecutionReceipt {
127
+ return {
128
+ ...shared,
129
+ runTool: { ...shared.runTool },
130
+ officialScorer: { ...shared.officialScorer },
131
+ image: {
132
+ tag: image.tag,
133
+ namespace: image.namespace,
134
+ id: image.identity.id,
135
+ repoDigests: [...image.identity.repoDigests],
136
+ },
137
+ }
138
+ }