@hona/openeval 0.5.1 → 0.5.2

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 (181) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/src/app/load-benchmark.ts +9 -0
  4. package/src/app/plan-benchmark.ts +23 -5
  5. package/src/app/read-results.ts +79 -42
  6. package/src/app/recover-run.ts +61 -0
  7. package/src/app/rejudge.ts +9 -5
  8. package/src/app/retry-run.ts +9 -5
  9. package/src/app/run-benchmark.ts +21 -5
  10. package/src/app/scores.ts +19 -19
  11. package/src/app/serve-results.ts +10 -5
  12. package/src/cli.ts +3 -0
  13. package/src/types.ts +7 -0
  14. package/src/view.ts +7 -0
  15. package/viewer/assets/{abnfDiagram-VCTEODGH-4dcAM__t.js → abnfDiagram-VCTEODGH-DheibVLk.js} +1 -1
  16. package/viewer/assets/{angular-html-DCa1K9Q5.js → angular-html-4AdCdqA_.js} +1 -1
  17. package/viewer/assets/{angular-ts-BwOaP4mi.js → angular-ts-D-G3eLWz.js} +1 -1
  18. package/viewer/assets/{apl-BDVbqe8d.js → apl-Ckl95Nnu.js} +1 -1
  19. package/viewer/assets/{arc-vf_TPbdA.js → arc-DBaUWWiS.js} +1 -1
  20. package/viewer/assets/architecture-7GRP2DOG-DtdqvSTn.js +1 -0
  21. package/viewer/assets/{architectureDiagram-5GKGNRK7-D9I5g97H.js → architectureDiagram-5GKGNRK7-BSEYyvUG.js} +1 -1
  22. package/viewer/assets/{astro-C4AVO9I1.js → astro-BpiishHg.js} +1 -1
  23. package/viewer/assets/{blade-1K2d7b5P.js → blade-DvRbLCOy.js} +1 -1
  24. package/viewer/assets/{blockDiagram-I7D4REHJ-CnebaxAP.js → blockDiagram-I7D4REHJ-DLxyKv07.js} +1 -1
  25. package/viewer/assets/{c-CrFxx71c.js → c-BZYE5LIm.js} +1 -1
  26. package/viewer/assets/{c4Diagram-7LVT6UL2-CZv4bp92.js → c4Diagram-7LVT6UL2--I50ySVl.js} +1 -1
  27. package/viewer/assets/channel-BksTOWku.js +1 -0
  28. package/viewer/assets/{chapel-CaV_aD0h.js → chapel-T-o4rT-z.js} +1 -1
  29. package/viewer/assets/{chunk-4HAMMTFA-1oAN0Hqj.js → chunk-4HAMMTFA-B9xd7TwX.js} +1 -1
  30. package/viewer/assets/{chunk-75Z2AOVW-DG-4lG9j.js → chunk-75Z2AOVW-B4XIg4PJ.js} +1 -1
  31. package/viewer/assets/{chunk-DU6HZSFF-K8r4K98Q.js → chunk-DU6HZSFF-Cty-qidK.js} +1 -1
  32. package/viewer/assets/{chunk-F27PBJKO-DOLcDIe-.js → chunk-F27PBJKO-8WDDKCG7.js} +1 -1
  33. package/viewer/assets/{chunk-GMAD6QVW-fbOr8e3A.js → chunk-GMAD6QVW-G948aN1X.js} +1 -1
  34. package/viewer/assets/{chunk-GVQU2GXP-DGskHf86.js → chunk-GVQU2GXP-CVFpDQVF.js} +1 -1
  35. package/viewer/assets/{chunk-IMKFNOWR-BkiL77Of.js → chunk-IMKFNOWR-BUQviXHJ.js} +1 -1
  36. package/viewer/assets/{chunk-L3NEJ4N5-rHC84FEy.js → chunk-L3NEJ4N5-BXvzxNPv.js} +1 -1
  37. package/viewer/assets/{chunk-OSK3NFVY-CDEFeiSo.js → chunk-OSK3NFVY-wLdUx9yD.js} +1 -1
  38. package/viewer/assets/{chunk-P2QGCYS3-BHNuVs3t.js → chunk-P2QGCYS3-hDwRspSg.js} +1 -1
  39. package/viewer/assets/{chunk-POPQ4Y6H-8IU6HL0N.js → chunk-POPQ4Y6H-LbeiA8tL.js} +1 -1
  40. package/viewer/assets/{chunk-PWAF6VOD--FR_J-_V.js → chunk-PWAF6VOD-BcGZUKY4.js} +1 -1
  41. package/viewer/assets/{chunk-SHT3W25Y-DHfu0YQd.js → chunk-SHT3W25Y-BqKh1v-r.js} +1 -1
  42. package/viewer/assets/{chunk-SVP7TREG-CZxnTp0N.js → chunk-SVP7TREG-BEMJeRHW.js} +1 -1
  43. package/viewer/assets/{chunk-TICWLB2K-DeYv6m7z.js → chunk-TICWLB2K-BiHATE6U.js} +1 -1
  44. package/viewer/assets/{chunk-XXDRQBXY-B-stp2Jg.js → chunk-XXDRQBXY-B0X2aTwI.js} +1 -1
  45. package/viewer/assets/classDiagram-ZZMXUADV-Cbfu0NLs.js +1 -0
  46. package/viewer/assets/classDiagram-v2-VYDZK3BY-Cbfu0NLs.js +1 -0
  47. package/viewer/assets/{cobol-Dfqw6e9L.js → cobol-DS-OUv7W.js} +1 -1
  48. package/viewer/assets/{coffee-B0VyEiZ_.js → coffee-UJZBXrQS.js} +1 -1
  49. package/viewer/assets/{cose-bilkent-JH36ORCC-DwO-FoMO.js → cose-bilkent-JH36ORCC-h7Gx4dzS.js} +1 -1
  50. package/viewer/assets/{cpp-DtAnLTpm.js → cpp-BuO_io74.js} +1 -1
  51. package/viewer/assets/{crystal-DR276aYT.js → crystal-D_Q6HPhm.js} +1 -1
  52. package/viewer/assets/{css-jcQdyKCB.js → css-B9bdOUvs.js} +1 -1
  53. package/viewer/assets/{cynefin-OW5HDTMX-CkCcLfFW.js → cynefin-OW5HDTMX-BONm5P8S.js} +1 -1
  54. package/viewer/assets/{cynefinDiagram-5FMLGOSQ-Btlq2-ol.js → cynefinDiagram-5FMLGOSQ-CWsyzZBO.js} +1 -1
  55. package/viewer/assets/{dagre-GXQ25YYZ-BtEJZ3id.js → dagre-GXQ25YYZ-CJG_-oax.js} +1 -1
  56. package/viewer/assets/{diagram-S7CK7UJ4-BdLily-u.js → diagram-S7CK7UJ4-uOGlO2Fe.js} +1 -1
  57. package/viewer/assets/{diagram-UQ7AKVKN-DBfD46yQ.js → diagram-UQ7AKVKN-B2mgymOu.js} +1 -1
  58. package/viewer/assets/{diagram-VSXAHHWV-BJuAmu9T.js → diagram-VSXAHHWV-UkXvypTd.js} +1 -1
  59. package/viewer/assets/{diagram-VX7I27RA-BVEEYjj-.js → diagram-VX7I27RA-Bzge-YWf.js} +1 -1
  60. package/viewer/assets/{diagram-Z3DM3KII-DqYpuagj.js → diagram-Z3DM3KII-BAqqEKF_.js} +1 -1
  61. package/viewer/assets/{dist-B0PK9u1q.js → dist-CACpyRbj.js} +1 -1
  62. package/viewer/assets/{ebnfDiagram-PWID7BFC-DafUAY97.js → ebnfDiagram-PWID7BFC-dsRp8_0H.js} +1 -1
  63. package/viewer/assets/{edge-CMwzwqao.js → edge-DUnvwjmM.js} +1 -1
  64. package/viewer/assets/{elixir-D0m0fb5e.js → elixir-QfE-NI0D.js} +1 -1
  65. package/viewer/assets/{elm-Dm3Vhwyo.js → elm-D4J4FYyf.js} +1 -1
  66. package/viewer/assets/{erDiagram-RLTQ6QDP-BjTQFP-n.js → erDiagram-RLTQ6QDP-Dmfq32IQ.js} +1 -1
  67. package/viewer/assets/{erb-BWrVRqIb.js → erb-DfUVZzqX.js} +1 -1
  68. package/viewer/assets/eventmodeling-NTZA5JFV-BsZg3yrD.js +1 -0
  69. package/viewer/assets/flowDiagram-HODETNUW-55jB5pet.js +1 -0
  70. package/viewer/assets/{ganttDiagram-EL5Y4UJY-cvpUkVwQ.js → ganttDiagram-EL5Y4UJY-DVTv_n_T.js} +1 -1
  71. package/viewer/assets/{git-rebase-Bx9WSw2T.js → git-rebase-B22-kUR2.js} +1 -1
  72. package/viewer/assets/{gitGraph-4MIJSDKK-YF9cEiQc.js → gitGraph-4MIJSDKK-C3ZU1Sml.js} +1 -1
  73. package/viewer/assets/{gitGraphDiagram-WWUBYQGX-Cf7LUlAN.js → gitGraphDiagram-WWUBYQGX-BJqeOFYd.js} +1 -1
  74. package/viewer/assets/{glimmer-js-jJ5_T2M9.js → glimmer-js-Cm5f8kCy.js} +1 -1
  75. package/viewer/assets/{glimmer-ts-DqUZ7xnF.js → glimmer-ts-CQ-nNZCl.js} +1 -1
  76. package/viewer/assets/{glsl-DHzsK5Yf.js → glsl-XxfyrGb3.js} +1 -1
  77. package/viewer/assets/{graphql-TivQ3Vbm.js → graphql-DBlG1DNQ.js} +1 -1
  78. package/viewer/assets/{hack-CXzUKlVo.js → hack-Dkc1g494.js} +1 -1
  79. package/viewer/assets/{haml-DB9hsLVR.js → haml-D9E0-Hw0.js} +1 -1
  80. package/viewer/assets/{handlebars-Dfxd-Vhz.js → handlebars-Cb17q7C4.js} +1 -1
  81. package/viewer/assets/{html-CwO_HZk6.js → html-5E3Hc3ha.js} +1 -1
  82. package/viewer/assets/{html-derivative-Da9QhqUr.js → html-derivative-DOZTqg-2.js} +1 -1
  83. package/viewer/assets/{http-DaNELgRt.js → http-DJH-LMLV.js} +1 -1
  84. package/viewer/assets/{hurl-DCibLrD6.js → hurl-CEYg3T6-.js} +1 -1
  85. package/viewer/assets/{index-DVYWRkK6.css → index-CkY7ZfKs.css} +1 -1
  86. package/viewer/assets/{index-Cc4pf8mQ.js → index-Dfdu9MKA.js} +76 -76
  87. package/viewer/assets/{info-A6RAGUB7-B7Ru1Oky.js → info-A6RAGUB7-B-4F1obM.js} +1 -1
  88. package/viewer/assets/{infoDiagram-27XIBGKW-RlWd2kYA.js → infoDiagram-27XIBGKW-B8RIO086.js} +1 -1
  89. package/viewer/assets/{ishikawaDiagram-5VMMS53U-CS2-lnGi.js → ishikawaDiagram-5VMMS53U-DIGOldDh.js} +1 -1
  90. package/viewer/assets/{java-D2z6ozlO.js → java-e3vi9Uzs.js} +1 -1
  91. package/viewer/assets/{javascript-CxVsIZNi.js → javascript-DXnYMYO7.js} +1 -1
  92. package/viewer/assets/{jinja-BSUcBlS_.js → jinja-C-Qo2832.js} +1 -1
  93. package/viewer/assets/{jison-qcnqXcSy.js → jison-CfzTRAm0.js} +1 -1
  94. package/viewer/assets/{journeyDiagram-3NMN7TZE-DqPSRu4-.js → journeyDiagram-3NMN7TZE-DbQ5PKPQ.js} +1 -1
  95. package/viewer/assets/{json-eiyev3x0.js → json-BaqYW62L.js} +1 -1
  96. package/viewer/assets/{jsx-rDHm7tH3.js → jsx-DTafsOf-.js} +1 -1
  97. package/viewer/assets/{julia-cFfLusXt.js → julia-Djdi6Tch.js} +1 -1
  98. package/viewer/assets/{just-D6wClmab.js → just-gZxWcw47.js} +1 -1
  99. package/viewer/assets/{kanban-definition-UXKFOSKX-BI_a5cRc.js → kanban-definition-UXKFOSKX-CwEeXphJ.js} +1 -1
  100. package/viewer/assets/{latex-BvnFBRMG.js → latex-CIO1OdmU.js} +1 -1
  101. package/viewer/assets/{line-ChPI9Rz5.js → line-Basn7TUv.js} +1 -1
  102. package/viewer/assets/{linear-CBr5H_et.js → linear-Bu88wzXh.js} +1 -1
  103. package/viewer/assets/{liquid-CIOCL1uY.js → liquid-mYuCSXLi.js} +1 -1
  104. package/viewer/assets/{lua-Dq6WuAHP.js → lua-lXhbu8ma.js} +1 -1
  105. package/viewer/assets/{marko-Nl8_Nklj.js → marko-BH-anPb4.js} +1 -1
  106. package/viewer/assets/{mdc-obW39E44.js → mdc-DOuUtwPr.js} +1 -1
  107. package/viewer/assets/{mermaid-parser.core-BFOx64cQ.js → mermaid-parser.core-CiGT3tRM.js} +3 -3
  108. package/viewer/assets/{mermaid.core-BDe6Tfbh.js → mermaid.core-BfFqBx2V.js} +4 -4
  109. package/viewer/assets/{mindmap-definition-YA3MSWOX-CjzA1VTL.js → mindmap-definition-YA3MSWOX-d_TnVAJG.js} +1 -1
  110. package/viewer/assets/{nginx-Czz1mxQD.js → nginx-BB1dPcHJ.js} +1 -1
  111. package/viewer/assets/{nim-Boz13cJ3.js → nim-BSJ4UdAS.js} +1 -1
  112. package/viewer/assets/{org-BOlhXpYw.js → org-JRn8h-K_.js} +1 -1
  113. package/viewer/assets/{packet-AYTQ26CC-MMkKPhzF.js → packet-AYTQ26CC-BA7VIQ3l.js} +1 -1
  114. package/viewer/assets/{pegDiagram-XKGWAZYB-q1Rf8_Y9.js → pegDiagram-XKGWAZYB-pfsoYtx9.js} +1 -1
  115. package/viewer/assets/{perl-BA7VmyiG.js → perl-GzZ7epP9.js} +1 -1
  116. package/viewer/assets/{php-dYlaksWx.js → php-Crolu7lU.js} +1 -1
  117. package/viewer/assets/{pie-WAS4IAKB-CLljEA6l.js → pie-WAS4IAKB-B9gCbpVP.js} +1 -1
  118. package/viewer/assets/{pieDiagram-E7YTZNPT-DqzGIO9H.js → pieDiagram-E7YTZNPT-D578iJLj.js} +1 -1
  119. package/viewer/assets/{pug-WDsqiRUK.js → pug-BRaBHOlA.js} +1 -1
  120. package/viewer/assets/{qml-B5P0chYn.js → qml-BBlfnxMr.js} +1 -1
  121. package/viewer/assets/{quadrantDiagram-AXDQQJYC-JV3sYNXt.js → quadrantDiagram-AXDQQJYC-DoKeLBeZ.js} +1 -1
  122. package/viewer/assets/{r-DmZfBDub.js → r-DG1YsJtP.js} +1 -1
  123. package/viewer/assets/{radar-RG4KPBEZ-DH4HiGh1.js → radar-RG4KPBEZ-Dr6Zcwqy.js} +1 -1
  124. package/viewer/assets/{railroad-74A4TZTK-BwJg41V9.js → railroad-74A4TZTK-CEncWApU.js} +1 -1
  125. package/viewer/assets/railroad-abnf-HS5TGJTU-BUgwRsng.js +1 -0
  126. package/viewer/assets/railroad-ebnf-LZEXJU2U-C22SR2MX.js +1 -0
  127. package/viewer/assets/railroad-peg-WCYAUIDC-DjI1jJwc.js +1 -0
  128. package/viewer/assets/{railroadDiagram-O6MQD6OU-CrclSzRj.js → railroadDiagram-O6MQD6OU-7j0Th0r6.js} +1 -1
  129. package/viewer/assets/{razor-BLQfBppW.js → razor-CY_sYdwH.js} +1 -1
  130. package/viewer/assets/{regexp-CePe_vV5.js → regexp-DZiZuF5z.js} +1 -1
  131. package/viewer/assets/{requirementDiagram-BXWQKSXE-5QlkAVAN.js → requirementDiagram-BXWQKSXE-Dk1vCdh-.js} +1 -1
  132. package/viewer/assets/{rst-D9pCulha.js → rst-Dn_AtwYd.js} +1 -1
  133. package/viewer/assets/{ruby-r3EAH5Rz.js → ruby-BIB6MqpW.js} +1 -1
  134. package/viewer/assets/{sankeyDiagram-P5KCCOFB-BJzQ5vkn.js → sankeyDiagram-P5KCCOFB-DaB06Zgj.js} +1 -1
  135. package/viewer/assets/{sas-Dxc2YEvn.js → sas-Ccxd8Air.js} +1 -1
  136. package/viewer/assets/{scss-rZd3d-ZI.js → scss-Cr7j0d4P.js} +1 -1
  137. package/viewer/assets/{sequenceDiagram-WJ2MYXX4--tS5gMY4.js → sequenceDiagram-WJ2MYXX4-BGEb3NdT.js} +1 -1
  138. package/viewer/assets/{shellscript-B-b5xnkP.js → shellscript-CHN-dwU6.js} +1 -1
  139. package/viewer/assets/{shellsession-DlWXnhiW.js → shellsession-uVGeQNm3.js} +1 -1
  140. package/viewer/assets/{soy-DXWgq6qD.js → soy-D7NWwJ5L.js} +1 -1
  141. package/viewer/assets/{sql-5vPpIo1-.js → sql-DRImQ2Oi.js} +1 -1
  142. package/viewer/assets/{src-eDHFRM9C.js → src-BnSwVg9U.js} +1 -1
  143. package/viewer/assets/{stata-BPQLlmzg.js → stata-BIe0AUpg.js} +1 -1
  144. package/viewer/assets/{stateDiagram-D77RDMKH-xapk0kUZ.js → stateDiagram-D77RDMKH-DsRu-B2h.js} +1 -1
  145. package/viewer/assets/stateDiagram-v2-MP3YSRHH-DtGwflXN.js +1 -0
  146. package/viewer/assets/{surrealql-CTEIAZfm.js → surrealql-LEEn9cBl.js} +1 -1
  147. package/viewer/assets/{svelte-B6EVsDV1.js → svelte-CAMB-f9H.js} +1 -1
  148. package/viewer/assets/{swimlanes-42K2YHIH-BLyG_o3-.js → swimlanes-42K2YHIH-DqXHORT2.js} +1 -1
  149. package/viewer/assets/swimlanesDiagram-VR7AAH4N-DCpSoqRu.js +8 -0
  150. package/viewer/assets/{templ-Bu4quHqE.js → templ-zrRSW6Nf.js} +1 -1
  151. package/viewer/assets/{tex-Dv_54fPG.js → tex-D4XEXDuZ.js} +1 -1
  152. package/viewer/assets/{timeline-definition-24CTP7MA-DBaZM3qq.js → timeline-definition-24CTP7MA-DSpHKRpt.js} +1 -1
  153. package/viewer/assets/{treeView-Q6P3EWNA-C3zH0Rwt.js → treeView-Q6P3EWNA-pE7RJ0LU.js} +1 -1
  154. package/viewer/assets/{treemap-WGGIJYW6-BTho8OjG.js → treemap-WGGIJYW6-CBfU2tIc.js} +1 -1
  155. package/viewer/assets/{ts-tags-0DHV6Smm.js → ts-tags-DqFdF1-k.js} +1 -1
  156. package/viewer/assets/{tsx-CmZ6PB1U.js → tsx-Dz5osFq9.js} +1 -1
  157. package/viewer/assets/{twig-u99yWuG6.js → twig-CQ3wxbq3.js} +1 -1
  158. package/viewer/assets/{typescript-WF79ZIBQ.js → typescript-Cu1dPDHw.js} +1 -1
  159. package/viewer/assets/{typst-DQ7XxvRZ.js → typst-DHrnWrov.js} +1 -1
  160. package/viewer/assets/{vennDiagram-4TSXK5OY-B5Cb69qm.js → vennDiagram-4TSXK5OY-BPPSR6nr.js} +1 -1
  161. package/viewer/assets/{vue-DXac3mhj.js → vue-BaoJW0em.js} +1 -1
  162. package/viewer/assets/{vue-html-FSSTYK94.js → vue-html-DB9LdXl9.js} +1 -1
  163. package/viewer/assets/{vue-vine-BDF8epqI.js → vue-vine-CqtlBPYW.js} +1 -1
  164. package/viewer/assets/{wardley-WFR3VGLG-DswcXSmz.js → wardley-WFR3VGLG-DfCbsSvk.js} +1 -1
  165. package/viewer/assets/{wardleyDiagram-VM6X3IG4-BPgNi0Kp.js → wardleyDiagram-VM6X3IG4-DeiKcJbE.js} +1 -1
  166. package/viewer/assets/{xml-HI856iuk.js → xml-CQ5T6aNZ.js} +1 -1
  167. package/viewer/assets/{xsl-Ck-aKGwi.js → xsl-DVQ2v5YX.js} +1 -1
  168. package/viewer/assets/{xychartDiagram-S5SC5T6Z-B8UAJr6c.js → xychartDiagram-S5SC5T6Z-BsvlKd7r.js} +1 -1
  169. package/viewer/assets/{yaml-DrPyW5uV.js → yaml-DXrThoWS.js} +1 -1
  170. package/viewer/index.html +2 -2
  171. package/viewer/assets/architecture-7GRP2DOG-ClUScBK0.js +0 -1
  172. package/viewer/assets/channel-DOp5Ibzq.js +0 -1
  173. package/viewer/assets/classDiagram-ZZMXUADV-fi0_kah3.js +0 -1
  174. package/viewer/assets/classDiagram-v2-VYDZK3BY-fi0_kah3.js +0 -1
  175. package/viewer/assets/eventmodeling-NTZA5JFV-QswxRol8.js +0 -1
  176. package/viewer/assets/flowDiagram-HODETNUW-DolIsASs.js +0 -1
  177. package/viewer/assets/railroad-abnf-HS5TGJTU-Byryy4k9.js +0 -1
  178. package/viewer/assets/railroad-ebnf-LZEXJU2U-BABBdJeJ.js +0 -1
  179. package/viewer/assets/railroad-peg-WCYAUIDC-C7YNbOxe.js +0 -1
  180. package/viewer/assets/stateDiagram-v2-MP3YSRHH-Cb8zYuMi.js +0 -1
  181. package/viewer/assets/swimlanesDiagram-VR7AAH4N-RLMdNA3S.js +0 -8
package/README.md CHANGED
@@ -143,7 +143,9 @@ bunx --bun @hona/openeval view
143
143
  ```
144
144
 
145
145
  The viewer opens at **http://127.0.0.1:4173**. `run` resumes the same aggregate;
146
- scope flags select work while retaining existing scores.
146
+ scope flags select work while retaining existing scores. If a runner stops
147
+ mid-run, the next `run` waits until its heartbeat is two minutes old, keeps the
148
+ interrupted records, and collects that work again.
147
149
 
148
150
  To remove a model from an existing aggregate, remove its entry from
149
151
  `benchmark.ts`, then retire its active selections:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hona/openeval",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Code and LLM evaluations with isolated agents, recorded OpenCode data, and a results viewer",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -266,6 +266,7 @@ export async function loadBenchmark(
266
266
  (key) =>
267
267
  ![
268
268
  "name",
269
+ "suite",
269
270
  "models",
270
271
  "judge",
271
272
  "repetitions",
@@ -277,6 +278,13 @@ export async function loadBenchmark(
277
278
  )
278
279
  )
279
280
  throw new Error("benchmark.ts contains unsupported settings");
281
+ if (
282
+ definition.suite !== undefined &&
283
+ (typeof definition.suite !== "string" ||
284
+ !definition.suite.trim() ||
285
+ definition.suite.length > 80)
286
+ )
287
+ throw new Error("benchmark.ts suite must be a label of 1 to 80 characters");
280
288
  if (
281
289
  definition.categories !== undefined &&
282
290
  (!Array.isArray(definition.categories) || !definition.categories.length)
@@ -352,6 +360,7 @@ export async function loadBenchmark(
352
360
  throw new Error("Websearch must be exa or false");
353
361
  return {
354
362
  name: definition.name ?? basename(directory),
363
+ ...(definition.suite ? { suite: definition.suite.trim() } : {}),
355
364
  directory,
356
365
  models,
357
366
  evals,
@@ -22,14 +22,18 @@ export {
22
22
  export const slotId = (evalId: string, model: ModelRef, repetition: number) =>
23
23
  `${evalId}:${model}:${repetition}`;
24
24
 
25
- /** Plan only missing or changed work. A failed execution requires an explicit retry. */
25
+ /** Plan only missing or changed work. A failed execution requires an explicit retry,
26
+ * except work a stopped runner interrupted. `stoppedRunner` plans open executions that way. */
26
27
  export function planBenchmark(
27
28
  definition: BenchmarkDefinition,
28
29
  runtime: BenchmarkRun["runtime"],
29
30
  results: Results,
31
+ options: { stoppedRunner?: boolean } = {},
30
32
  ): PlanItem[] {
31
33
  const previous = new Map(results.slots().map((slot) => [slot.id, slot]));
32
34
  const judgments = results.judgeRuns();
35
+ const interrupted = (run: { state: string; interrupted?: boolean }) =>
36
+ !!run.interrupted || (!!options.stoppedRunner && run.state === "running");
33
37
  return Array.from(
34
38
  { length: definition.repetitions },
35
39
  (_, index) => index + 1,
@@ -70,6 +74,17 @@ export function planBenchmark(
70
74
  : "New eval/model/repetition",
71
75
  };
72
76
  const execution = results.evalRun(slot.evalRunId)!;
77
+ if (interrupted(execution))
78
+ return {
79
+ slot: {
80
+ ...slot,
81
+ evalRunId: null,
82
+ judgeRunId: null,
83
+ previousEvalRunId: execution.id,
84
+ },
85
+ action: "candidate",
86
+ reason: "The runner stopped during this session; collecting it again",
87
+ };
73
88
  if (!canJudgeEval(execution))
74
89
  return {
75
90
  slot,
@@ -119,12 +134,13 @@ export function planBenchmark(
119
134
  action: "reuse",
120
135
  reason: "Candidate inputs and judgment unchanged",
121
136
  };
122
- const failed = judgments.find(
137
+ const attempts = judgments.filter(
123
138
  (run) =>
124
139
  run.input.evalRunId === slot.evalRunId &&
125
140
  run.input.judgeHash === slot.judgeHash &&
126
141
  run.state !== "completed",
127
142
  );
143
+ const failed = attempts.find((run) => !interrupted(run));
128
144
  if (failed)
129
145
  return {
130
146
  slot,
@@ -134,9 +150,11 @@ export function planBenchmark(
134
150
  return {
135
151
  slot,
136
152
  action: "judge",
137
- reason: !sameJudge
138
- ? "Judge inputs changed"
139
- : "Recorded work is ready for judging",
153
+ reason: attempts.length
154
+ ? "The runner stopped during judging; judging again"
155
+ : !sameJudge
156
+ ? "Judge inputs changed"
157
+ : "Recorded work is ready for judging",
140
158
  };
141
159
  }),
142
160
  ),
@@ -58,6 +58,7 @@ const entryOf = (
58
58
  name: evalId
59
59
  ? (run.definition.evals.find((item) => item.id === evalId)?.name ?? evalId)
60
60
  : run.name,
61
+ ...(!evalId && run.definition.suite ? { suite: run.definition.suite } : {}),
61
62
  status: run.state,
62
63
  startedAt: run.createdAt,
63
64
  updatedAt: Date.parse(run.updatedAt),
@@ -78,6 +79,7 @@ export class ResultReader {
78
79
  benchmark: BenchmarkRun,
79
80
  now: number,
80
81
  candidate?: EvalRun,
82
+ judges?: readonly JudgeRun[],
81
83
  ) {
82
84
  const key = `${results.path}:${run.id}`;
83
85
  const cached = this.runtimes.get(key);
@@ -91,8 +93,7 @@ export class ResultReader {
91
93
  scheduled(slot, benchmark, now);
92
94
  if (current && "evalRunId" in run.input)
93
95
  current =
94
- results
95
- .judgeRuns()
96
+ (judges ?? results.judgeRuns())
96
97
  .filter(
97
98
  (judge) =>
98
99
  judge.input.evalRunId === candidate!.id &&
@@ -279,7 +280,16 @@ export class ResultReader {
279
280
  evalId,
280
281
  ),
281
282
  );
282
- const candidates = results.evalRuns(),
283
+ // Runtime and cost describe the work behind the active selections, not retired history.
284
+ const active = new Set(
285
+ results
286
+ .slots()
287
+ .filter((slot) => slot.active)
288
+ .map((slot) => slot.id),
289
+ );
290
+ const candidates = results
291
+ .evalRuns()
292
+ .filter((candidate) => active.has(candidate.slotId)),
283
293
  allJudges = results.judgeRuns();
284
294
  const now = Date.now(),
285
295
  candidateIndex = new Map(candidates.map((run) => [run.id, run]));
@@ -291,7 +301,9 @@ export class ResultReader {
291
301
  : (execution as EvalRun);
292
302
  if (!candidate) continue;
293
303
  const intervals = evalRuntime.get(candidate.input.evalId) ?? [];
294
- intervals.push(...this.runtime(execution, results, run, now, candidate));
304
+ intervals.push(
305
+ ...this.runtime(execution, results, run, now, candidate, allJudges),
306
+ );
295
307
  evalRuntime.set(candidate.input.evalId, intervals);
296
308
  }
297
309
  for (const [id, intervals] of evalRuntime)
@@ -323,6 +335,9 @@ export class ResultReader {
323
335
  entry: entryOf(run, id, evalId),
324
336
  overview: {
325
337
  name: evalId ? evals[0].name : run.name,
338
+ ...(!evalId && run.definition.suite
339
+ ? { suite: run.definition.suite }
340
+ : {}),
326
341
  kind: evalId ? "eval" : "benchmark",
327
342
  status: run.state,
328
343
  startedAt: run.createdAt,
@@ -353,59 +368,69 @@ export class ResultReader {
353
368
  if (split < 0) throw new Error("Select an eval");
354
369
  const evalId = decodeURIComponent(id.slice(split + 1));
355
370
  using results = await this.database(id);
356
- const benchmark = results.benchmark!,
357
- executions = results.evalRuns(),
358
- judges = results.judgeRuns();
359
- const runs = results
360
- .slots()
361
- .filter((slot) => slot.active && slot.evalId === evalId)
362
- .map((slot) => {
363
- const candidate = executions.find((run) => run.id === slot.evalRunId);
364
- const current = liveRun(
365
- slot,
366
- candidate,
367
- judges,
368
- benchmark,
369
- results,
370
- (run) => this.runtime(run, results, benchmark, Date.now(), candidate),
371
- );
372
- const related = executions.filter((run) => run.slotId === slot.id),
373
- ids = new Set(related.map((run) => run.id));
374
- return {
375
- ...current,
376
- cost: runCosts(
377
- [
378
- ...related,
379
- ...judges.filter((run) => ids.has(run.input.evalRunId)),
380
- ],
381
- results,
382
- ),
383
- elapsedMs: runtimeMs(
384
- [...current.eval.runtime, ...current.judge.runtime],
385
- Date.now(),
386
- ),
387
- };
388
- });
371
+ const benchmark = results.benchmark!;
389
372
  return {
390
373
  entry: entryOf(benchmark, id, evalId),
391
374
  eval: evalId,
392
375
  benchmarkId: benchmark.id,
393
- runs,
376
+ runs: this.liveRuns(results, [evalId]).get(evalId)!,
394
377
  };
395
378
  }
379
+ /** Active slot rows for the requested evals, from one read of the store. */
380
+ private liveRuns(results: Results, evalIds: readonly string[]) {
381
+ const benchmark = results.benchmark!,
382
+ executions = results.evalRuns(),
383
+ judges = results.judgeRuns(),
384
+ now = Date.now();
385
+ const byId = new Map(executions.map((run) => [run.id, run]));
386
+ const bySlot = Map.groupBy(executions, (run) => run.slotId);
387
+ const judgesFor = Map.groupBy(judges, (run) => run.input.evalRunId);
388
+ const out = new Map<string, EvalRunSummary[]>(
389
+ evalIds.map((evalId) => [evalId, []]),
390
+ );
391
+ for (const slot of results.slots()) {
392
+ const rows = out.get(slot.evalId);
393
+ if (!slot.active || !rows) continue;
394
+ const candidate = slot.evalRunId ? byId.get(slot.evalRunId) : undefined;
395
+ const current = liveRun(
396
+ slot,
397
+ candidate,
398
+ candidate ? (judgesFor.get(candidate.id) ?? []) : [],
399
+ benchmark,
400
+ results,
401
+ (run) => this.runtime(run, results, benchmark, now, candidate, judges),
402
+ );
403
+ const related = bySlot.get(slot.id) ?? [];
404
+ rows.push({
405
+ ...current,
406
+ cost: runCosts(
407
+ [...related, ...related.flatMap((run) => judgesFor.get(run.id) ?? [])],
408
+ results,
409
+ ),
410
+ elapsedMs: runtimeMs(
411
+ [...current.eval.runtime, ...current.judge.runtime],
412
+ now,
413
+ ),
414
+ });
415
+ }
416
+ return out;
417
+ }
396
418
  async activity(): Promise<ActivityRun[]> {
397
419
  const index = await this.index(),
398
420
  out: ActivityRun[] = [];
399
421
  for (const entry of index.entries) {
422
+ const summary = await this.summary(entry.id);
400
423
  using results = await this.database(entry.id);
401
424
  const benchmark = results.benchmark!;
402
- const summary = await this.summary(entry.id);
425
+ const live = this.liveRuns(
426
+ results,
427
+ benchmark.definition.evals.map((item) => item.id),
428
+ );
403
429
  for (const [
404
430
  evalOrder,
405
431
  evalDefinition,
406
432
  ] of benchmark.definition.evals.entries()) {
407
- const id = evalResultId(benchmark.id, evalDefinition.id),
408
- runs = await this.evalRuns(id);
433
+ const id = evalResultId(benchmark.id, evalDefinition.id);
409
434
  out.push({
410
435
  id,
411
436
  benchmarkId: benchmark.id,
@@ -415,15 +440,27 @@ export class ResultReader {
415
440
  id,
416
441
  eval: evalDefinition.id,
417
442
  benchmark: benchmark.id,
443
+ name: benchmark.name,
444
+ ...(benchmark.definition.suite
445
+ ? { suite: benchmark.definition.suite }
446
+ : {}),
418
447
  startedAt: benchmark.createdAt,
419
448
  heartbeatAt: benchmark.updatedAt,
420
449
  status: benchmark.state,
421
450
  concurrency: benchmark.definition.concurrency,
422
451
  evalOrder,
423
- runs: runs.runs,
452
+ runs: live.get(evalDefinition.id)!,
424
453
  runtime: summary.related.find(
425
454
  (item) => item.eval === evalDefinition.id,
426
455
  )!.runtime,
456
+ ...(benchmark.execution
457
+ ? {
458
+ invocation: {
459
+ startedAt: benchmark.execution.startedAt,
460
+ spentUSD: benchmark.execution.spentUSD,
461
+ },
462
+ }
463
+ : {}),
427
464
  },
428
465
  });
429
466
  }
@@ -0,0 +1,61 @@
1
+ import type { BenchmarkRun } from "../types";
2
+ import type { Results } from "../infra/sqlite";
3
+
4
+ /** A live runner saves its heartbeat every five seconds. */
5
+ export const STOPPED_RUNNER_MS = 120_000;
6
+
7
+ export const runnerStopped = (
8
+ benchmark: Pick<BenchmarkRun, "state" | "updatedAt">,
9
+ now = Date.now(),
10
+ ) =>
11
+ benchmark.state === "running" &&
12
+ now - Date.parse(benchmark.updatedAt) > STOPPED_RUNNER_MS;
13
+
14
+ /** Close the executions a stopped runner left open. Their records stay; the planner repeats the work. */
15
+ export function recoverStoppedRunner(results: Results, now = new Date()) {
16
+ const time = now.toISOString();
17
+ const elapsedMs = (startedAt: string) =>
18
+ Math.max(0, now.getTime() - Date.parse(startedAt));
19
+ const recovered = { evalRuns: 0, judgeRuns: 0 };
20
+ results.transaction(() => {
21
+ for (const run of results.judgeRuns()) {
22
+ if (run.state !== "running") continue;
23
+ for (const check of results.judgeChecks(run.id))
24
+ if (check.state === "running")
25
+ results.finishJudgeCheck({
26
+ ...check,
27
+ state: "cancelled",
28
+ completedAt: time,
29
+ });
30
+ results.finishJudge({
31
+ ...run,
32
+ state: "failed",
33
+ interrupted: true,
34
+ error: "The runner stopped before judging finished",
35
+ completedAt: time,
36
+ elapsedMs: elapsedMs(run.startedAt),
37
+ });
38
+ recovered.judgeRuns++;
39
+ }
40
+ for (const run of results.evalRuns()) {
41
+ if (run.state !== "running") continue;
42
+ results.finishEval({
43
+ ...run,
44
+ state: "failed",
45
+ interrupted: true,
46
+ error: "The runner stopped before this session finished",
47
+ completedAt: time,
48
+ elapsedMs: elapsedMs(run.startedAt),
49
+ ...(run.stop ? { stop: { ...run.stop, applied: false } } : {}),
50
+ });
51
+ recovered.evalRuns++;
52
+ }
53
+ results.saveBenchmark({
54
+ ...results.benchmark!,
55
+ state: "incomplete",
56
+ scheduledSlotIds: [],
57
+ updatedAt: time,
58
+ });
59
+ });
60
+ return recovered;
61
+ }
@@ -8,6 +8,7 @@ import { executeQueue, finishBenchmark } from "./run-benchmark";
8
8
  import { judgingFingerprint } from "../infra/judging";
9
9
  import { canJudgeEval } from "./eval-state";
10
10
  import { verificationRuntime } from "../infra/verification/image";
11
+ import { recoverStoppedRunner, runnerStopped } from "./recover-run";
11
12
 
12
13
  /** Rejudging changes only the selected judgment. The candidate execution is never repeated. */
13
14
  export async function judgeRun(
@@ -26,13 +27,16 @@ export async function judgeRuns(
26
27
  ): Promise<JudgeRun[]> {
27
28
  if (!evalRunIds.length) return [];
28
29
  using results = new Results(resolve(directory, "runner.db"));
29
- const previous = results.benchmark!;
30
- if (previous.mergedInto)
30
+ if (results.benchmark!.mergedInto)
31
31
  throw new Error(
32
- `This run was merged into ${previous.mergedInto}; use the aggregate run`,
32
+ `This run was merged into ${results.benchmark!.mergedInto}; use the aggregate run`,
33
33
  );
34
- if (previous.state === "running")
35
- throw new Error("Wait for the active benchmark run");
34
+ if (results.benchmark!.state === "running") {
35
+ if (!runnerStopped(results.benchmark!))
36
+ throw new Error("Wait for the active benchmark run");
37
+ recoverStoppedRunner(results);
38
+ }
39
+ const previous = results.benchmark!;
36
40
  const candidates = [...new Set(evalRunIds)].map((evalRunId) => {
37
41
  const candidate = results.evalRun(evalRunId);
38
42
  if (!canJudgeEval(candidate))
@@ -6,6 +6,7 @@ import { candidateFingerprint } from "./plan-benchmark";
6
6
  import { runEvalPipeline } from "./run-eval-pipeline";
7
7
  import { ExecutionBudget } from "./execution-budget";
8
8
  import { finishBenchmark } from "./run-benchmark";
9
+ import { recoverStoppedRunner, runnerStopped } from "./recover-run";
9
10
 
10
11
  export async function retryEvalRun(
11
12
  directory: string,
@@ -13,13 +14,16 @@ export async function retryEvalRun(
13
14
  onEvent?: ExecutionObserver,
14
15
  ) {
15
16
  using results = new Results(resolve(directory, "runner.db"));
16
- const benchmark = results.benchmark!;
17
- if (benchmark.mergedInto)
17
+ if (results.benchmark!.mergedInto)
18
18
  throw new Error(
19
- `This run was merged into ${benchmark.mergedInto}; use the aggregate run`,
19
+ `This run was merged into ${results.benchmark!.mergedInto}; use the aggregate run`,
20
20
  );
21
- if (benchmark.state === "running")
22
- throw new Error("Wait for the active benchmark run");
21
+ if (results.benchmark!.state === "running") {
22
+ if (!runnerStopped(results.benchmark!))
23
+ throw new Error("Wait for the active benchmark run");
24
+ recoverStoppedRunner(results);
25
+ }
26
+ const benchmark = results.benchmark!;
23
27
  const previous = results.evalRun(evalRunId);
24
28
  if (!previous || previous.state === "completed")
25
29
  throw new Error(
@@ -23,6 +23,7 @@ import { canJudgeEval } from "./eval-state";
23
23
  import { isScored } from "../judgment";
24
24
  import { benchmarkScores } from "./scores";
25
25
  import { CostBudget, estimateWork } from "./cost-plan";
26
+ import { recoverStoppedRunner, runnerStopped } from "./recover-run";
26
27
  import {
27
28
  categoryKey,
28
29
  categoryList,
@@ -101,7 +102,8 @@ export function finishBenchmark(context: ExecutionContext) {
101
102
  (run) =>
102
103
  run.input.evalRunId === slot.evalRunId &&
103
104
  run.input.judgeHash === slot.judgeHash &&
104
- ["failed", "timed_out"].includes(run.state),
105
+ ["failed", "timed_out"].includes(run.state) &&
106
+ !run.interrupted,
105
107
  ) &&
106
108
  !slot.judgeRunId),
107
109
  );
@@ -183,6 +185,7 @@ export async function runBenchmark(
183
185
  (!options.fresh
184
186
  ? await currentBenchmarkRun(definition.directory)
185
187
  : undefined);
188
+ let stopped = false;
186
189
  if (directory) {
187
190
  using previous = new Results(resolve(directory, "runner.db"), true);
188
191
  const saved = previous.benchmark!;
@@ -190,8 +193,9 @@ export async function runBenchmark(
190
193
  throw new Error(
191
194
  `This run was merged into ${saved.mergedInto}; use the aggregate run`,
192
195
  );
193
- if (saved.state === "running")
196
+ if (saved.state === "running" && !runnerStopped(saved))
194
197
  throw new Error("This benchmark run is already running");
198
+ stopped = saved.state === "running";
195
199
  definition.models = [
196
200
  ...new Set([...saved.definition.models, ...definition.models]),
197
201
  ];
@@ -220,10 +224,16 @@ export async function runBenchmark(
220
224
  ? new Results(database, true)
221
225
  : new Results(":memory:")
222
226
  : new Results(database);
227
+ // A stopped runner leaves sessions open. A dry run plans them as interrupted work.
228
+ const recovered = stopped && !options.dryRun
229
+ ? recoverStoppedRunner(results)
230
+ : undefined;
223
231
  const previousSelections = new Map(
224
232
  results.slots().map((slot) => [slot.id, slot]),
225
233
  );
226
- let plan = planBenchmark(definition, runtime, results);
234
+ let plan = planBenchmark(definition, runtime, results, {
235
+ stoppedRunner: stopped,
236
+ });
227
237
  if (selected || onlyEvals || options.onlyRepetitions) {
228
238
  const retained = new Map(results.slots().map((slot) => [slot.id, slot]));
229
239
  plan = plan.map((item) =>
@@ -260,7 +270,7 @@ export async function runBenchmark(
260
270
  fingerprint(previous.runtime) === fingerprint(runtime) &&
261
271
  fingerprint(previous.modelNames ?? {}) === fingerprint(modelNames)
262
272
  )
263
- return { directory, plan, estimate, benchmark: previous };
273
+ return { directory, plan, estimate, benchmark: previous, recovered };
264
274
  const benchmark: BenchmarkRun = {
265
275
  id: previous?.id ?? `benchmark_${randomUUID()}`,
266
276
  name: definition.name,
@@ -397,7 +407,13 @@ export async function runBenchmark(
397
407
  deferred: plan.filter((item) => item.action === "deferred").length,
398
408
  },
399
409
  });
400
- return { directory, plan, estimate, benchmark: finishBenchmark(context) };
410
+ return {
411
+ directory,
412
+ plan,
413
+ estimate,
414
+ benchmark: finishBenchmark(context),
415
+ recovered,
416
+ };
401
417
  } catch (error) {
402
418
  results.saveBenchmark({
403
419
  ...results.benchmark!,
package/src/app/scores.ts CHANGED
@@ -14,6 +14,15 @@ export function benchmarkScores(
14
14
  const evals = definition.evals.filter(
15
15
  (item) => !evalId || item.id === evalId,
16
16
  );
17
+ // A code judgment counts only when it ran the eval's current judge.ts to completion.
18
+ const current = (item: (typeof evals)[number], slot: Slot) => {
19
+ const judge = slot.judgeRunId ? index.get(slot.judgeRunId) : undefined;
20
+ return !item.code ||
21
+ (judge?.code?.state === "completed" &&
22
+ judge.input.code?.hash === item.code.hash)
23
+ ? judge
24
+ : undefined;
25
+ };
17
26
  const keys = definition.categories?.map(categoryKey);
18
27
  const definitions = new Map(
19
28
  evals.map((item) => {
@@ -58,9 +67,7 @@ export function benchmarkScores(
58
67
  const values = selected
59
68
  .map(
60
69
  (slot) =>
61
- (slot.judgeRunId
62
- ? index.get(slot.judgeRunId)?.judgment?.scores
63
- : undefined)?.[criterion.id]?.value,
70
+ current(item, slot)?.judgment?.scores?.[criterion.id]?.value,
64
71
  )
65
72
  .filter(isScored);
66
73
  const scoredSum = values.reduce<number>(
@@ -86,26 +93,19 @@ export function benchmarkScores(
86
93
  });
87
94
  }),
88
95
  evals.map((item) => item.id),
96
+ // Without a declared `criteria` export, judge.ts reveals its criteria only by running.
89
97
  evals
90
98
  .filter(
91
99
  (item) =>
92
100
  item.code &&
93
- slots
94
- .filter(
95
- (slot) =>
96
- slot.active &&
97
- slot.evalId === item.id &&
98
- slot.model === model,
99
- )
100
- .some((slot) => {
101
- const judge = slot.judgeRunId
102
- ? index.get(slot.judgeRunId)
103
- : undefined;
104
- return (
105
- judge?.code?.state !== "completed" ||
106
- judge.input.code?.hash !== item.code!.hash
107
- );
108
- }),
101
+ !item.codeCriteria?.length &&
102
+ slots.some(
103
+ (slot) =>
104
+ slot.active &&
105
+ slot.evalId === item.id &&
106
+ slot.model === model &&
107
+ !current(item, slot),
108
+ ),
109
109
  )
110
110
  .map((item) => item.id),
111
111
  ),
@@ -236,11 +236,16 @@ export async function serveResults(options: {
236
236
  if (path !== assets && !path.startsWith(assets + sep))
237
237
  return new Response("Not found", { status: 404 });
238
238
  const file = Bun.file(path);
239
- return new Response(
240
- url.pathname !== "/" && (await file.exists())
241
- ? file
242
- : Bun.file(resolve(assets, "index.html")),
243
- );
239
+ const found = url.pathname !== "/" && (await file.exists());
240
+ return new Response(found ? file : Bun.file(resolve(assets, "index.html")), {
241
+ // Built assets have content-hashed names; the page itself must revalidate.
242
+ headers: {
243
+ "Cache-Control":
244
+ found && url.pathname.startsWith("/assets/")
245
+ ? "public, max-age=31536000, immutable"
246
+ : "no-cache",
247
+ },
248
+ });
244
249
  }
245
250
  return new Response("Not found", { status: 404 });
246
251
  } catch (error) {
package/src/cli.ts CHANGED
@@ -133,6 +133,9 @@ Requires Bun 1.4.2+, Docker, and an authenticated OpenCode installation.`);
133
133
  reason: item.reason,
134
134
  })),
135
135
  status: result.benchmark?.state,
136
+ ...("recovered" in result && result.recovered
137
+ ? { recovered: result.recovered }
138
+ : {}),
136
139
  },
137
140
  null,
138
141
  2,
package/src/types.ts CHANGED
@@ -36,6 +36,8 @@ export type Judge = {
36
36
  };
37
37
  export type Benchmark = {
38
38
  name?: string;
39
+ /** Display label for one benchmark.ts among several that share `name`, such as "Frontier". */
40
+ suite?: string;
39
41
  models: readonly ModelRef[];
40
42
  judge?: Judge;
41
43
  repetitions?: number;
@@ -101,6 +103,7 @@ export type EvalDefinition = {
101
103
  };
102
104
  export type BenchmarkDefinition = {
103
105
  name: string;
106
+ suite?: string;
104
107
  directory: string;
105
108
  models: ModelRef[];
106
109
  judge: { model?: ModelRef; timeoutMs: number; websearch: "exa" | false; verification?: VerificationRuntime | Required<VerificationEnvironment> };
@@ -220,6 +223,8 @@ export type EvalRun = {
220
223
  error?: string;
221
224
  replaces?: string;
222
225
  stop?: EvalStop;
226
+ /** The runner stopped before this session finished; the planner collects it again. */
227
+ interrupted?: boolean;
223
228
  };
224
229
  export type CriterionDefinition = { id: string; name: string };
225
230
  export type EvidenceCitation = (
@@ -282,6 +287,8 @@ export type JudgeRun = {
282
287
  monitorError?: string;
283
288
  monitorErrorAt?: string;
284
289
  monitorLimit?: string;
290
+ /** The runner stopped before judging finished; the planner judges the evidence again. */
291
+ interrupted?: boolean;
285
292
  };
286
293
  export type Slot = {
287
294
  id: string;