agent-hitch 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (562) hide show
  1. package/README.md +30 -3
  2. package/README.zh-CN.md +14 -2
  3. package/dist/bin/hitch.js +25 -3
  4. package/dist/bin/hitch.js.map +1 -1
  5. package/dist/scripts/canary-benchmark.js +54 -0
  6. package/dist/scripts/canary-benchmark.js.map +1 -0
  7. package/dist/scripts/canary-buildkit-secrets.js +166 -0
  8. package/dist/scripts/canary-buildkit-secrets.js.map +1 -0
  9. package/dist/scripts/canary-collision-domains.js +77 -0
  10. package/dist/scripts/canary-collision-domains.js.map +1 -0
  11. package/dist/scripts/canary-docker-image-observation.js +181 -0
  12. package/dist/scripts/canary-docker-image-observation.js.map +1 -0
  13. package/dist/scripts/canary-eval-scheduler-throughput.js +47 -0
  14. package/dist/scripts/canary-eval-scheduler-throughput.js.map +1 -0
  15. package/dist/scripts/canary-gpu-hardware.js +95 -0
  16. package/dist/scripts/canary-gpu-hardware.js.map +1 -0
  17. package/dist/scripts/canary-harbor-load.js +365 -0
  18. package/dist/scripts/canary-harbor-load.js.map +1 -0
  19. package/dist/scripts/canary-packaged-harness.js +75 -0
  20. package/dist/scripts/canary-packaged-harness.js.map +1 -0
  21. package/dist/scripts/canary-resource-load.js +188 -0
  22. package/dist/scripts/canary-resource-load.js.map +1 -0
  23. package/dist/scripts/check-architecture.js +10 -5
  24. package/dist/scripts/check-architecture.js.map +1 -1
  25. package/dist/scripts/docker-reaper-crash-child.js +21 -0
  26. package/dist/scripts/docker-reaper-crash-child.js.map +1 -0
  27. package/dist/src/adapters/catalog.js +3 -1
  28. package/dist/src/adapters/catalog.js.map +1 -1
  29. package/dist/src/adapters/discovery.js +1 -1
  30. package/dist/src/adapters/discovery.js.map +1 -1
  31. package/dist/src/adapters/providers/claude.js +6 -0
  32. package/dist/src/adapters/providers/claude.js.map +1 -1
  33. package/dist/src/adapters/providers/codex-auth.js +29 -0
  34. package/dist/src/adapters/providers/codex-auth.js.map +1 -0
  35. package/dist/src/adapters/providers/codex.js +29 -6
  36. package/dist/src/adapters/providers/codex.js.map +1 -1
  37. package/dist/src/adapters/providers/deepseek.js +6 -0
  38. package/dist/src/adapters/providers/deepseek.js.map +1 -1
  39. package/dist/src/adapters/providers/model-call.js +26 -0
  40. package/dist/src/adapters/providers/model-call.js.map +1 -0
  41. package/dist/src/adapters/providers/opencode.js +6 -0
  42. package/dist/src/adapters/providers/opencode.js.map +1 -1
  43. package/dist/src/adapters/providers/pi.js +6 -0
  44. package/dist/src/adapters/providers/pi.js.map +1 -1
  45. package/dist/src/artifacts/index.js +1 -0
  46. package/dist/src/artifacts/index.js.map +1 -1
  47. package/dist/src/artifacts/preparer.js +7 -2
  48. package/dist/src/artifacts/preparer.js.map +1 -1
  49. package/dist/src/backends/harbor/agent-budget.js +31 -0
  50. package/dist/src/backends/harbor/agent-budget.js.map +1 -0
  51. package/dist/src/backends/harbor/backend.js +59 -107
  52. package/dist/src/backends/harbor/backend.js.map +1 -1
  53. package/dist/src/backends/harbor/environment-config.js +92 -0
  54. package/dist/src/backends/harbor/environment-config.js.map +1 -0
  55. package/dist/src/backends/harbor/index.js +6 -1
  56. package/dist/src/backends/harbor/index.js.map +1 -1
  57. package/dist/src/backends/harbor/model-proxy-config.js +23 -0
  58. package/dist/src/backends/harbor/model-proxy-config.js.map +1 -0
  59. package/dist/src/backends/harbor/process.js +177 -0
  60. package/dist/src/backends/harbor/process.js.map +1 -0
  61. package/dist/src/backends/harbor/regrade.js +77 -0
  62. package/dist/src/backends/harbor/regrade.js.map +1 -0
  63. package/dist/src/backends/harbor/runtime-toolchain.js +30 -0
  64. package/dist/src/backends/harbor/runtime-toolchain.js.map +1 -0
  65. package/dist/src/backends/harbor/supervisor.js +74 -0
  66. package/dist/src/backends/harbor/supervisor.js.map +1 -0
  67. package/dist/src/backends/harbor/task-resources.js +206 -0
  68. package/dist/src/backends/harbor/task-resources.js.map +1 -0
  69. package/dist/src/backends/harbor/tools.js +6 -29
  70. package/dist/src/backends/harbor/tools.js.map +1 -1
  71. package/dist/src/backends/index.js +5 -1
  72. package/dist/src/backends/index.js.map +1 -1
  73. package/dist/src/benchmarks/index.js +3 -0
  74. package/dist/src/benchmarks/index.js.map +1 -0
  75. package/dist/src/benchmarks/loader.js +271 -0
  76. package/dist/src/benchmarks/loader.js.map +1 -0
  77. package/dist/src/benchmarks/metrics.js +16 -0
  78. package/dist/src/benchmarks/metrics.js.map +1 -0
  79. package/dist/src/benchmarks/toml.js +13 -0
  80. package/dist/src/benchmarks/toml.js.map +1 -0
  81. package/dist/src/benchmarks/validation.js +242 -0
  82. package/dist/src/benchmarks/validation.js.map +1 -0
  83. package/dist/src/cli/arguments.js +6 -4
  84. package/dist/src/cli/arguments.js.map +1 -1
  85. package/dist/src/cli/commands/benchmark.js +22 -0
  86. package/dist/src/cli/commands/benchmark.js.map +1 -0
  87. package/dist/src/cli/commands/capabilities.js +18 -0
  88. package/dist/src/cli/commands/capabilities.js.map +1 -0
  89. package/dist/src/cli/commands/daemon.js +135 -8
  90. package/dist/src/cli/commands/daemon.js.map +1 -1
  91. package/dist/src/cli/commands/eval.js +228 -6
  92. package/dist/src/cli/commands/eval.js.map +1 -1
  93. package/dist/src/cli/commands/images.js +40 -0
  94. package/dist/src/cli/commands/images.js.map +1 -0
  95. package/dist/src/cli/commands/run.js +42 -17
  96. package/dist/src/cli/commands/run.js.map +1 -1
  97. package/dist/src/cli/commands/runs.js +25 -3
  98. package/dist/src/cli/commands/runs.js.map +1 -1
  99. package/dist/src/cli/commands/trajectory.js +96 -5
  100. package/dist/src/cli/commands/trajectory.js.map +1 -1
  101. package/dist/src/cli/commands/verifier.js +36 -0
  102. package/dist/src/cli/commands/verifier.js.map +1 -0
  103. package/dist/src/cli/commands/worker.js +79 -0
  104. package/dist/src/cli/commands/worker.js.map +1 -0
  105. package/dist/src/cli/main.js +10 -0
  106. package/dist/src/cli/main.js.map +1 -1
  107. package/dist/src/cli/output.js +84 -4
  108. package/dist/src/cli/output.js.map +1 -1
  109. package/dist/src/control-plane/build-admission.js +69 -0
  110. package/dist/src/control-plane/build-admission.js.map +1 -0
  111. package/dist/src/control-plane/build-records.js +6 -0
  112. package/dist/src/control-plane/build-records.js.map +1 -0
  113. package/dist/src/control-plane/collisions.js +40 -0
  114. package/dist/src/control-plane/collisions.js.map +1 -0
  115. package/dist/src/control-plane/eval-control-state.js +22 -0
  116. package/dist/src/control-plane/eval-control-state.js.map +1 -0
  117. package/dist/src/control-plane/eval-control-work.js +44 -0
  118. package/dist/src/control-plane/eval-control-work.js.map +1 -0
  119. package/dist/src/control-plane/eval-image-resolution.js +52 -0
  120. package/dist/src/control-plane/eval-image-resolution.js.map +1 -0
  121. package/dist/src/control-plane/eval-image-services.js +30 -0
  122. package/dist/src/control-plane/eval-image-services.js.map +1 -0
  123. package/dist/src/control-plane/eval-records.js +252 -0
  124. package/dist/src/control-plane/eval-records.js.map +1 -0
  125. package/dist/src/control-plane/eval-recovery.js +181 -0
  126. package/dist/src/control-plane/eval-recovery.js.map +1 -0
  127. package/dist/src/control-plane/eval-scheduler.js +450 -0
  128. package/dist/src/control-plane/eval-scheduler.js.map +1 -0
  129. package/dist/src/control-plane/index.js +20 -0
  130. package/dist/src/control-plane/index.js.map +1 -0
  131. package/dist/src/control-plane/local-worker.js +49 -0
  132. package/dist/src/control-plane/local-worker.js.map +1 -0
  133. package/dist/src/control-plane/model-capture-planning.js +12 -0
  134. package/dist/src/control-plane/model-capture-planning.js.map +1 -0
  135. package/dist/src/control-plane/remote-result-transport.js +175 -0
  136. package/dist/src/control-plane/remote-result-transport.js.map +1 -0
  137. package/dist/src/control-plane/remote-work-coordinator.js +333 -0
  138. package/dist/src/control-plane/remote-work-coordinator.js.map +1 -0
  139. package/dist/src/control-plane/remote-work-inputs.js +266 -0
  140. package/dist/src/control-plane/remote-work-inputs.js.map +1 -0
  141. package/dist/src/control-plane/remote-work-item.js +62 -0
  142. package/dist/src/control-plane/remote-work-item.js.map +1 -0
  143. package/dist/src/control-plane/remote-work-recovery.js +220 -0
  144. package/dist/src/control-plane/remote-work-recovery.js.map +1 -0
  145. package/dist/src/control-plane/remote-worker-artifacts.js +135 -0
  146. package/dist/src/control-plane/remote-worker-artifacts.js.map +1 -0
  147. package/dist/src/control-plane/remote-worker-client.js +202 -0
  148. package/dist/src/control-plane/remote-worker-client.js.map +1 -0
  149. package/dist/src/control-plane/remote-worker-configuration.js +34 -0
  150. package/dist/src/control-plane/remote-worker-configuration.js.map +1 -0
  151. package/dist/src/control-plane/remote-worker-credentials.js +58 -0
  152. package/dist/src/control-plane/remote-worker-credentials.js.map +1 -0
  153. package/dist/src/control-plane/remote-worker-protocol.js +489 -0
  154. package/dist/src/control-plane/remote-worker-protocol.js.map +1 -0
  155. package/dist/src/control-plane/remote-worker-runner.js +214 -0
  156. package/dist/src/control-plane/remote-worker-runner.js.map +1 -0
  157. package/dist/src/control-plane/remote-workers.js +377 -0
  158. package/dist/src/control-plane/remote-workers.js.map +1 -0
  159. package/dist/src/control-plane/rerun-scheduler.js +448 -0
  160. package/dist/src/control-plane/rerun-scheduler.js.map +1 -0
  161. package/dist/src/control-plane/rerun-submission.js +62 -0
  162. package/dist/src/control-plane/rerun-submission.js.map +1 -0
  163. package/dist/src/control-plane/resources.js +182 -0
  164. package/dist/src/control-plane/resources.js.map +1 -0
  165. package/dist/src/control-plane/scheduler-eval-entry.js +32 -0
  166. package/dist/src/control-plane/scheduler-eval-entry.js.map +1 -0
  167. package/dist/src/control-plane/synthetic-result.js +21 -0
  168. package/dist/src/control-plane/synthetic-result.js.map +1 -0
  169. package/dist/src/control-plane/work-admission.js +23 -0
  170. package/dist/src/control-plane/work-admission.js.map +1 -0
  171. package/dist/src/control-plane/work-dispatcher.js +237 -0
  172. package/dist/src/control-plane/work-dispatcher.js.map +1 -0
  173. package/dist/src/controller-runtime/hash.js +19 -3
  174. package/dist/src/controller-runtime/hash.js.map +1 -1
  175. package/dist/src/controller-runtime/index.js +1 -1
  176. package/dist/src/controller-runtime/index.js.map +1 -1
  177. package/dist/src/controller-runtime/store.js +15 -6
  178. package/dist/src/controller-runtime/store.js.map +1 -1
  179. package/dist/src/daemon/auth.js +5 -0
  180. package/dist/src/daemon/auth.js.map +1 -1
  181. package/dist/src/daemon/client.js.map +1 -1
  182. package/dist/src/daemon/event-stream.js +46 -0
  183. package/dist/src/daemon/event-stream.js.map +1 -0
  184. package/dist/src/daemon/health.js +66 -0
  185. package/dist/src/daemon/health.js.map +1 -0
  186. package/dist/src/daemon/launcher.js +28 -1
  187. package/dist/src/daemon/launcher.js.map +1 -1
  188. package/dist/src/daemon/logging.js +23 -0
  189. package/dist/src/daemon/logging.js.map +1 -0
  190. package/dist/src/daemon/scheduler.js +26 -5
  191. package/dist/src/daemon/scheduler.js.map +1 -1
  192. package/dist/src/daemon/server.js +255 -98
  193. package/dist/src/daemon/server.js.map +1 -1
  194. package/dist/src/daemon/telemetry.js +163 -0
  195. package/dist/src/daemon/telemetry.js.map +1 -0
  196. package/dist/src/daemon/worker-routes.js +219 -0
  197. package/dist/src/daemon/worker-routes.js.map +1 -0
  198. package/dist/src/domain/benchmarks.js +2 -0
  199. package/dist/src/domain/benchmarks.js.map +1 -0
  200. package/dist/src/domain/bundles.js +2 -0
  201. package/dist/src/domain/bundles.js.map +1 -0
  202. package/dist/src/domain/control-plane.js +2 -0
  203. package/dist/src/domain/control-plane.js.map +1 -0
  204. package/dist/src/domain/execution-evidence.js +2 -0
  205. package/dist/src/domain/execution-evidence.js.map +1 -0
  206. package/dist/src/domain/execution-plan.js +2 -0
  207. package/dist/src/domain/execution-plan.js.map +1 -0
  208. package/dist/src/domain/images.js +2 -0
  209. package/dist/src/domain/images.js.map +1 -0
  210. package/dist/src/domain/index.js +13 -0
  211. package/dist/src/domain/index.js.map +1 -1
  212. package/dist/src/domain/interactions.js +2 -0
  213. package/dist/src/domain/interactions.js.map +1 -0
  214. package/dist/src/domain/providers.js +2 -0
  215. package/dist/src/domain/providers.js.map +1 -0
  216. package/dist/src/domain/resources.js +2 -0
  217. package/dist/src/domain/resources.js.map +1 -0
  218. package/dist/src/domain/runs.js.map +1 -1
  219. package/dist/src/domain/validation.js +11 -4
  220. package/dist/src/domain/validation.js.map +1 -1
  221. package/dist/src/domain/verifier-evidence-validation.js +214 -0
  222. package/dist/src/domain/verifier-evidence-validation.js.map +1 -0
  223. package/dist/src/domain/verifier-evidence.js +3 -0
  224. package/dist/src/domain/verifier-evidence.js.map +1 -0
  225. package/dist/src/domain/verifier-score-contract.js +232 -0
  226. package/dist/src/domain/verifier-score-contract.js.map +1 -0
  227. package/dist/src/domain/workers.js +2 -0
  228. package/dist/src/domain/workers.js.map +1 -0
  229. package/dist/src/evals/benchmark-adapter-manifest.js +219 -0
  230. package/dist/src/evals/benchmark-adapter-manifest.js.map +1 -0
  231. package/dist/src/evals/benchmark-candidate.js +36 -0
  232. package/dist/src/evals/benchmark-candidate.js.map +1 -0
  233. package/dist/src/evals/benchmark-run.js +166 -0
  234. package/dist/src/evals/benchmark-run.js.map +1 -0
  235. package/dist/src/evals/collect-only-rerun.js +183 -0
  236. package/dist/src/evals/collect-only-rerun.js.map +1 -0
  237. package/dist/src/evals/directory.js +30 -0
  238. package/dist/src/evals/directory.js.map +1 -0
  239. package/dist/src/evals/docker-engine-stats.js +89 -0
  240. package/dist/src/evals/docker-engine-stats.js.map +1 -0
  241. package/dist/src/evals/docker-ownership.js +51 -0
  242. package/dist/src/evals/docker-ownership.js.map +1 -0
  243. package/dist/src/evals/docker-reaper.js +144 -0
  244. package/dist/src/evals/docker-reaper.js.map +1 -0
  245. package/dist/src/evals/docker-resource-observer.js +207 -0
  246. package/dist/src/evals/docker-resource-observer.js.map +1 -0
  247. package/dist/src/evals/duration-estimator.js +126 -0
  248. package/dist/src/evals/duration-estimator.js.map +1 -0
  249. package/dist/src/evals/environment-image-planning.js +164 -0
  250. package/dist/src/evals/environment-image-planning.js.map +1 -0
  251. package/dist/src/evals/eval-artifact-planning.js +56 -0
  252. package/dist/src/evals/eval-artifact-planning.js.map +1 -0
  253. package/dist/src/evals/eval-finalization.js +10 -0
  254. package/dist/src/evals/eval-finalization.js.map +1 -0
  255. package/dist/src/evals/eval-lifecycle-events.js +29 -0
  256. package/dist/src/evals/eval-lifecycle-events.js.map +1 -0
  257. package/dist/src/evals/eval-logical-plan.js +51 -0
  258. package/dist/src/evals/eval-logical-plan.js.map +1 -0
  259. package/dist/src/evals/eval-result-builder.js +81 -0
  260. package/dist/src/evals/eval-result-builder.js.map +1 -0
  261. package/dist/src/evals/events.js +53 -5
  262. package/dist/src/evals/events.js.map +1 -1
  263. package/dist/src/evals/evolution-baseline.js +191 -0
  264. package/dist/src/evals/evolution-baseline.js.map +1 -0
  265. package/dist/src/evals/execution-evidence.js +116 -0
  266. package/dist/src/evals/execution-evidence.js.map +1 -0
  267. package/dist/src/evals/execution-leases.js +342 -0
  268. package/dist/src/evals/execution-leases.js.map +1 -0
  269. package/dist/src/evals/execution-plan-artifacts.js +72 -0
  270. package/dist/src/evals/execution-plan-artifacts.js.map +1 -0
  271. package/dist/src/evals/execution-plan-images.js +69 -0
  272. package/dist/src/evals/execution-plan-images.js.map +1 -0
  273. package/dist/src/evals/execution-plan-resources.js +174 -0
  274. package/dist/src/evals/execution-plan-resources.js.map +1 -0
  275. package/dist/src/evals/execution-plan.js +451 -0
  276. package/dist/src/evals/execution-plan.js.map +1 -0
  277. package/dist/src/evals/failure-classifier.js +61 -0
  278. package/dist/src/evals/failure-classifier.js.map +1 -0
  279. package/dist/src/evals/harbor-artifact-builder-image.js +99 -0
  280. package/dist/src/evals/harbor-artifact-builder-image.js.map +1 -0
  281. package/dist/src/evals/harbor-artifact-builder.js +358 -0
  282. package/dist/src/evals/harbor-artifact-builder.js.map +1 -0
  283. package/dist/src/evals/harbor-bridge-error.js +58 -3
  284. package/dist/src/evals/harbor-bridge-error.js.map +1 -1
  285. package/dist/src/evals/harbor-node-runtime.js +146 -0
  286. package/dist/src/evals/harbor-node-runtime.js.map +1 -0
  287. package/dist/src/evals/harbor-phase-timings.js +60 -0
  288. package/dist/src/evals/harbor-phase-timings.js.map +1 -0
  289. package/dist/src/evals/index.js +40 -2
  290. package/dist/src/evals/index.js.map +1 -1
  291. package/dist/src/evals/infrastructure-retry.js +208 -84
  292. package/dist/src/evals/infrastructure-retry.js.map +1 -1
  293. package/dist/src/evals/interaction-capture-import.js +49 -0
  294. package/dist/src/evals/interaction-capture-import.js.map +1 -0
  295. package/dist/src/evals/local-docker-provider.js +372 -0
  296. package/dist/src/evals/local-docker-provider.js.map +1 -0
  297. package/dist/src/evals/local-eval-planning.js +80 -0
  298. package/dist/src/evals/local-eval-planning.js.map +1 -0
  299. package/dist/src/evals/model-capture-plan.js +22 -0
  300. package/dist/src/evals/model-capture-plan.js.map +1 -0
  301. package/dist/src/evals/model-capture-runtime.js +114 -0
  302. package/dist/src/evals/model-capture-runtime.js.map +1 -0
  303. package/dist/src/evals/model-proxy-runtime-state.js +53 -0
  304. package/dist/src/evals/model-proxy-runtime-state.js.map +1 -0
  305. package/dist/src/evals/native-phase-evidence.js +440 -0
  306. package/dist/src/evals/native-phase-evidence.js.map +1 -0
  307. package/dist/src/evals/physical-retry-work.js +31 -0
  308. package/dist/src/evals/physical-retry-work.js.map +1 -0
  309. package/dist/src/evals/planned-execution-support.js +66 -0
  310. package/dist/src/evals/planned-execution-support.js.map +1 -0
  311. package/dist/src/evals/planned-execution.js +469 -0
  312. package/dist/src/evals/planned-execution.js.map +1 -0
  313. package/dist/src/evals/planned-progress-publisher.js +62 -0
  314. package/dist/src/evals/planned-progress-publisher.js.map +1 -0
  315. package/dist/src/evals/planned-remote-execution.js +40 -0
  316. package/dist/src/evals/planned-remote-execution.js.map +1 -0
  317. package/dist/src/evals/planned-retry-execution.js +174 -0
  318. package/dist/src/evals/planned-retry-execution.js.map +1 -0
  319. package/dist/src/evals/planned-retry-lifecycle.js +188 -0
  320. package/dist/src/evals/planned-retry-lifecycle.js.map +1 -0
  321. package/dist/src/evals/preparation-rerun.js +225 -0
  322. package/dist/src/evals/preparation-rerun.js.map +1 -0
  323. package/dist/src/evals/prepared-harness.js +76 -0
  324. package/dist/src/evals/prepared-harness.js.map +1 -0
  325. package/dist/src/evals/progress.js +52 -12
  326. package/dist/src/evals/progress.js.map +1 -1
  327. package/dist/src/evals/records.js +20 -3
  328. package/dist/src/evals/records.js.map +1 -1
  329. package/dist/src/evals/recovery.js +242 -0
  330. package/dist/src/evals/recovery.js.map +1 -0
  331. package/dist/src/evals/regrade-evidence.js +77 -0
  332. package/dist/src/evals/regrade-evidence.js.map +1 -0
  333. package/dist/src/evals/remote-infrastructure-retry.js +165 -0
  334. package/dist/src/evals/remote-infrastructure-retry.js.map +1 -0
  335. package/dist/src/evals/request.js +21 -3
  336. package/dist/src/evals/request.js.map +1 -1
  337. package/dist/src/evals/rerun-inputs.js +108 -0
  338. package/dist/src/evals/rerun-inputs.js.map +1 -0
  339. package/dist/src/evals/rerun-slots.js +4 -3
  340. package/dist/src/evals/rerun-slots.js.map +1 -1
  341. package/dist/src/evals/rerun-types.js +45 -0
  342. package/dist/src/evals/rerun-types.js.map +1 -0
  343. package/dist/src/evals/rerun.js +133 -88
  344. package/dist/src/evals/rerun.js.map +1 -1
  345. package/dist/src/evals/result-helpers.js +78 -0
  346. package/dist/src/evals/result-helpers.js.map +1 -0
  347. package/dist/src/evals/resume-state.js +36 -0
  348. package/dist/src/evals/resume-state.js.map +1 -0
  349. package/dist/src/evals/retry-backoff.js +20 -0
  350. package/dist/src/evals/retry-backoff.js.map +1 -0
  351. package/dist/src/evals/retry-state.js +195 -0
  352. package/dist/src/evals/retry-state.js.map +1 -0
  353. package/dist/src/evals/scheduler-metrics.js +95 -0
  354. package/dist/src/evals/scheduler-metrics.js.map +1 -0
  355. package/dist/src/evals/scheduler-trace-replay.js +107 -0
  356. package/dist/src/evals/scheduler-trace-replay.js.map +1 -0
  357. package/dist/src/evals/service-types.js +2 -0
  358. package/dist/src/evals/service-types.js.map +1 -0
  359. package/dist/src/evals/service.js +331 -347
  360. package/dist/src/evals/service.js.map +1 -1
  361. package/dist/src/evals/task-resources.js +209 -0
  362. package/dist/src/evals/task-resources.js.map +1 -0
  363. package/dist/src/evals/trial-environment-evidence.js +87 -0
  364. package/dist/src/evals/trial-environment-evidence.js.map +1 -0
  365. package/dist/src/evals/trial-execution-evidence.js +13 -0
  366. package/dist/src/evals/trial-execution-evidence.js.map +1 -0
  367. package/dist/src/evals/trial-import-identity.js +31 -0
  368. package/dist/src/evals/trial-import-identity.js.map +1 -0
  369. package/dist/src/evals/trial-import.js +183 -188
  370. package/dist/src/evals/trial-import.js.map +1 -1
  371. package/dist/src/evals/trial-publication-recovery.js +144 -0
  372. package/dist/src/evals/trial-publication-recovery.js.map +1 -0
  373. package/dist/src/evals/trial-publication.js +25 -0
  374. package/dist/src/evals/trial-publication.js.map +1 -0
  375. package/dist/src/evals/trial-reference-validation.js +49 -0
  376. package/dist/src/evals/trial-reference-validation.js.map +1 -0
  377. package/dist/src/evals/verifier-artifacts.js +317 -0
  378. package/dist/src/evals/verifier-artifacts.js.map +1 -0
  379. package/dist/src/evals/verifier-eligibility.js +25 -0
  380. package/dist/src/evals/verifier-eligibility.js.map +1 -0
  381. package/dist/src/evals/verifier-only-rerun.js +210 -0
  382. package/dist/src/evals/verifier-only-rerun.js.map +1 -0
  383. package/dist/src/evals/verifier-runtime.js +61 -0
  384. package/dist/src/evals/verifier-runtime.js.map +1 -0
  385. package/dist/src/evals/verifier-score-artifacts.js +119 -0
  386. package/dist/src/evals/verifier-score-artifacts.js.map +1 -0
  387. package/dist/src/evals/work-item-artifacts.js +23 -0
  388. package/dist/src/evals/work-item-artifacts.js.map +1 -0
  389. package/dist/src/foundation/config.js +17 -0
  390. package/dist/src/foundation/config.js.map +1 -1
  391. package/dist/src/foundation/contained-file.js +85 -0
  392. package/dist/src/foundation/contained-file.js.map +1 -0
  393. package/dist/src/foundation/credential-redaction.js +162 -0
  394. package/dist/src/foundation/credential-redaction.js.map +1 -0
  395. package/dist/src/foundation/environment-image-references.js +59 -0
  396. package/dist/src/foundation/environment-image-references.js.map +1 -0
  397. package/dist/src/foundation/executable.js +45 -5
  398. package/dist/src/foundation/executable.js.map +1 -1
  399. package/dist/src/foundation/fs.js +2 -1
  400. package/dist/src/foundation/fs.js.map +1 -1
  401. package/dist/src/foundation/index.js +6 -2
  402. package/dist/src/foundation/index.js.map +1 -1
  403. package/dist/src/foundation/process-identity.js +69 -0
  404. package/dist/src/foundation/process-identity.js.map +1 -0
  405. package/dist/src/images/context.js +47 -0
  406. package/dist/src/images/context.js.map +1 -0
  407. package/dist/src/images/docker-buildkit.js +137 -0
  408. package/dist/src/images/docker-buildkit.js.map +1 -0
  409. package/dist/src/images/dockerfile.js +67 -0
  410. package/dist/src/images/dockerfile.js.map +1 -0
  411. package/dist/src/images/gc.js +261 -0
  412. package/dist/src/images/gc.js.map +1 -0
  413. package/dist/src/images/index.js +10 -0
  414. package/dist/src/images/index.js.map +1 -0
  415. package/dist/src/images/manifest.js +109 -0
  416. package/dist/src/images/manifest.js.map +1 -0
  417. package/dist/src/images/ownership.js +5 -0
  418. package/dist/src/images/ownership.js.map +1 -0
  419. package/dist/src/images/records.js +32 -0
  420. package/dist/src/images/records.js.map +1 -0
  421. package/dist/src/images/registry.js +154 -0
  422. package/dist/src/images/registry.js.map +1 -0
  423. package/dist/src/images/service.js +224 -0
  424. package/dist/src/images/service.js.map +1 -0
  425. package/dist/src/model-access/capture.js +255 -0
  426. package/dist/src/model-access/capture.js.map +1 -0
  427. package/dist/src/model-access/index.js +5 -0
  428. package/dist/src/model-access/index.js.map +1 -0
  429. package/dist/src/model-access/policy.js +49 -0
  430. package/dist/src/model-access/policy.js.map +1 -0
  431. package/dist/src/model-access/proxy.js +336 -0
  432. package/dist/src/model-access/proxy.js.map +1 -0
  433. package/dist/src/model-access/records.js +167 -0
  434. package/dist/src/model-access/records.js.map +1 -0
  435. package/dist/src/runs/adapter-process.js +12 -0
  436. package/dist/src/runs/adapter-process.js.map +1 -0
  437. package/dist/src/runs/bundle.js +450 -0
  438. package/dist/src/runs/bundle.js.map +1 -0
  439. package/dist/src/runs/events.js +12 -1
  440. package/dist/src/runs/events.js.map +1 -1
  441. package/dist/src/runs/executor.js +46 -45
  442. package/dist/src/runs/executor.js.map +1 -1
  443. package/dist/src/runs/finalizer.js +17 -0
  444. package/dist/src/runs/finalizer.js.map +1 -1
  445. package/dist/src/runs/index.js +8 -1
  446. package/dist/src/runs/index.js.map +1 -1
  447. package/dist/src/runs/manifest.js +5 -5
  448. package/dist/src/runs/manifest.js.map +1 -1
  449. package/dist/src/runs/phase-bundle.js +86 -0
  450. package/dist/src/runs/phase-bundle.js.map +1 -0
  451. package/dist/src/runs/phase-cancellation.js +107 -0
  452. package/dist/src/runs/phase-cancellation.js.map +1 -0
  453. package/dist/src/runs/phase-group.js +113 -0
  454. package/dist/src/runs/phase-group.js.map +1 -0
  455. package/dist/src/runs/query.js +1 -1
  456. package/dist/src/runs/query.js.map +1 -1
  457. package/dist/src/runs/records.js +3 -0
  458. package/dist/src/runs/records.js.map +1 -1
  459. package/dist/src/runs/request.js +10 -3
  460. package/dist/src/runs/request.js.map +1 -1
  461. package/dist/src/runs/training-candidate.js +161 -0
  462. package/dist/src/runs/training-candidate.js.map +1 -0
  463. package/dist/src/runs/verifier-evidence-redaction.js +61 -0
  464. package/dist/src/runs/verifier-evidence-redaction.js.map +1 -0
  465. package/dist/src/runs/verifier-evidence.js +459 -0
  466. package/dist/src/runs/verifier-evidence.js.map +1 -0
  467. package/dist/src/runs/verifier-structured-evidence.js +83 -0
  468. package/dist/src/runs/verifier-structured-evidence.js.map +1 -0
  469. package/dist/src/trajectories/analysis.js +288 -0
  470. package/dist/src/trajectories/analysis.js.map +1 -0
  471. package/dist/src/trajectories/chunk-projection.js +148 -0
  472. package/dist/src/trajectories/chunk-projection.js.map +1 -0
  473. package/dist/src/trajectories/content-projection.js +389 -0
  474. package/dist/src/trajectories/content-projection.js.map +1 -0
  475. package/dist/src/trajectories/dsh-chunk-contract.js +176 -0
  476. package/dist/src/trajectories/dsh-chunk-contract.js.map +1 -0
  477. package/dist/src/trajectories/dsh-contract.js +284 -0
  478. package/dist/src/trajectories/dsh-contract.js.map +1 -0
  479. package/dist/src/trajectories/events-chunk-drill.js +95 -0
  480. package/dist/src/trajectories/events-chunk-drill.js.map +1 -0
  481. package/dist/src/trajectories/events-page.js +367 -0
  482. package/dist/src/trajectories/events-page.js.map +1 -0
  483. package/dist/src/trajectories/index.js +3 -0
  484. package/dist/src/trajectories/index.js.map +1 -1
  485. package/dist/src/trajectories/provider-capture.js +15 -24
  486. package/dist/src/trajectories/provider-capture.js.map +1 -1
  487. package/dist/src/trajectories/providers/deepseek.js +3 -3
  488. package/dist/src/trajectories/providers/deepseek.js.map +1 -1
  489. package/dist/src/trajectories/request-attempt.js +47 -0
  490. package/dist/src/trajectories/request-attempt.js.map +1 -0
  491. package/dist/src/trajectories/stream-reader.js +311 -0
  492. package/dist/src/trajectories/stream-reader.js.map +1 -0
  493. package/dist/src/trajectories/surface-fold.js +126 -0
  494. package/dist/src/trajectories/surface-fold.js.map +1 -0
  495. package/dist/src/workers/index.js +3 -0
  496. package/dist/src/workers/index.js.map +1 -0
  497. package/dist/src/workers/remote-harbor-work-spec.js +97 -0
  498. package/dist/src/workers/remote-harbor-work-spec.js.map +1 -0
  499. package/dist/src/workers/remote-harbor-worker.js +223 -0
  500. package/dist/src/workers/remote-harbor-worker.js.map +1 -0
  501. package/docs/schemas/benchmark-hook-request-v1.schema.json +65 -0
  502. package/docs/schemas/benchmark-hook-response-v1.schema.json +75 -0
  503. package/docs/schemas/benchmark-hook-v1.schema.json +33 -0
  504. package/docs/schemas/benchmark-lock-v1.schema.json +351 -0
  505. package/docs/schemas/benchmark-metric-v1.schema.json +37 -0
  506. package/docs/schemas/benchmark-package-v1.schema.json +167 -0
  507. package/docs/schemas/benchmark-phase-group.schema.json +41 -0
  508. package/docs/schemas/benchmark-profile-v1.schema.json +141 -0
  509. package/docs/schemas/benchmark-task-v1.schema.json +384 -0
  510. package/docs/schemas/benchmark-tool-result-v1.schema.json +30 -0
  511. package/docs/schemas/environment-build-index.schema.json +13 -0
  512. package/docs/schemas/environment-build-record.schema.json +25 -0
  513. package/docs/schemas/environment-image-manifest.schema.json +62 -0
  514. package/docs/schemas/error.schema.json +22 -0
  515. package/docs/schemas/eval-control.schema.json +36 -0
  516. package/docs/schemas/eval-execution-plan.schema.json +216 -0
  517. package/docs/schemas/eval-progress.schema.json +1 -15
  518. package/docs/schemas/eval-rerun-result.schema.json +58 -0
  519. package/docs/schemas/eval-rerun-state.schema.json +61 -0
  520. package/docs/schemas/eval-rerun-submission.schema.json +60 -0
  521. package/docs/schemas/eval-result.schema.json +32 -13
  522. package/docs/schemas/eval-submission-input.schema.json +14 -0
  523. package/docs/schemas/eval-submission.schema.json +68 -0
  524. package/docs/schemas/eval-trial-publication.schema.json +15 -0
  525. package/docs/schemas/eval-trial-reference.schema.json +129 -0
  526. package/docs/schemas/execution-evidence.schema.json +96 -0
  527. package/docs/schemas/execution-lease.schema.json +50 -0
  528. package/docs/schemas/execution-provider-status.schema.json +65 -0
  529. package/docs/schemas/execution-worker.schema.json +53 -0
  530. package/docs/schemas/harbor-process-exit.schema.json +14 -0
  531. package/docs/schemas/interaction-capture-ref.schema.json +45 -0
  532. package/docs/schemas/local-provider-execution.schema.json +33 -0
  533. package/docs/schemas/model-interaction.schema.json +41 -0
  534. package/docs/schemas/regrade-assessment-reference.schema.json +11 -0
  535. package/docs/schemas/remote-result-envelope.schema.json +32 -0
  536. package/docs/schemas/remote-tree-envelope.schema.json +25 -0
  537. package/docs/schemas/remote-work-offer.schema.json +58 -0
  538. package/docs/schemas/remote-work-receipt.schema.json +50 -0
  539. package/docs/schemas/remote-worker-artifact.schema.json +17 -0
  540. package/docs/schemas/remote-worker-event.schema.json +18 -0
  541. package/docs/schemas/remote-worker-heartbeat.schema.json +37 -0
  542. package/docs/schemas/remote-worker-registration.schema.json +52 -0
  543. package/docs/schemas/result-bundle-index.schema.json +133 -0
  544. package/docs/schemas/run-context.schema.json +15 -0
  545. package/docs/schemas/run-environment-images.schema.json +39 -0
  546. package/docs/schemas/run-manifest.schema.json +4 -0
  547. package/docs/schemas/run-request.schema.json +4 -0
  548. package/docs/schemas/training-data-candidate.schema.json +46 -0
  549. package/docs/schemas/trajectory-analysis.schema.json +258 -0
  550. package/docs/schemas/trajectory-events-page.schema.json +40 -0
  551. package/docs/schemas/verifier-assessment.schema.json +77 -0
  552. package/docs/schemas/verifier-evidence.schema.json +276 -0
  553. package/integrations/harbor/hitch_benchmark.py +252 -0
  554. package/integrations/harbor/hitch_candidate_recycle.py +262 -0
  555. package/integrations/harbor/hitch_harbor_agent.py +698 -568
  556. package/integrations/harbor/hitch_harbor_environment.py +469 -0
  557. package/integrations/harbor/hitch_harbor_task_resources.py +275 -0
  558. package/integrations/harbor/hitch_harbor_verifier.py +83 -2
  559. package/integrations/harbor/hitch_phase_supervisor.py +452 -0
  560. package/integrations/harbor/hitch_tool_client.mjs +91 -0
  561. package/integrations/model-call/cli.js +57 -0
  562. package/package.json +30 -6
@@ -0,0 +1,252 @@
1
+ """Generic benchmark package hooks, tool binding and metric normalization.
2
+
3
+ Runs in the trusted Harbor worker. Package business code is never imported here.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import hashlib
9
+ import json
10
+ import math
11
+ from pathlib import Path
12
+ import shlex
13
+ import tempfile
14
+ import uuid
15
+
16
+
17
+ def descriptor(environment_dir):
18
+ path = Path(environment_dir).parent / ".hitch-benchmark.json"
19
+ return json.loads(path.read_text()) if path.is_file() else None
20
+
21
+
22
+ class BenchmarkSession:
23
+ def __init__(self, env, config):
24
+ self.env, self.config = env, config
25
+ self.responses = {}
26
+ self.started = False
27
+ self.stopped = False
28
+ self.failure = None
29
+ self.journal = env.trial_paths.trial_dir / "benchmark-lifecycle.json"
30
+
31
+ async def phase(self, phase):
32
+ if phase in self.responses:
33
+ return self.responses[phase]
34
+ hook = self.config["task"]["lifecycle"][phase]
35
+ labels = self.env._hitch_ownership_labels
36
+ request = {
37
+ "schema_version": "1",
38
+ "request_id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.env.session_id + ":" + phase)),
39
+ "phase": phase, "task_id": self.config["task_id"],
40
+ "logical_trial_id": self.env.session_id, "execution_index": 0,
41
+ "lease_id": labels.get("io.hitch.lease-id", self.env.session_id),
42
+ "epoch": int(labels.get("io.hitch.lease-epoch", "1")),
43
+ "profile_digest": self.config["profile_digest"], "input_refs": [],
44
+ }
45
+ # Harbor's exec API accepts shell text. Quote each argv and stdin byte;
46
+ # no task text is interpolated as shell syntax.
47
+ command = "printf %s " + shlex.quote(json.dumps(request)) + " | " + shlex.join(hook["argv"])
48
+ try:
49
+ result = await self.env.service_exec(command, service=hook["target"].split(":", 1)[1], timeout_sec=math.ceil(hook["timeout_ms"] / 1000))
50
+ if result.return_code != 0:
51
+ raise RuntimeError(f"hook {phase} exited {result.return_code}: {result.stderr}")
52
+ if len(result.stdout or "") > 1024 * 1024:
53
+ raise RuntimeError("oversized hook response")
54
+ response = json.loads(result.stdout)
55
+ if set(response) != {"schema_version", "request_id", "status", "output"}:
56
+ raise RuntimeError(f"invalid hook response fields: {phase}")
57
+ if response.get("schema_version") != "1" or response.get("request_id") != request["request_id"] or response.get("status") != "ok":
58
+ raise RuntimeError(f"invalid/failed hook response: {phase}")
59
+ output = response["output"]
60
+ required = {"prepare": "ready", "quiesce": "quiesced", "cleanup": "cleaned"}.get(phase)
61
+ if required and output.get(required) is not True:
62
+ raise RuntimeError(f"hook {phase} did not confirm {required}")
63
+ if phase == "snapshot":
64
+ paths = self.config["task"]["submission"]["paths"]
65
+ artifacts = output.get("artifacts", [])
66
+ if not isinstance(artifacts, list) or any(type(a.get("bytes")) is not int or a["bytes"] < 0 for a in artifacts):
67
+ raise RuntimeError("invalid snapshot size metadata")
68
+ if sorted(a.get("path", "") for a in artifacts) != sorted(paths):
69
+ raise RuntimeError("snapshot artifact membership mismatch")
70
+ size = sum(a["bytes"] for a in artifacts)
71
+ if size > self.config["task"]["submission"]["max_bytes"] or size < 1:
72
+ raise RuntimeError("snapshot size outside package limit")
73
+ self.responses[phase] = response
74
+ self.write_journal()
75
+ return response
76
+ except BaseException as error:
77
+ self.failure = {"phase": phase, "error": type(error).__name__}
78
+ self.write_journal()
79
+ raise
80
+
81
+ def write_journal(self):
82
+ # Never persist tool tokens or opaque management handles.
83
+ phases = {p: {"request_id": r["request_id"], "status": r["status"]} for p, r in self.responses.items()}
84
+ self.journal.parent.mkdir(parents=True, exist_ok=True)
85
+ self.journal.write_text(json.dumps({"schema_version": "1", "phases": phases, "failure": self.failure}, indent=2))
86
+
87
+ async def prepare(self):
88
+ self.started = True
89
+ if self.config["task"]["driver"]["kind"] != "tool-server":
90
+ self.write_journal()
91
+ return
92
+ response = await self.phase("prepare")
93
+ if self.config["task"]["driver"]["config"].get("native_phases"):
94
+ # There is no candidate identity yet. Only the phase supervisor may
95
+ # bind and upload a phase token after prepare/setup have completed.
96
+ phases = self.config["task"]["driver"]["config"]["native_phases"]
97
+ if (response["output"].get("native_phases_ready") is not True or response["output"].get("tool_bindings", []) != []
98
+ or phases["protocol"] == "hitch-native-phase-control@2" and response["output"].get("native_deadline_ready") is not True):
99
+ self.failure = {"phase": "prepare", "error": "NativePhasePrepareInvalid"}
100
+ self.write_journal()
101
+ raise RuntimeError("native phase prepare must confirm readiness without a static binding")
102
+ return
103
+ bindings = response["output"]["tool_bindings"]
104
+ expected = self.config["task"]["driver"]["config"]
105
+ if len(bindings) != 1 or bindings[0]["endpoint"] != expected["endpoint"] or bindings[0]["tools"] != self.config["tools"]:
106
+ raise RuntimeError("prepared tool binding differs from locked definition")
107
+ binding = bindings[0]
108
+ if not isinstance(binding.get("token"), str) or len(binding["token"]) < 32:
109
+ raise RuntimeError("missing per-trial tool authorization")
110
+ with tempfile.TemporaryDirectory(prefix="hitch-binding-") as temp:
111
+ file = Path(temp) / "binding.json"
112
+ file.write_text(json.dumps(binding)); file.chmod(0o600)
113
+ await self.env.upload_file(file, "/tmp/hitch-tool-binding.json")
114
+ await self.env.upload_file(Path(__file__).with_name("hitch_tool_client.mjs"), "/tmp/hitch-tools.mjs")
115
+
116
+ async def snapshot(self):
117
+ if self.stopped:
118
+ return
119
+ if self.config["task"]["driver"]["kind"] != "tool-server":
120
+ # Harbor collects the native task's declared artifacts. Shared
121
+ # verifiers retain upstream live-workspace semantics.
122
+ self.stopped = True
123
+ return
124
+ async def collect():
125
+ await self.phase("quiesce")
126
+ await self.phase("snapshot")
127
+ budget = self.config["profile"]["budget"]["collection_timeout_ms"]
128
+ await asyncio.wait_for(collect(), budget / 1000)
129
+ self.stopped = True
130
+
131
+ async def cleanup(self):
132
+ if self.started and self.config["task"]["driver"]["kind"] == "tool-server":
133
+ await self.phase("cleanup")
134
+
135
+
136
+ def candidate_instruction(instruction, environment):
137
+ config = descriptor(environment.environment_dir)
138
+ if config is None:
139
+ return instruction, None
140
+ if config["task"]["driver"]["kind"] != "tool-server":
141
+ return (json.dumps(config["candidate_input"]) if "candidate_input" in config else instruction), int(config["agent_timeout_sec"] * 1000)
142
+ instruction += "\n\nTools are available through the locked tool-server bridge. Run `node /tmp/hitch-tools.mjs list` to read tool descriptions and JSON schemas. Invoke a tool using `node /tmp/hitch-tools.mjs TOOL_NAME 'JSON_ARGUMENTS'` (or pass - and JSON via stdin). Complete the requested workflow with these simulated service tools.\n"
143
+ if "tool-result-images@1" in config["task"]["requirements"]:
144
+ instruction += "Image tool results contain absolute local paths. Open each image with your native image viewing tool to inspect the observation. The files contain the original image bytes; the JSON metadata is not a visual observation.\n"
145
+ return instruction, int(config["agent_timeout_sec"] * 1000)
146
+
147
+
148
+ async def export_final_response(environment, result):
149
+ config = descriptor(environment.environment_dir)
150
+ target = config["task"]["submission"].get("final_response") if config else None
151
+ if not target:
152
+ return
153
+ if not isinstance(result.get("output"), str):
154
+ raise RuntimeError("canonical candidate final response is unavailable")
155
+ with tempfile.TemporaryDirectory(prefix="hitch-response-") as temp:
156
+ source = Path(temp) / "response.json"
157
+ encoded = json.dumps({"schema_version": "1", "run_id": result["run_id"],
158
+ "response": result["output"], "termination": result["status"], "source": "hitch-run-result"})
159
+ if len(encoded.encode()) > config["task"]["submission"]["max_bytes"]:
160
+ raise RuntimeError("canonical response exceeds submission limit")
161
+ source.write_text(encoded)
162
+ # Keep an authoritative copy outside the candidate environment. A
163
+ # background process cannot replace the response used for grading.
164
+ (environment.trial_paths.trial_dir / "hitch-final-response.json").write_text(encoded)
165
+ prepared = await environment.exec("mkdir -p /hitch-evidence")
166
+ if prepared.return_code != 0:
167
+ raise RuntimeError("cannot prepare candidate response export")
168
+ await environment.upload_file(source, target)
169
+
170
+
171
+ async def restore_final_response(verifier):
172
+ config = descriptor(verifier.task.paths.environment_dir)
173
+ target = config["task"]["submission"].get("final_response") if config else None
174
+ if not target:
175
+ return
176
+ source = verifier.trial_paths.trial_dir / "hitch-final-response.json"
177
+ if not source.is_file():
178
+ raise RuntimeError("trusted candidate response evidence is missing")
179
+ # Called after Harbor uploads the frozen artifacts, in the separate grader.
180
+ prepared = await verifier.environment.exec("rm -rf -- /hitch-evidence && mkdir -p /hitch-evidence")
181
+ if prepared.return_code != 0:
182
+ raise RuntimeError("cannot restore trusted candidate response")
183
+ await verifier.environment.upload_file(source, target)
184
+
185
+
186
+ def validate_collected_submission(verifier):
187
+ config = descriptor(verifier.task.paths.environment_dir)
188
+ if not config or config["task"]["driver"]["kind"] == "tool-server":
189
+ return
190
+ task = config["task"]
191
+ if "separate-verifier" not in task["requirements"]:
192
+ return
193
+ # Harbor 0.21 mirrors absolute sources directly under artifacts/.
194
+ root = verifier.trial_paths.trial_dir / "artifacts"
195
+ seen, size = set(), 0
196
+ for source in task["submission"]["paths"]:
197
+ file = root / source.lstrip("/")
198
+ for ancestor in [file, *file.parents]:
199
+ if ancestor == root.parent:
200
+ break
201
+ if ancestor.is_symlink():
202
+ raise RuntimeError("collected submission contains a symlink")
203
+ if not file.exists():
204
+ raise RuntimeError(f"submission_missing: {source}")
205
+ for entry in [file, *(file.rglob("*") if file.is_dir() else [])]:
206
+ if entry.is_symlink():
207
+ raise RuntimeError("collected submission contains a symlink")
208
+ if entry.is_file() and entry not in seen:
209
+ seen.add(entry)
210
+ size += entry.stat().st_size
211
+ if size > task["submission"]["max_bytes"]:
212
+ raise RuntimeError("collected submission exceeds package limit")
213
+
214
+
215
+ def normalize_rewards(verifier, result):
216
+ config = descriptor(verifier.task.paths.environment_dir)
217
+ if config is None:
218
+ return result
219
+ journal = json.loads((verifier.trial_paths.trial_dir / "benchmark-lifecycle.json").read_text())
220
+ native = config["task"]["driver"]["kind"] != "tool-server"
221
+ if journal["failure"] or (not native and not {"prepare", "quiesce", "snapshot"} <= journal["phases"].keys()):
222
+ raise RuntimeError("benchmark lifecycle did not produce a valid snapshot")
223
+ directory = verifier.trial_paths.verifier_dir
224
+ upstream = config["task"]["grading"]["kind"] == "harbor"
225
+ if (directory / "reward.txt").exists() and not upstream:
226
+ raise RuntimeError("standard packages require only reward.json")
227
+ # Read the original JSON before Harbor/Pydantic numeric coercion: bools and
228
+ # numeric strings must not become valid binary scores via the result model.
229
+ if (directory / "reward.json").is_file():
230
+ raw = json.loads((directory / "reward.json").read_text())
231
+ elif upstream:
232
+ raw = {"reward": float((directory / "reward.txt").read_text().strip())}
233
+ else:
234
+ raise RuntimeError("metric_missing: reward.json")
235
+ if not isinstance(raw, dict):
236
+ raise RuntimeError("invalid grader metric object")
237
+ mapped = {}
238
+ for name, metric in config["metrics"].items():
239
+ field = config["task"]["grading"]["metric_map"][name]
240
+ if field not in raw:
241
+ raise RuntimeError(f"metric_missing: {field}")
242
+ value = raw[field]
243
+ if type(value) not in (float, int) or not math.isfinite(value) or not metric["range"][0] <= value <= metric["range"][1] or (metric["type"] == "binary" and value not in (0, 1)):
244
+ raise RuntimeError(f"metric_invalid: {field}")
245
+ mapped[name] = value
246
+ (directory / "benchmark-rewards.json").write_text(json.dumps({"raw": raw, "metrics": mapped, "primary_metric": config["primary_metric"], "source_task_id": config["task"]["source_task_id"], "task_digest": config["task_digest"]}, indent=2))
247
+ contract = config.get("score_contract", {"total_score": config["primary_metric"]})
248
+ if not isinstance(contract, dict) or set(contract) != {"total_score"} or contract["total_score"] not in mapped:
249
+ raise RuntimeError("invalid standardized score contract")
250
+ total = mapped[contract["total_score"]]
251
+ auxiliary = {name: value for name, value in raw.items() if name not in {"reward", "total_score", "process_score"}}
252
+ return result.model_copy(update={"rewards": {**auxiliary, "reward": total, "total_score": total}})
@@ -0,0 +1,262 @@
1
+ """Retire a candidate container without resetting the benchmark's sidecars.
2
+
3
+ Trusted host API only. The caller must revoke the old tool binding and finish
4
+ exporting the old run before calling this API, then reinstall the harness and
5
+ bind the next run before starting a model. Receipts prove environment changes,
6
+ not model conversation freshness, phase completion, or benchmark correctness.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import re
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+
20
+ class CandidateRecycleError(RuntimeError):
21
+ pass
22
+
23
+
24
+ def _overlaps(left: Path, right: Path) -> bool:
25
+ return left == right or left in right.parents or right in left.parents
26
+
27
+
28
+ def _directory(path: Path) -> Path:
29
+ absolute = path.absolute()
30
+ if absolute.resolve(strict=True) != absolute or not absolute.is_dir():
31
+ raise CandidateRecycleError("Candidate phase paths must be real directories without symlinks")
32
+ return absolute
33
+
34
+
35
+ def _write_json(path: Path, value: Any) -> None:
36
+ temporary = path.with_suffix(".pending")
37
+ with temporary.open("x", encoding="utf-8") as stream:
38
+ os.chmod(temporary, 0o600)
39
+ json.dump(value, stream, sort_keys=True, indent=2, allow_nan=False)
40
+ stream.write("\n")
41
+ stream.flush()
42
+ os.fsync(stream.fileno())
43
+ temporary.replace(path)
44
+
45
+
46
+ async def _docker(arguments: list[str]) -> str:
47
+ process = await asyncio.create_subprocess_exec(
48
+ "docker", *arguments, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
49
+ )
50
+ try:
51
+ stdout, _ = await asyncio.wait_for(process.communicate(), timeout=60)
52
+ except BaseException:
53
+ if process.returncode is None:
54
+ process.kill()
55
+ await process.wait()
56
+ raise
57
+ if process.returncode:
58
+ # Docker errors can contain environment values. Never journal them.
59
+ raise CandidateRecycleError("Candidate Docker inspection failed")
60
+ return stdout.decode("utf-8")
61
+
62
+
63
+ _INSPECT = ('{"id":{{json .Id}},"image":{{json .Image}},'
64
+ '"state":{{json .State.Status}},"started_at":{{json .State.StartedAt}},'
65
+ '"labels":{{json .Config.Labels}},"mounts":{{json .Mounts}},'
66
+ '"host":{{json .HostConfig}},"config":{{json .Config}}}')
67
+
68
+
69
+ def _configuration_digest(container: dict[str, Any]) -> str:
70
+ # Compare configuration without persisting potentially secret environment
71
+ # values. Compose regenerates its own config-hash on the image-pin overlay.
72
+ config = dict(container["config"])
73
+ config.pop("Image", None)
74
+ config.pop("Hostname", None)
75
+ environment = config.get("Env") or []
76
+ # Docker/Compose may reorder map-derived env and bind lists. Unique env
77
+ # keys have no ordering semantics; duplicate keys must retain their order.
78
+ if len({entry.split("=", 1)[0] for entry in environment}) == len(environment):
79
+ config["Env"] = sorted(environment)
80
+ config["Labels"] = {key: value for key, value in (config.get("Labels") or {}).items()
81
+ if not key.startswith("com.docker.compose.") and key != "io.hitch.candidate-phase"}
82
+ host = dict(container["host"])
83
+ # These two are generated per container by Docker, not requested resources.
84
+ host.pop("ContainerIDFile", None)
85
+ if host.get("Binds"):
86
+ host["Binds"] = sorted(host["Binds"])
87
+ digest = hashlib.sha256(json.dumps([config, host], sort_keys=True).encode()).hexdigest()
88
+ return f"sha256:{digest}"
89
+
90
+
91
+ class CandidateRecycler:
92
+ def __init__(self, environment: Any):
93
+ self.environment = environment
94
+ self.lock = asyncio.Lock()
95
+
96
+ async def _compose(self, arguments: list[str]) -> str:
97
+ result = await self.environment._run_docker_compose_command(
98
+ arguments, check=False, timeout_sec=60
99
+ )
100
+ if result.return_code:
101
+ raise CandidateRecycleError("Candidate Compose operation failed; trial cleanup required")
102
+ return result.stdout or ""
103
+
104
+ async def _containers(self) -> dict[str, dict[str, Any]]:
105
+ ids = (await self._compose(["ps", "--all", "--quiet"])).split()
106
+ if not ids or len(ids) > 64 or any(not re.fullmatch(r"[0-9a-f]{64}", item) for item in ids):
107
+ raise CandidateRecycleError("Candidate project container inventory is invalid")
108
+ documents = await _docker(["inspect", "--format", _INSPECT, *ids])
109
+ containers: dict[str, dict[str, Any]] = {}
110
+ ownership = self.environment._hitch_ownership_labels
111
+ if not ownership:
112
+ raise CandidateRecycleError("Candidate recycling requires Hitch lease ownership")
113
+ projects = set()
114
+ for line in documents.splitlines():
115
+ item = json.loads(line)
116
+ labels = item["labels"] or {}
117
+ if any(labels.get(key) != value for key, value in ownership.items()):
118
+ raise CandidateRecycleError("Candidate project lease ownership changed")
119
+ service = labels.get("com.docker.compose.service")
120
+ projects.add(labels.get("com.docker.compose.project"))
121
+ if not service or service in containers or labels.get("com.docker.compose.oneoff", "False").lower() != "false":
122
+ raise CandidateRecycleError("Candidate recycling requires one container per service")
123
+ containers[service] = item
124
+ if len(containers) != len(ids) or "main" not in containers or len(projects) != 1 or None in projects:
125
+ raise CandidateRecycleError("Candidate project inventory is incomplete")
126
+ return containers
127
+
128
+ @staticmethod
129
+ def _sidecars(containers: dict[str, dict[str, Any]]) -> dict[str, Any]:
130
+ result = {}
131
+ for service, item in containers.items():
132
+ if service != "main":
133
+ if item["state"] != "running":
134
+ raise CandidateRecycleError("A benchmark sidecar is not running")
135
+ result[service] = {"id": item["id"], "image": item["image"], "started_at": item["started_at"]}
136
+ return result
137
+
138
+ def _mounts(self, containers: dict[str, dict[str, Any]], archive: Path) -> dict[str, Path]:
139
+ paths = self.environment.trial_paths
140
+ allowed = {"/logs/agent": paths.agent_dir, "/logs/verifier": paths.verifier_dir,
141
+ "/logs/artifacts": paths.artifacts_dir}
142
+ main = containers["main"]
143
+ host = main["host"]
144
+ if (host.get("Privileged") or host.get("VolumesFrom") or host.get("Devices") or host.get("CapAdd")
145
+ or host.get("PidMode") or host.get("IpcMode") in ("host",) or str(host.get("IpcMode", "")).startswith("container:")
146
+ or host.get("NetworkMode") == "host"):
147
+ raise CandidateRecycleError("Candidate container isolation configuration is unsupported")
148
+ writable = {}
149
+ all_sources = []
150
+ for mount in main["mounts"]:
151
+ if mount["Type"] == "tmpfs":
152
+ continue # Recreated with the container.
153
+ if mount["Type"] != "bind":
154
+ raise CandidateRecycleError("Persistent candidate volumes are unsupported for phase recycling")
155
+ source = Path(mount["Source"]).absolute()
156
+ if source.resolve(strict=True) != source:
157
+ raise CandidateRecycleError("Candidate mount source contains a symlink")
158
+ if not source.is_file() and not source.is_dir():
159
+ raise CandidateRecycleError("Special-file candidate mounts are unsupported")
160
+ if _overlaps(source, archive):
161
+ raise CandidateRecycleError("Candidate can access the phase archive")
162
+ all_sources.append(source)
163
+ if mount["RW"]:
164
+ target = mount["Destination"]
165
+ if target not in allowed or source != Path(allowed[target]).absolute():
166
+ raise CandidateRecycleError("Only Harbor trial log directories may be writable candidate mounts")
167
+ writable[target] = _directory(source)
168
+ if ("/logs/agent" not in writable or len(set(writable.values())) != len(writable)
169
+ or len({source.name for source in writable.values()}) != len(writable)):
170
+ raise CandidateRecycleError("Candidate phase log mounts are missing or aliased")
171
+ for source in writable.values():
172
+ if sum(_overlaps(source, other) for other in all_sources) != 1:
173
+ raise CandidateRecycleError("Candidate mount paths overlap")
174
+ for service, item in containers.items():
175
+ if service != "main" and any(mount["Type"] == "bind" and _overlaps(source, Path(mount["Source"])) for mount in item["mounts"]):
176
+ raise CandidateRecycleError("Candidate logs are shared with a persistent service")
177
+ return writable
178
+
179
+ async def recycle(self, phase_index: int) -> dict[str, Any]:
180
+ if type(phase_index) is not int or not 1 <= phase_index <= 9999:
181
+ raise CandidateRecycleError("Candidate phase index must be an integer from 1 to 9999")
182
+ async with self.lock:
183
+ if getattr(self.environment, "_is_windows_container", False):
184
+ raise CandidateRecycleError("Candidate recycling supports Linux containers only")
185
+ trial = _directory(self.environment.trial_paths.trial_dir)
186
+ root = trial / "hitch-candidate-phases"
187
+ before = await self._containers()
188
+ mounts = self._mounts(before, root)
189
+ sidecars = self._sidecars(before)
190
+ phase = root / f"phase-{phase_index:04d}"
191
+ receipt_path = phase / "receipt.json"
192
+ root.mkdir(mode=0o700, exist_ok=True)
193
+ _directory(root)
194
+ if root.stat().st_mode & 0o077:
195
+ raise CandidateRecycleError("Candidate phase archive must be private to the host controller")
196
+ if receipt_path.exists():
197
+ _directory(phase)
198
+ if receipt_path.is_symlink():
199
+ raise CandidateRecycleError("Candidate phase receipt is a symlink")
200
+ receipt = json.loads(receipt_path.read_text())
201
+ if (receipt.get("status") != "completed" or receipt.get("new_container_id") != before["main"]["id"]
202
+ or receipt.get("sidecars") != sidecars or receipt.get("ownership") != self.environment._hitch_ownership_labels):
203
+ raise CandidateRecycleError("Candidate recycle receipt is stale or incomplete; cleanup required")
204
+ return receipt
205
+ if phase.exists():
206
+ raise CandidateRecycleError("Candidate phase archive already exists; cleanup required")
207
+ existing = sorted(item.name for item in root.iterdir())
208
+ if existing != [f"phase-{index:04d}" for index in range(1, phase_index)]:
209
+ raise CandidateRecycleError("Candidate phases must be recycled consecutively")
210
+ if phase_index > 1:
211
+ previous = json.loads((root / f"phase-{phase_index - 1:04d}" / "receipt.json").read_text())
212
+ if (previous.get("status") != "completed" or previous.get("new_container_id") != before["main"]["id"]
213
+ or previous.get("sidecars") != sidecars or previous.get("image") != before["main"]["image"]):
214
+ raise CandidateRecycleError("Previous candidate replacement is not complete")
215
+ main = before["main"]
216
+ if not re.fullmatch(r"sha256:[0-9a-f]{64}", main["image"]):
217
+ raise CandidateRecycleError("Candidate image identity is invalid")
218
+ phase.mkdir(mode=0o700)
219
+ receipt = {"schema_version": "hitch-candidate-recycle@1", "scope": "environment-only",
220
+ "phase_index": phase_index, "status": "prepared", "old_container_id": main["id"],
221
+ "image": main["image"], "configuration_digest": _configuration_digest(main),
222
+ "ownership": dict(self.environment._hitch_ownership_labels), "sidecars": sidecars,
223
+ "archives": {target: f"phase-{phase_index:04d}/{source.name}" for target, source in mounts.items()}}
224
+ _write_json(receipt_path, receipt)
225
+ try:
226
+ # Deliberately bypass stop_service(): that hook snapshots the
227
+ # entire benchmark and would terminate the native phase loop.
228
+ await self._compose(["rm", "--stop", "--force", "--volumes", "main"])
229
+ remaining = set((await _docker(["ps", "--all", "--no-trunc", "--quiet"])).split())
230
+ if main["id"] in remaining:
231
+ raise CandidateRecycleError("Previous candidate container still exists")
232
+ receipt["status"] = "retired"
233
+ _write_json(receipt_path, receipt)
234
+ for source in mounts.values():
235
+ _directory(source)
236
+ mode = source.stat().st_mode & 0o777
237
+ source.rename(phase / source.name)
238
+ source.mkdir(mode=mode)
239
+ source.chmod(mode)
240
+ receipt["status"] = "archived"
241
+ _write_json(receipt_path, receipt)
242
+ overlay = phase / "replacement.json"
243
+ _write_json(overlay, {"services": {"main": {"image": main["image"], "labels": {"io.hitch.candidate-phase": str(phase_index + 1)}}}})
244
+ self.environment._hitch_phase_compose_path = overlay
245
+ await self._compose(["up", "--detach", "--wait", "--wait-timeout", "45", "--no-deps", "--no-build", "--pull", "never", "main"])
246
+ after = await self._containers()
247
+ next_main = after["main"]
248
+ checks = {"new-container": next_main["id"] != main["id"], "image": next_main["image"] == main["image"],
249
+ "running": next_main["state"] == "running", "configuration": _configuration_digest(next_main) == receipt["configuration_digest"],
250
+ "sidecars": self._sidecars(after) == sidecars, "mounts": self._mounts(after, root) == mounts}
251
+ failed_checks = [name for name, passed in checks.items() if not passed]
252
+ if failed_checks:
253
+ raise CandidateRecycleError("Replacement changed the benchmark environment contract: " + ", ".join(failed_checks))
254
+ await self.environment.ensure_dirs(list(mounts))
255
+ await self.environment._upload_environment_dir_after_start()
256
+ receipt.update(status="completed", new_container_id=next_main["id"])
257
+ _write_json(receipt_path, receipt)
258
+ return receipt
259
+ except BaseException as error:
260
+ receipt.update(status="failed", failure_type=type(error).__name__)
261
+ _write_json(receipt_path, receipt)
262
+ raise