@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,352 @@
1
+ import { execFileSync, spawn } from 'node:child_process'
2
+ import {
3
+ closeSync,
4
+ createReadStream,
5
+ existsSync,
6
+ fsyncSync,
7
+ openSync,
8
+ readFileSync,
9
+ readdirSync,
10
+ renameSync,
11
+ writeFileSync,
12
+ } from 'node:fs'
13
+ import { basename, isAbsolute, join, resolve } from 'node:path'
14
+
15
+ const identityFile = 'identity.json'
16
+ const terminalFile = 'terminal.json'
17
+ const stopFile = 'stop-requested'
18
+
19
+ function fail(message) {
20
+ throw new Error(message)
21
+ }
22
+
23
+ function readJson(path, label) {
24
+ let value
25
+ try {
26
+ value = JSON.parse(readFileSync(path, 'utf8'))
27
+ } catch (error) {
28
+ throw new Error(`${label} is not valid JSON`, { cause: error })
29
+ }
30
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31
+ fail(`${label} must be an object`)
32
+ }
33
+ return value
34
+ }
35
+
36
+ function syncDirectory(directory) {
37
+ const fd = openSync(directory, 'r')
38
+ try {
39
+ fsyncSync(fd)
40
+ } finally {
41
+ closeSync(fd)
42
+ }
43
+ }
44
+
45
+ function writeJsonAtomic(directory, name, value) {
46
+ const target = join(directory, name)
47
+ const temporary = join(directory, `.${name}.${process.pid}.tmp`)
48
+ const fd = openSync(temporary, 'wx', 0o600)
49
+ try {
50
+ writeFileSync(fd, `${JSON.stringify(value)}\n`, 'utf8')
51
+ fsyncSync(fd)
52
+ } finally {
53
+ closeSync(fd)
54
+ }
55
+ renameSync(temporary, target)
56
+ syncDirectory(directory)
57
+ }
58
+
59
+ async function readControlInput() {
60
+ const chunks = []
61
+ for await (const chunk of createReadStream('', { fd: 3 })) {
62
+ chunks.push(Buffer.from(chunk))
63
+ }
64
+ const bytes = Buffer.concat(chunks)
65
+ if (bytes.byteLength === 0) fail('Pier supervisor received no launch request')
66
+ return JSON.parse(bytes.toString('utf8'))
67
+ }
68
+
69
+ function processIdentity(pid) {
70
+ const text = readFileSync(`/proc/${pid}/stat`, 'utf8')
71
+ const closing = text.lastIndexOf(')')
72
+ if (closing < 0) fail(`cannot parse process identity for ${pid}`)
73
+ const fields = text.slice(closing + 2).trim().split(/\s+/)
74
+ const processGroupId = Number(fields[2])
75
+ const sessionId = Number(fields[3])
76
+ const startTicks = fields[19]
77
+ if (
78
+ !Number.isSafeInteger(processGroupId) ||
79
+ !Number.isSafeInteger(sessionId) ||
80
+ !/^\d+$/.test(startTicks ?? '')
81
+ ) {
82
+ fail(`cannot parse process identity for ${pid}`)
83
+ }
84
+ return { pid, processGroupId, sessionId, startTicks }
85
+ }
86
+
87
+ function processMatches(identity) {
88
+ try {
89
+ const current = processIdentity(identity.pid)
90
+ return (
91
+ current.startTicks === identity.startTicks &&
92
+ current.processGroupId === identity.processGroupId &&
93
+ current.sessionId === identity.sessionId
94
+ )
95
+ } catch (error) {
96
+ if (error?.code === 'ENOENT' || error?.code === 'ESRCH') return false
97
+ throw error
98
+ }
99
+ }
100
+
101
+ function sessionMembers(sessionId) {
102
+ const members = []
103
+ for (const entry of readdirSync('/proc', { withFileTypes: true })) {
104
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue
105
+ try {
106
+ const identity = processIdentity(Number(entry.name))
107
+ if (identity.sessionId === sessionId) members.push(identity)
108
+ } catch (error) {
109
+ if (error?.code !== 'ENOENT') throw error
110
+ }
111
+ }
112
+ return members
113
+ }
114
+
115
+ async function signalSessionUntilEmpty(identity, signal, timeoutMs) {
116
+ const deadline = Date.now() + timeoutMs
117
+ while (true) {
118
+ const members = sessionMembers(identity.sessionId)
119
+ if (members.length === 0) return true
120
+ for (const member of members) {
121
+ try {
122
+ process.kill(member.pid, signal)
123
+ } catch (error) {
124
+ if (error?.code !== 'ESRCH') throw error
125
+ }
126
+ }
127
+ if (Date.now() >= deadline) return false
128
+ await new Promise((resolveWait) => setTimeout(resolveWait, 20))
129
+ }
130
+ }
131
+
132
+ function sanitizeProject(value) {
133
+ let project = value.toLowerCase()
134
+ if (!/^[a-z0-9]/.test(project)) project = `0${project}`
135
+ return project.replace(/[^a-z0-9_-]/g, '-')
136
+ }
137
+
138
+ function trialProjects(jobsDirectory, jobName) {
139
+ const jobRoot = join(jobsDirectory, jobName)
140
+ if (!existsSync(jobRoot)) return []
141
+ return readdirSync(jobRoot, { withFileTypes: true })
142
+ .filter((entry) => entry.isDirectory() && !entry.isSymbolicLink())
143
+ .map((entry) => sanitizeProject(entry.name))
144
+ }
145
+
146
+ function matchingContainers(dockerCommand, projects) {
147
+ if (projects.length === 0) return []
148
+ const output = execFileSync(
149
+ dockerCommand,
150
+ ['ps', '-a', '--format', '{{.ID}}\t{{.Label "com.docker.compose.project"}}'],
151
+ { encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 },
152
+ )
153
+ const matches = []
154
+ for (const line of output.split('\n')) {
155
+ if (!line) continue
156
+ const tab = line.indexOf('\t')
157
+ if (tab < 1) continue
158
+ const id = line.slice(0, tab)
159
+ const project = line.slice(tab + 1)
160
+ if (
161
+ projects.some(
162
+ (expected) =>
163
+ project === expected || project.startsWith(`${expected}__verifier__`),
164
+ )
165
+ ) {
166
+ matches.push(id)
167
+ }
168
+ }
169
+ return matches
170
+ }
171
+
172
+ function removeTrialContainers(config) {
173
+ const projects = trialProjects(config.jobsDirectory, config.jobName)
174
+ const containers = matchingContainers(config.dockerCommand, projects)
175
+ if (containers.length > 0) {
176
+ try {
177
+ execFileSync(config.dockerCommand, ['rm', '-f', ...containers], {
178
+ encoding: 'utf8',
179
+ timeout: 30_000,
180
+ maxBuffer: 4 * 1024 * 1024,
181
+ })
182
+ } catch (error) {
183
+ if (matchingContainers(config.dockerCommand, projects).length > 0) throw error
184
+ }
185
+ }
186
+ const remaining = matchingContainers(config.dockerCommand, projects)
187
+ if (remaining.length > 0) {
188
+ fail(`Pier task containers survived removal: ${remaining.join(', ')}`)
189
+ }
190
+ return containers.length
191
+ }
192
+
193
+ async function waitForExit(exited, timeoutMs) {
194
+ return await Promise.race([
195
+ exited.then(() => true),
196
+ new Promise((resolveTimeout) => setTimeout(() => resolveTimeout(false), timeoutMs)),
197
+ ])
198
+ }
199
+
200
+ const directory = resolve(process.argv[2] ?? '')
201
+ if (!isAbsolute(directory) || basename(directory) === '') {
202
+ fail('Pier supervisor control directory must be absolute')
203
+ }
204
+
205
+ let config
206
+ let child
207
+ let childIdentity
208
+ let stopRequested = existsSync(join(directory, stopFile))
209
+ let stopping
210
+
211
+ async function stopChild() {
212
+ if (!child) return
213
+ if (!childIdentity) {
214
+ try {
215
+ child.process.kill('SIGKILL')
216
+ } catch (error) {
217
+ if (error?.code !== 'ESRCH') throw error
218
+ }
219
+ if (!(await waitForExit(child.exited, 5_000))) {
220
+ fail('Pier process with unreadable identity survived SIGKILL')
221
+ }
222
+ return
223
+ }
224
+ if (!(await signalSessionUntilEmpty(childIdentity, 'SIGTERM', 2_000))) {
225
+ if (!(await signalSessionUntilEmpty(childIdentity, 'SIGKILL', 5_000))) {
226
+ fail(`Pier process session ${childIdentity.sessionId} survived SIGKILL`)
227
+ }
228
+ }
229
+ if (!(await waitForExit(child.exited, 5_000))) {
230
+ fail(`Pier process session ${childIdentity.sessionId} retained child processes`)
231
+ }
232
+ }
233
+
234
+ function requestStop() {
235
+ stopRequested = true
236
+ stopping ??= stopChild()
237
+ }
238
+
239
+ process.on('SIGTERM', requestStop)
240
+ process.on('SIGINT', requestStop)
241
+
242
+ try {
243
+ config = await readControlInput()
244
+ if (
245
+ !config ||
246
+ typeof config !== 'object' ||
247
+ typeof config.command !== 'string' ||
248
+ !Array.isArray(config.args) ||
249
+ typeof config.cwd !== 'string' ||
250
+ !config.env ||
251
+ typeof config.env !== 'object' ||
252
+ typeof config.jobsDirectory !== 'string' ||
253
+ typeof config.jobName !== 'string' ||
254
+ typeof config.dockerCommand !== 'string'
255
+ ) {
256
+ fail('Pier supervisor launch request is malformed')
257
+ }
258
+
259
+ const identity = readJson(join(directory, identityFile), 'Pier trial identity')
260
+ if (stopRequested || existsSync(join(directory, stopFile))) {
261
+ const removedContainers = removeTrialContainers(config)
262
+ writeJsonAtomic(directory, terminalFile, {
263
+ schemaVersion: 1,
264
+ kind: 'pier-trial-terminal',
265
+ status: 'stopped',
266
+ processExited: true,
267
+ containersRemoved: true,
268
+ removedContainers,
269
+ })
270
+ process.exitCode = 0
271
+ } else {
272
+ const spawned = spawn(config.command, config.args, {
273
+ cwd: config.cwd,
274
+ env: config.env,
275
+ detached: true,
276
+ stdio: 'ignore',
277
+ })
278
+ if (spawned.pid === undefined) fail('Pier process started without a pid')
279
+ const exited = new Promise((resolveExit) => {
280
+ let settled = false
281
+ spawned.once('error', (error) => {
282
+ if (settled) return
283
+ settled = true
284
+ resolveExit({ code: null, signal: null, error })
285
+ })
286
+ spawned.once('close', (code, signal) => {
287
+ if (settled) return
288
+ settled = true
289
+ resolveExit({ code, signal })
290
+ })
291
+ })
292
+ child = { process: spawned, exited }
293
+ childIdentity = processIdentity(spawned.pid)
294
+ writeJsonAtomic(directory, identityFile, {
295
+ ...identity,
296
+ state: 'running',
297
+ pier: childIdentity,
298
+ })
299
+
300
+ if (stopRequested || existsSync(join(directory, stopFile))) {
301
+ stopRequested = true
302
+ stopping = stopChild()
303
+ }
304
+ const exit = await exited
305
+ if (stopping) await stopping
306
+ else await stopChild()
307
+ const removedContainers = removeTrialContainers(config)
308
+ const status = stopRequested ? 'stopped' : exit.code === 0 ? 'completed' : 'failed'
309
+ writeJsonAtomic(directory, terminalFile, {
310
+ schemaVersion: 1,
311
+ kind: 'pier-trial-terminal',
312
+ status,
313
+ processExited: true,
314
+ containersRemoved: true,
315
+ removedContainers,
316
+ exitCode: exit.code,
317
+ signal: exit.signal,
318
+ ...(exit.error ? { error: 'Pier process failed to start' } : {}),
319
+ ...(status === 'failed' && !exit.error
320
+ ? { error: `Pier process exited ${exit.signal ?? exit.code ?? 'without status'}` }
321
+ : {}),
322
+ })
323
+ }
324
+ } catch (error) {
325
+ const cleanupErrors = []
326
+ try {
327
+ requestStop()
328
+ if (stopping) await stopping
329
+ } catch (cleanup) {
330
+ cleanupErrors.push(cleanup)
331
+ }
332
+ if (config) {
333
+ try {
334
+ removeTrialContainers(config)
335
+ } catch (cleanup) {
336
+ cleanupErrors.push(cleanup)
337
+ }
338
+ }
339
+ writeJsonAtomic(directory, terminalFile, {
340
+ schemaVersion: 1,
341
+ kind: 'pier-trial-terminal',
342
+ status: 'failed',
343
+ processExited: !childIdentity || !processMatches(childIdentity),
344
+ containersRemoved: cleanupErrors.length === 0,
345
+ error: `${error instanceof Error ? error.message : String(error)}${
346
+ cleanupErrors.length > 0
347
+ ? `; cleanup failed: ${cleanupErrors.map((item) => item?.message ?? String(item)).join('; ')}`
348
+ : ''
349
+ }`,
350
+ })
351
+ process.exitCode = 1
352
+ }
@@ -8,6 +8,10 @@
8
8
  * domains whose worker is a completion, or where box egress is blocked.
9
9
  * - `router` + searchProvider → OFF-BOX `router-tools`: the same off-box loop with a live
10
10
  * `web_search` tool (the capability axis research benches need).
11
+ * - `bridge` → OFF-BOX: a local cli-bridge fronting a harness CLI
12
+ * (opencode/kimi-code/…) as the leaf executor. Same resumable
13
+ * `bridgeExecutor` the supervisor uses; harness+model ride the
14
+ * bridge `model` id (`${harness}/${model}`).
11
15
  * - anything else (`sandbox`/a BackendType) → IN-BOX: a real `Sandbox`. The in-box backend
12
16
  * TYPE (opencode/codex/…) is set separately on the `AgentRunSpec`;
13
17
  * this only decides off-box-vs-in-box transport for `runLoop`.
@@ -15,7 +19,12 @@
15
19
  * Centralizing it here means a new entrypoint gets the full off-box/in-box matrix for free and
16
20
  * the mapping can't drift between callers.
17
21
  */
18
- import { createExecutor, inlineSandboxClient, type SandboxClient } from '@tangle-network/agent-runtime/loops'
22
+ import {
23
+ createExecutor,
24
+ inlineSandboxClient,
25
+ resolveSandboxClient,
26
+ type SandboxClient,
27
+ } from '@tangle-network/agent-runtime/loops'
19
28
  import { Sandbox } from '@tangle-network/sandbox'
20
29
  import { makeSearchExecutor, webSearchTool } from './search-tool'
21
30
 
@@ -29,8 +38,12 @@ export interface ResolveBenchClientOptions {
29
38
  * with a live `web_search` tool backed by this provider (`you`/`exa`/…). */
30
39
  searchProvider?: string
31
40
  sandboxBaseUrl?: string
32
- /** In-box sandbox timeout (ms). */
41
+ /** In-box sandbox timeout (ms). Also the per-turn deadline for the `bridge` backend. */
33
42
  timeoutMs?: number
43
+ /** `bridge` backend: cli-bridge base URL. Defaults to `http://127.0.0.1:3355`. */
44
+ bridgeUrl?: string
45
+ /** `bridge` backend: bearer the bridge requires. Falls back to `routerKey` when unset. */
46
+ bridgeBearer?: string
34
47
  }
35
48
 
36
49
  export function resolveBenchClient(opts: ResolveBenchClientOptions): SandboxClient {
@@ -50,6 +63,16 @@ export function resolveBenchClient(opts: ResolveBenchClientOptions): SandboxClie
50
63
  }
51
64
  return inlineSandboxClient(createExecutor({ backend: 'router', routerBaseUrl, routerKey, model }))
52
65
  }
66
+ if (backend === 'bridge') {
67
+ // bench's bearer fallback (`?? routerKey`) resolves first, then the shared
68
+ // resolver core wires the bridge seam — no re-implemented createExecutor branch.
69
+ const bridgeBearer = opts.bridgeBearer ?? routerKey
70
+ if (!bridgeBearer) throw new Error("resolveBenchClient: backend 'bridge' needs bridgeBearer or routerKey")
71
+ return resolveSandboxClient({
72
+ backend: 'bridge',
73
+ bridge: { url: opts.bridgeUrl, bearer: bridgeBearer, model, timeoutMs: opts.timeoutMs },
74
+ })
75
+ }
53
76
  return new Sandbox({
54
77
  baseUrl: opts.sandboxBaseUrl ?? 'https://sandbox.tangle.tools',
55
78
  apiKey: routerKey,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Run a SUBSET of the benchmark registry over a matrix of agent cells, end to end:
3
+ *
4
+ * export TANGLE_API_KEY=... # router + each adapter judge's creds
5
+ * BENCHMARKS=humaneval,swe-bench \
6
+ * CELLS=opencode/glm-4.6,codex/gpt-5,deepseek-v4-flash \
7
+ * N=20 CONCURRENCY=6 tsx src/run-benchmarks-cli.mts
8
+ *
9
+ * A cell is `harness/model` (in-box: that coding harness runs the model) or a bare `model`
10
+ * (off-box: a router completion is the worker). The number is the adapter's OWN deterministic
11
+ * judge — pick benchmarks whose `adapter.judge` is a runnable checker. Output is a leaderboard:
12
+ * resolve-rate and mean graded score per (benchmark × cell), with any unavailable benchmark
13
+ * (missing Docker/venv/dataset) listed, never silently dropped.
14
+ */
15
+ import { printBenchmarksReport, runBenchmarks, type BenchCell } from './run-benchmarks'
16
+
17
+ const must = (k: string): string => {
18
+ const v = process.env[k]
19
+ if (!v) throw new Error(`env ${k} is required`)
20
+ return v
21
+ }
22
+
23
+ /** `harness/model` → in-box cell; bare `model` → off-box router cell. For cli-bridge, the
24
+ * full model id is already `harness/model`, so do not split it. */
25
+ function parseCell(spec: string): BenchCell {
26
+ const s = spec.trim()
27
+ if (process.env.BACKEND === 'bridge') {
28
+ return { label: s, model: s, backend: 'bridge', ...(process.env.SEARCH_PROVIDER ? { searchProvider: process.env.SEARCH_PROVIDER } : {}) }
29
+ }
30
+ const slash = s.indexOf('/')
31
+ if (slash > 0) {
32
+ const harness = s.slice(0, slash)
33
+ const model = s.slice(slash + 1)
34
+ return { label: s, harness, model, backend: process.env.BACKEND ?? 'sandbox', ...(process.env.SEARCH_PROVIDER ? { searchProvider: process.env.SEARCH_PROVIDER } : {}) }
35
+ }
36
+ return { label: s, model: s, backend: process.env.BACKEND ?? 'router', ...(process.env.SEARCH_PROVIDER ? { searchProvider: process.env.SEARCH_PROVIDER } : {}) }
37
+ }
38
+
39
+ async function main(): Promise<void> {
40
+ const benchmarks = (process.env.BENCHMARKS ?? '').split(',').map((s) => s.trim()).filter(Boolean)
41
+ if (benchmarks.length === 0) throw new Error('env BENCHMARKS is required (comma-separated registry keys, e.g. humaneval,swe-bench)')
42
+ const cells = (process.env.CELLS ?? process.env.WORKER_MODEL ?? 'deepseek-v4-flash')
43
+ .split(',').map((s) => s.trim()).filter(Boolean).map(parseCell)
44
+
45
+ const report = await runBenchmarks({
46
+ benchmarks,
47
+ cells,
48
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
49
+ routerKey: must('TANGLE_API_KEY'),
50
+ ...(process.env.BRIDGE_URL ? { bridgeUrl: process.env.BRIDGE_URL } : {}),
51
+ ...(process.env.BRIDGE_BEARER ?? process.env.CLI_BRIDGE_BEARER ? { bridgeBearer: process.env.BRIDGE_BEARER ?? process.env.CLI_BRIDGE_BEARER } : {}),
52
+ ...(process.env.SANDBOX_BASE ? { sandboxBaseUrl: process.env.SANDBOX_BASE } : {}),
53
+ n: Number(process.env.N ?? 10),
54
+ ...(process.env.IDS ? { ids: process.env.IDS.split(',') } : {}),
55
+ ...(process.env.SPLIT ? { split: process.env.SPLIT } : {}),
56
+ ...(process.env.REPS ? { reps: Number(process.env.REPS) } : {}),
57
+ ...(process.env.LOOP_ATTEMPTS ? { loopAttempts: Number(process.env.LOOP_ATTEMPTS) } : {}),
58
+ concurrency: Number(process.env.CONCURRENCY ?? 4),
59
+ ...(process.env.TIMEOUT_MS ? { timeoutMs: Number(process.env.TIMEOUT_MS) } : {}),
60
+ ...(process.env.VERIFY_JUDGE === '0' ? { verifyJudge: false } : {}),
61
+ onResult: (r) =>
62
+ console.log(` [${r.ok ? (r.resolved ? 'PASS' : 'fail') : 'ERR '}] ${r.benchmark} ${r.cell} ${r.taskId} score=${r.score.toFixed(2)} ${(r.wallMs / 1000).toFixed(1)}s${r.detail ? ` (${r.detail})` : ''}`),
63
+ })
64
+
65
+ console.log(`\n=== benchmark matrix: ${report.benchmarks.join(', ')} × ${report.cells.length} cell(s) ===`)
66
+ console.log(printBenchmarksReport(report))
67
+ }
68
+
69
+ main().catch((e) => {
70
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
71
+ process.exit(1)
72
+ })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * run-benchmarks-report — render a `runBenchmarks` result as a rich, multi-axis leaderboard (markdown +
3
+ * SVG + HTML), with the BENCHMARKS as the axes. `runBenchmarks` (the registry matrix runner) already
4
+ * returns a fixed-width CLI table; this bridges its per-task results into the domain-agnostic `leaderboard`
5
+ * engine so the same run also yields a publishable board — every harness×model×persona cell scored on every
6
+ * benchmark, the vals.ai-style surface.
7
+ *
8
+ * No new aggregation logic: it maps each `BenchCellTaskResult` into the universal `RunRecord` shape the
9
+ * engine reads (cell → profile, benchmark → axis, score → score), then calls the shared renderers.
10
+ * Errored shots (`ok:false`) are dropped, exactly as the runner excludes them from its own denominator.
11
+ */
12
+ import type { RunRecord } from '@tangle-network/agent-eval'
13
+ import {
14
+ type Leaderboard,
15
+ leaderboard,
16
+ renderLeaderboardHtml,
17
+ renderLeaderboardMarkdown,
18
+ renderLeaderboardSvg,
19
+ } from '@tangle-network/agent-runtime/loops'
20
+ import type { BenchCellTaskResult, RunBenchmarksReport } from './run-benchmarks'
21
+
22
+ /** Project the runner's per-task results into `RunRecord`s the leaderboard engine reads. Cost/tokens are
23
+ * left at 0 — `runBenchmarks` does not meter them per task, so the board shows them as not-captured rather
24
+ * than inventing a number. */
25
+ function toRecords(perTask: readonly BenchCellTaskResult[]): RunRecord[] {
26
+ return perTask
27
+ .filter((t) => t.ok)
28
+ .map(
29
+ (t) =>
30
+ ({
31
+ runId: `${t.benchmark}:${t.cell}:${t.taskId}:${t.rep}`,
32
+ experimentId: t.benchmark,
33
+ candidateId: t.cell,
34
+ seed: t.rep,
35
+ // The cell label is `harness/model` (or a persona id); carry it as the model + harness so the
36
+ // leaderboard's default profile key resolves to the cell.
37
+ model: t.cell,
38
+ promptHash: '',
39
+ configHash: '',
40
+ commitSha: '',
41
+ wallMs: t.wallMs,
42
+ costUsd: 0,
43
+ tokenUsage: { input: 0, output: 0 },
44
+ outcome: { holdoutScore: t.score, raw: { resolved: t.resolved ? 1 : 0 } },
45
+ splitTag: 'holdout',
46
+ scenarioId: t.benchmark,
47
+ agentProfile: { harness: t.cell.split('/')[0] ?? t.cell, model: t.cell },
48
+ }) as unknown as RunRecord,
49
+ )
50
+ }
51
+
52
+ /** Build the rich leaderboard from a `runBenchmarks` result — benchmarks are the axes, cells are the rows. */
53
+ export function benchmarksLeaderboard(
54
+ report: RunBenchmarksReport,
55
+ opts: { title?: string; meta?: Record<string, string> } = {},
56
+ ): Leaderboard {
57
+ return leaderboard(toRecords(report.perTask), {
58
+ title: opts.title ?? 'Benchmark matrix — cell × benchmark',
59
+ // The cell (harness/model/persona) is the row label; one column per benchmark.
60
+ profileKeyOf: (r) => r.candidateId,
61
+ groupOf: (r) => r.scenarioId ?? r.experimentId,
62
+ ...(opts.meta ? { meta: opts.meta } : {}),
63
+ })
64
+ }
65
+
66
+ export { renderLeaderboardHtml, renderLeaderboardMarkdown, renderLeaderboardSvg }