agent-hitch 0.2.7 → 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 (287) hide show
  1. package/README.md +16 -2
  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-eval-scheduler-throughput.js +47 -0
  8. package/dist/scripts/canary-eval-scheduler-throughput.js.map +1 -0
  9. package/dist/scripts/check-architecture.js +3 -2
  10. package/dist/scripts/check-architecture.js.map +1 -1
  11. package/dist/src/adapters/catalog.js +2 -1
  12. package/dist/src/adapters/catalog.js.map +1 -1
  13. package/dist/src/adapters/providers/codex-auth.js +29 -0
  14. package/dist/src/adapters/providers/codex-auth.js.map +1 -0
  15. package/dist/src/adapters/providers/codex.js +23 -6
  16. package/dist/src/adapters/providers/codex.js.map +1 -1
  17. package/dist/src/adapters/providers/model-call.js +26 -0
  18. package/dist/src/adapters/providers/model-call.js.map +1 -0
  19. package/dist/src/artifacts/index.js +1 -1
  20. package/dist/src/artifacts/index.js.map +1 -1
  21. package/dist/src/artifacts/preparer.js +7 -2
  22. package/dist/src/artifacts/preparer.js.map +1 -1
  23. package/dist/src/backends/harbor/agent-budget.js +31 -0
  24. package/dist/src/backends/harbor/agent-budget.js.map +1 -0
  25. package/dist/src/backends/harbor/backend.js +6 -4
  26. package/dist/src/backends/harbor/backend.js.map +1 -1
  27. package/dist/src/backends/harbor/index.js +1 -0
  28. package/dist/src/backends/harbor/index.js.map +1 -1
  29. package/dist/src/backends/harbor/regrade.js +77 -0
  30. package/dist/src/backends/harbor/regrade.js.map +1 -0
  31. package/dist/src/backends/index.js +1 -0
  32. package/dist/src/backends/index.js.map +1 -1
  33. package/dist/src/benchmarks/index.js +3 -0
  34. package/dist/src/benchmarks/index.js.map +1 -0
  35. package/dist/src/benchmarks/loader.js +271 -0
  36. package/dist/src/benchmarks/loader.js.map +1 -0
  37. package/dist/src/benchmarks/metrics.js +16 -0
  38. package/dist/src/benchmarks/metrics.js.map +1 -0
  39. package/dist/src/benchmarks/toml.js +13 -0
  40. package/dist/src/benchmarks/toml.js.map +1 -0
  41. package/dist/src/benchmarks/validation.js +242 -0
  42. package/dist/src/benchmarks/validation.js.map +1 -0
  43. package/dist/src/cli/arguments.js +6 -4
  44. package/dist/src/cli/arguments.js.map +1 -1
  45. package/dist/src/cli/commands/benchmark.js +22 -0
  46. package/dist/src/cli/commands/benchmark.js.map +1 -0
  47. package/dist/src/cli/commands/capabilities.js +18 -0
  48. package/dist/src/cli/commands/capabilities.js.map +1 -0
  49. package/dist/src/cli/commands/eval.js +15 -3
  50. package/dist/src/cli/commands/eval.js.map +1 -1
  51. package/dist/src/cli/commands/run.js +37 -15
  52. package/dist/src/cli/commands/run.js.map +1 -1
  53. package/dist/src/cli/commands/trajectory.js +96 -5
  54. package/dist/src/cli/commands/trajectory.js.map +1 -1
  55. package/dist/src/cli/commands/verifier.js +36 -0
  56. package/dist/src/cli/commands/verifier.js.map +1 -0
  57. package/dist/src/cli/main.js +6 -0
  58. package/dist/src/cli/main.js.map +1 -1
  59. package/dist/src/cli/output.js +14 -2
  60. package/dist/src/cli/output.js.map +1 -1
  61. package/dist/src/control-plane/eval-control-work.js +5 -0
  62. package/dist/src/control-plane/eval-control-work.js.map +1 -1
  63. package/dist/src/control-plane/eval-scheduler.js +4 -1
  64. package/dist/src/control-plane/eval-scheduler.js.map +1 -1
  65. package/dist/src/control-plane/index.js +1 -1
  66. package/dist/src/control-plane/index.js.map +1 -1
  67. package/dist/src/control-plane/remote-result-transport.js +3 -0
  68. package/dist/src/control-plane/remote-result-transport.js.map +1 -1
  69. package/dist/src/control-plane/remote-work-coordinator.js +1 -0
  70. package/dist/src/control-plane/remote-work-coordinator.js.map +1 -1
  71. package/dist/src/control-plane/remote-work-item.js +62 -0
  72. package/dist/src/control-plane/remote-work-item.js.map +1 -0
  73. package/dist/src/control-plane/remote-work-recovery.js +44 -5
  74. package/dist/src/control-plane/remote-work-recovery.js.map +1 -1
  75. package/dist/src/control-plane/remote-worker-protocol.js +2 -23
  76. package/dist/src/control-plane/remote-worker-protocol.js.map +1 -1
  77. package/dist/src/control-plane/rerun-scheduler.js +51 -14
  78. package/dist/src/control-plane/rerun-scheduler.js.map +1 -1
  79. package/dist/src/control-plane/rerun-submission.js +10 -4
  80. package/dist/src/control-plane/rerun-submission.js.map +1 -1
  81. package/dist/src/control-plane/work-admission.js +2 -0
  82. package/dist/src/control-plane/work-admission.js.map +1 -1
  83. package/dist/src/control-plane/work-dispatcher.js +9 -1
  84. package/dist/src/control-plane/work-dispatcher.js.map +1 -1
  85. package/dist/src/controller-runtime/hash.js +7 -2
  86. package/dist/src/controller-runtime/hash.js.map +1 -1
  87. package/dist/src/daemon/auth.js +5 -0
  88. package/dist/src/daemon/auth.js.map +1 -1
  89. package/dist/src/domain/benchmarks.js +2 -0
  90. package/dist/src/domain/benchmarks.js.map +1 -0
  91. package/dist/src/domain/index.js +4 -0
  92. package/dist/src/domain/index.js.map +1 -1
  93. package/dist/src/domain/runs.js.map +1 -1
  94. package/dist/src/domain/validation.js +11 -4
  95. package/dist/src/domain/validation.js.map +1 -1
  96. package/dist/src/domain/verifier-evidence-validation.js +214 -0
  97. package/dist/src/domain/verifier-evidence-validation.js.map +1 -0
  98. package/dist/src/domain/verifier-evidence.js +3 -0
  99. package/dist/src/domain/verifier-evidence.js.map +1 -0
  100. package/dist/src/domain/verifier-score-contract.js +232 -0
  101. package/dist/src/domain/verifier-score-contract.js.map +1 -0
  102. package/dist/src/evals/benchmark-adapter-manifest.js +219 -0
  103. package/dist/src/evals/benchmark-adapter-manifest.js.map +1 -0
  104. package/dist/src/evals/benchmark-candidate.js +36 -0
  105. package/dist/src/evals/benchmark-candidate.js.map +1 -0
  106. package/dist/src/evals/benchmark-run.js +166 -0
  107. package/dist/src/evals/benchmark-run.js.map +1 -0
  108. package/dist/src/evals/collect-only-rerun.js +2 -1
  109. package/dist/src/evals/collect-only-rerun.js.map +1 -1
  110. package/dist/src/evals/directory.js +1 -1
  111. package/dist/src/evals/directory.js.map +1 -1
  112. package/dist/src/evals/duration-estimator.js +126 -0
  113. package/dist/src/evals/duration-estimator.js.map +1 -0
  114. package/dist/src/evals/eval-lifecycle-events.js +13 -0
  115. package/dist/src/evals/eval-lifecycle-events.js.map +1 -1
  116. package/dist/src/evals/eval-logical-plan.js +51 -0
  117. package/dist/src/evals/eval-logical-plan.js.map +1 -0
  118. package/dist/src/evals/eval-result-builder.js +81 -0
  119. package/dist/src/evals/eval-result-builder.js.map +1 -0
  120. package/dist/src/evals/evolution-baseline.js +191 -0
  121. package/dist/src/evals/evolution-baseline.js.map +1 -0
  122. package/dist/src/evals/execution-plan.js +39 -3
  123. package/dist/src/evals/execution-plan.js.map +1 -1
  124. package/dist/src/evals/failure-classifier.js +61 -0
  125. package/dist/src/evals/failure-classifier.js.map +1 -0
  126. package/dist/src/evals/harbor-artifact-builder.js +6 -2
  127. package/dist/src/evals/harbor-artifact-builder.js.map +1 -1
  128. package/dist/src/evals/harbor-bridge-error.js +41 -1
  129. package/dist/src/evals/harbor-bridge-error.js.map +1 -1
  130. package/dist/src/evals/harbor-node-runtime.js +146 -0
  131. package/dist/src/evals/harbor-node-runtime.js.map +1 -0
  132. package/dist/src/evals/index.js +15 -0
  133. package/dist/src/evals/index.js.map +1 -1
  134. package/dist/src/evals/infrastructure-retry.js +109 -22
  135. package/dist/src/evals/infrastructure-retry.js.map +1 -1
  136. package/dist/src/evals/native-phase-evidence.js +440 -0
  137. package/dist/src/evals/native-phase-evidence.js.map +1 -0
  138. package/dist/src/evals/physical-retry-work.js +31 -0
  139. package/dist/src/evals/physical-retry-work.js.map +1 -0
  140. package/dist/src/evals/planned-execution-support.js +22 -10
  141. package/dist/src/evals/planned-execution-support.js.map +1 -1
  142. package/dist/src/evals/planned-execution.js +120 -112
  143. package/dist/src/evals/planned-execution.js.map +1 -1
  144. package/dist/src/evals/planned-progress-publisher.js +62 -0
  145. package/dist/src/evals/planned-progress-publisher.js.map +1 -0
  146. package/dist/src/evals/planned-retry-execution.js +174 -0
  147. package/dist/src/evals/planned-retry-execution.js.map +1 -0
  148. package/dist/src/evals/planned-retry-lifecycle.js +4 -2
  149. package/dist/src/evals/planned-retry-lifecycle.js.map +1 -1
  150. package/dist/src/evals/preparation-rerun.js +225 -0
  151. package/dist/src/evals/preparation-rerun.js.map +1 -0
  152. package/dist/src/evals/progress.js +48 -8
  153. package/dist/src/evals/progress.js.map +1 -1
  154. package/dist/src/evals/recovery.js +54 -4
  155. package/dist/src/evals/recovery.js.map +1 -1
  156. package/dist/src/evals/regrade-evidence.js +77 -0
  157. package/dist/src/evals/regrade-evidence.js.map +1 -0
  158. package/dist/src/evals/remote-infrastructure-retry.js +84 -18
  159. package/dist/src/evals/remote-infrastructure-retry.js.map +1 -1
  160. package/dist/src/evals/request.js +21 -3
  161. package/dist/src/evals/request.js.map +1 -1
  162. package/dist/src/evals/rerun-inputs.js +19 -0
  163. package/dist/src/evals/rerun-inputs.js.map +1 -1
  164. package/dist/src/evals/rerun-types.js +2 -6
  165. package/dist/src/evals/rerun-types.js.map +1 -1
  166. package/dist/src/evals/rerun.js +27 -20
  167. package/dist/src/evals/rerun.js.map +1 -1
  168. package/dist/src/evals/result-helpers.js +13 -0
  169. package/dist/src/evals/result-helpers.js.map +1 -1
  170. package/dist/src/evals/retry-backoff.js +20 -0
  171. package/dist/src/evals/retry-backoff.js.map +1 -0
  172. package/dist/src/evals/retry-state.js +195 -0
  173. package/dist/src/evals/retry-state.js.map +1 -0
  174. package/dist/src/evals/scheduler-metrics.js +95 -0
  175. package/dist/src/evals/scheduler-metrics.js.map +1 -0
  176. package/dist/src/evals/scheduler-trace-replay.js +107 -0
  177. package/dist/src/evals/scheduler-trace-replay.js.map +1 -0
  178. package/dist/src/evals/service.js +87 -138
  179. package/dist/src/evals/service.js.map +1 -1
  180. package/dist/src/evals/trial-import.js +47 -50
  181. package/dist/src/evals/trial-import.js.map +1 -1
  182. package/dist/src/evals/trial-publication-recovery.js +45 -5
  183. package/dist/src/evals/trial-publication-recovery.js.map +1 -1
  184. package/dist/src/evals/trial-reference-validation.js +49 -0
  185. package/dist/src/evals/trial-reference-validation.js.map +1 -0
  186. package/dist/src/evals/verifier-artifacts.js +317 -0
  187. package/dist/src/evals/verifier-artifacts.js.map +1 -0
  188. package/dist/src/evals/verifier-eligibility.js +25 -0
  189. package/dist/src/evals/verifier-eligibility.js.map +1 -0
  190. package/dist/src/evals/verifier-only-rerun.js +210 -0
  191. package/dist/src/evals/verifier-only-rerun.js.map +1 -0
  192. package/dist/src/evals/verifier-runtime.js +61 -0
  193. package/dist/src/evals/verifier-runtime.js.map +1 -0
  194. package/dist/src/evals/verifier-score-artifacts.js +119 -0
  195. package/dist/src/evals/verifier-score-artifacts.js.map +1 -0
  196. package/dist/src/foundation/contained-file.js +85 -0
  197. package/dist/src/foundation/contained-file.js.map +1 -0
  198. package/dist/src/foundation/credential-redaction.js +26 -1
  199. package/dist/src/foundation/credential-redaction.js.map +1 -1
  200. package/dist/src/foundation/fs.js +2 -1
  201. package/dist/src/foundation/fs.js.map +1 -1
  202. package/dist/src/foundation/index.js +2 -1
  203. package/dist/src/foundation/index.js.map +1 -1
  204. package/dist/src/runs/adapter-process.js +12 -0
  205. package/dist/src/runs/adapter-process.js.map +1 -0
  206. package/dist/src/runs/executor.js +24 -30
  207. package/dist/src/runs/executor.js.map +1 -1
  208. package/dist/src/runs/finalizer.js +17 -0
  209. package/dist/src/runs/finalizer.js.map +1 -1
  210. package/dist/src/runs/index.js +5 -0
  211. package/dist/src/runs/index.js.map +1 -1
  212. package/dist/src/runs/phase-bundle.js +86 -0
  213. package/dist/src/runs/phase-bundle.js.map +1 -0
  214. package/dist/src/runs/phase-cancellation.js +107 -0
  215. package/dist/src/runs/phase-cancellation.js.map +1 -0
  216. package/dist/src/runs/phase-group.js +113 -0
  217. package/dist/src/runs/phase-group.js.map +1 -0
  218. package/dist/src/runs/query.js +1 -1
  219. package/dist/src/runs/query.js.map +1 -1
  220. package/dist/src/runs/records.js +3 -0
  221. package/dist/src/runs/records.js.map +1 -1
  222. package/dist/src/runs/request.js +4 -2
  223. package/dist/src/runs/request.js.map +1 -1
  224. package/dist/src/runs/verifier-evidence-redaction.js +61 -0
  225. package/dist/src/runs/verifier-evidence-redaction.js.map +1 -0
  226. package/dist/src/runs/verifier-evidence.js +459 -0
  227. package/dist/src/runs/verifier-evidence.js.map +1 -0
  228. package/dist/src/runs/verifier-structured-evidence.js +83 -0
  229. package/dist/src/runs/verifier-structured-evidence.js.map +1 -0
  230. package/dist/src/trajectories/analysis.js +288 -0
  231. package/dist/src/trajectories/analysis.js.map +1 -0
  232. package/dist/src/trajectories/chunk-projection.js +148 -0
  233. package/dist/src/trajectories/chunk-projection.js.map +1 -0
  234. package/dist/src/trajectories/content-projection.js +389 -0
  235. package/dist/src/trajectories/content-projection.js.map +1 -0
  236. package/dist/src/trajectories/dsh-chunk-contract.js +176 -0
  237. package/dist/src/trajectories/dsh-chunk-contract.js.map +1 -0
  238. package/dist/src/trajectories/dsh-contract.js +284 -0
  239. package/dist/src/trajectories/dsh-contract.js.map +1 -0
  240. package/dist/src/trajectories/events-chunk-drill.js +95 -0
  241. package/dist/src/trajectories/events-chunk-drill.js.map +1 -0
  242. package/dist/src/trajectories/events-page.js +367 -0
  243. package/dist/src/trajectories/events-page.js.map +1 -0
  244. package/dist/src/trajectories/index.js +3 -0
  245. package/dist/src/trajectories/index.js.map +1 -1
  246. package/dist/src/trajectories/request-attempt.js +47 -0
  247. package/dist/src/trajectories/request-attempt.js.map +1 -0
  248. package/dist/src/trajectories/stream-reader.js +311 -0
  249. package/dist/src/trajectories/stream-reader.js.map +1 -0
  250. package/dist/src/trajectories/surface-fold.js +126 -0
  251. package/dist/src/trajectories/surface-fold.js.map +1 -0
  252. package/dist/src/workers/remote-harbor-worker.js +3 -0
  253. package/dist/src/workers/remote-harbor-worker.js.map +1 -1
  254. package/docs/schemas/benchmark-hook-request-v1.schema.json +65 -0
  255. package/docs/schemas/benchmark-hook-response-v1.schema.json +75 -0
  256. package/docs/schemas/benchmark-hook-v1.schema.json +33 -0
  257. package/docs/schemas/benchmark-lock-v1.schema.json +351 -0
  258. package/docs/schemas/benchmark-metric-v1.schema.json +37 -0
  259. package/docs/schemas/benchmark-package-v1.schema.json +167 -0
  260. package/docs/schemas/benchmark-phase-group.schema.json +41 -0
  261. package/docs/schemas/benchmark-profile-v1.schema.json +141 -0
  262. package/docs/schemas/benchmark-task-v1.schema.json +384 -0
  263. package/docs/schemas/benchmark-tool-result-v1.schema.json +30 -0
  264. package/docs/schemas/error.schema.json +22 -0
  265. package/docs/schemas/eval-progress.schema.json +1 -15
  266. package/docs/schemas/eval-rerun-result.schema.json +6 -1
  267. package/docs/schemas/eval-rerun-submission.schema.json +3 -1
  268. package/docs/schemas/eval-result.schema.json +1 -15
  269. package/docs/schemas/eval-trial-publication.schema.json +1 -25
  270. package/docs/schemas/eval-trial-reference.schema.json +129 -0
  271. package/docs/schemas/regrade-assessment-reference.schema.json +11 -0
  272. package/docs/schemas/run-context.schema.json +15 -0
  273. package/docs/schemas/run-manifest.schema.json +4 -0
  274. package/docs/schemas/run-request.schema.json +4 -0
  275. package/docs/schemas/trajectory-analysis.schema.json +258 -0
  276. package/docs/schemas/trajectory-events-page.schema.json +40 -0
  277. package/docs/schemas/verifier-assessment.schema.json +77 -0
  278. package/docs/schemas/verifier-evidence.schema.json +276 -0
  279. package/integrations/harbor/hitch_benchmark.py +252 -0
  280. package/integrations/harbor/hitch_candidate_recycle.py +262 -0
  281. package/integrations/harbor/hitch_harbor_agent.py +541 -43
  282. package/integrations/harbor/hitch_harbor_environment.py +138 -0
  283. package/integrations/harbor/hitch_harbor_verifier.py +60 -2
  284. package/integrations/harbor/hitch_phase_supervisor.py +452 -0
  285. package/integrations/harbor/hitch_tool_client.mjs +91 -0
  286. package/integrations/model-call/cli.js +57 -0
  287. package/package.json +17 -6
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import json
6
7
  import re
7
8
  import tempfile
@@ -35,6 +36,8 @@ _GPU_OVERRIDE_PREFIX = "__HITCH_GPU_OVERRIDE_"
35
36
  class HitchHarborDockerEnvironment(DockerEnvironment):
36
37
  """Use Harbor's Docker semantics with a final ownership-label overlay."""
37
38
 
39
+ _hitch_static_no_network = False
40
+
38
41
  def __init__(
39
42
  self,
40
43
  *args: Any,
@@ -62,12 +65,16 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
62
65
  self._hitch_main_gpu_count = hitch_main_gpu_count
63
66
  self._hitch_ownership_temp_dir: tempfile.TemporaryDirectory[str] | None = None
64
67
  self._hitch_ownership_compose_path: Path | None = None
68
+ self._hitch_phase_compose_path: Path | None = None
69
+ self._hitch_candidate_recycler = None
65
70
  positional = list(args)
66
71
  task_env_config = kwargs.get("task_env_config")
67
72
  task_env_position: int | None = None
68
73
  if task_env_config is None and len(positional) >= 5:
69
74
  task_env_position = 4
70
75
  task_env_config = positional[task_env_position]
76
+ self._hitch_static_no_network = _static_no_network(positional, kwargs, task_env_config)
77
+ self._hitch_static_network_receipt = None
71
78
  if task_env_config is not None:
72
79
  requested = getattr(task_env_config, "docker_image", None)
73
80
  resolved = self._hitch_prebuilt_task_image or self._hitch_resolved_images.get(requested)
@@ -78,6 +85,14 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
78
85
  else:
79
86
  positional[task_env_position] = updated
80
87
  super().__init__(*positional, **kwargs)
88
+ self._hitch_benchmark = None
89
+ self._hitch_benchmark_platform = None
90
+ directory = getattr(self, "environment_dir", None)
91
+ if directory and (directory.parent / ".hitch-benchmark.json").is_file():
92
+ self._hitch_benchmark_platform = "linux/amd64"
93
+ if directory and directory.name == "environment" and (directory.parent / ".hitch-benchmark.json").is_file():
94
+ from hitch_benchmark import BenchmarkSession, descriptor
95
+ self._hitch_benchmark = BenchmarkSession(self, descriptor(directory))
81
96
  if (
82
97
  self._hitch_ownership_labels
83
98
  or self._hitch_service_resource_limits
@@ -85,16 +100,108 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
85
100
  or self._hitch_prebuilt_task_image
86
101
  or self._hitch_model_proxy_host_gateway
87
102
  or self._hitch_main_gpu_count > 0
103
+ or self._hitch_benchmark_platform
104
+ or self._hitch_static_no_network
88
105
  ):
89
106
  self._hitch_ownership_compose_path = self._write_ownership_overlay()
90
107
 
91
108
  @property
92
109
  def _docker_compose_paths(self) -> list[Path]:
110
+ if self._hitch_static_no_network and (self._environment_docker_compose_path.exists() or self.extra_docker_compose_paths):
111
+ raise ValueError("static no-network requires a single Dockerfile/image environment without Compose additions")
93
112
  paths = list(super()._docker_compose_paths)
94
113
  if self._hitch_ownership_compose_path is not None:
95
114
  paths.append(self._hitch_ownership_compose_path)
115
+ if self._hitch_phase_compose_path is not None:
116
+ paths.append(self._hitch_phase_compose_path)
96
117
  return paths
97
118
 
119
+ def _requires_egress_control(self, *, startup_network_policy, phase_network_policies):
120
+ if self._hitch_static_no_network:
121
+ return False
122
+ return super()._requires_egress_control(startup_network_policy=startup_network_policy, phase_network_policies=phase_network_policies)
123
+
124
+ @property
125
+ def capabilities(self):
126
+ result = super().capabilities
127
+ if self._hitch_static_no_network:
128
+ return result.model_copy(update={"disable_internet": True, "dynamic_network_policy": False})
129
+ return result
130
+
131
+ async def _apply_network_policy(self, network_policy):
132
+ if self._hitch_static_no_network:
133
+ if network_policy != self.network_policy:
134
+ raise ValueError("static no-network cannot change runtime network policy")
135
+ return
136
+ await super()._apply_network_policy(network_policy)
137
+
138
+ async def _verify_static_network(self):
139
+ result = await self._run_docker_compose_command(["ps", "--all", "--quiet", MAIN_SERVICE_NAME])
140
+ identity = result.stdout.strip()
141
+ if not re.fullmatch(r"[a-f0-9]{64}", identity):
142
+ raise RuntimeError("static no-network requires exactly one owned main container")
143
+ template = '{"mode":{{json .HostConfig.NetworkMode}},"networks":{{json .NetworkSettings.Networks}},"ports":{{json .HostConfig.PortBindings}}}'
144
+ process = await asyncio.create_subprocess_exec("docker", "inspect", "--format", template, identity,
145
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL)
146
+ try:
147
+ stdout, _ = await asyncio.wait_for(process.communicate(), 15)
148
+ finally:
149
+ if process.returncode is None:
150
+ process.kill(); await process.wait()
151
+ if process.returncode != 0 or len(stdout) > 65536:
152
+ raise RuntimeError("could not verify static no-network container")
153
+ inspected = json.loads(stdout)
154
+ if inspected.get("mode") != "none" or set(inspected.get("networks") or {}) != {"none"} or inspected.get("ports"):
155
+ raise RuntimeError("Docker did not enforce the static no-network declaration")
156
+ self._hitch_static_network_receipt = {"container_id": identity, "network_mode": "none", "host_ports": False}
157
+
158
+ async def recycle_candidate_phase(self, phase_index: int) -> dict[str, Any]:
159
+ """Replace only main after the supervisor has exported the retired run.
160
+
161
+ Does not start a model or prepare a tool binding. The native task VM and
162
+ service containers remain live. Failures require whole-trial cleanup.
163
+ """
164
+ if self._hitch_candidate_recycler is None:
165
+ from hitch_candidate_recycle import CandidateRecycler
166
+ self._hitch_candidate_recycler = CandidateRecycler(self)
167
+ return await self._hitch_candidate_recycler.recycle(phase_index)
168
+
169
+ async def start(self, force_build: bool):
170
+ try:
171
+ await super().start(force_build)
172
+ if self._hitch_static_no_network:
173
+ await self._verify_static_network()
174
+ if self._hitch_benchmark:
175
+ await self._hitch_benchmark.prepare()
176
+ except BaseException:
177
+ # Harbor may abort setup before entering its normal teardown path.
178
+ try:
179
+ await self.stop(delete=True)
180
+ except BaseException:
181
+ pass
182
+ raise
183
+
184
+ async def stop_service(self, service: str) -> None:
185
+ try:
186
+ await super().stop_service(service)
187
+ if service == MAIN_SERVICE_NAME and self._hitch_benchmark:
188
+ await self._hitch_benchmark.snapshot()
189
+ except BaseException as error:
190
+ if self._hitch_benchmark:
191
+ self._hitch_benchmark.failure = {"phase": "quiesce", "error": type(error).__name__}
192
+ self._hitch_benchmark.write_journal()
193
+ raise
194
+
195
+ async def stop(self, delete: bool):
196
+ try:
197
+ if self._hitch_benchmark and self._hitch_benchmark.started:
198
+ try:
199
+ await super().stop_service(MAIN_SERVICE_NAME)
200
+ finally:
201
+ await self._hitch_benchmark.cleanup()
202
+ finally:
203
+ await super().stop(delete)
204
+
98
205
  def _write_ownership_overlay(self) -> Path:
99
206
  services = {MAIN_SERVICE_NAME}
100
207
  if self._enable_egress_control:
@@ -104,6 +211,7 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
104
211
  external_networks: set[str] = set()
105
212
  external_volumes: set[str] = set()
106
213
  build_services: set[str] = set()
214
+ declared_platforms: set[str] = set()
107
215
  image_overlays: dict[str, str] = {}
108
216
  sources = [self._environment_docker_compose_path, *self.extra_docker_compose_paths]
109
217
  for source in sources:
@@ -117,6 +225,11 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
117
225
  services.update(_mapping_names(document.get("services"), "services", source))
118
226
  for name, config in (document.get("services") or {}).items():
119
227
  if isinstance(config, dict):
228
+ if 'platform' in config:
229
+ if config['platform'] is not None:
230
+ declared_platforms.add(name)
231
+ else:
232
+ declared_platforms.discard(name)
120
233
  if "build" in config and config.get("build") is not None:
121
234
  build_services.add(name)
122
235
  reference = config.get("image")
@@ -132,8 +245,14 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
132
245
  service_overlays: dict[str, dict[str, Any]] = {}
133
246
  for name in sorted(services):
134
247
  config: dict[str, Any] = {}
248
+ # The benchmark default must not replace explicit per-service
249
+ # architecture, e.g. an ARM QEMU sidecar beside an AMD64 candidate.
250
+ if getattr(self, "_hitch_benchmark_platform", None) and name not in declared_platforms:
251
+ config["platform"] = self._hitch_benchmark_platform
135
252
  if labels:
136
253
  config["labels"] = labels
254
+ if name == MAIN_SERVICE_NAME and self._hitch_static_no_network:
255
+ config["network_mode"] = "none"
137
256
  resolved_image = image_overlays.get(name)
138
257
  if name == MAIN_SERVICE_NAME and self._hitch_prebuilt_task_image:
139
258
  resolved_image = self._hitch_prebuilt_task_image
@@ -207,6 +326,25 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
207
326
  return target
208
327
 
209
328
 
329
+ def _static_no_network(positional, kwargs, task_env_config) -> bool:
330
+ """Use Docker's isolated namespace only when no phase needs connectivity.
331
+
332
+ Compose tasks and dynamic policies retain Harbor's egress-control path.
333
+ A single image/Dockerfile has no sibling-service network to preserve.
334
+ """
335
+ policy = kwargs.get("network_policy", positional[6] if len(positional) > 6 else None)
336
+ phases = kwargs.get("phase_network_policies", positional[7] if len(positional) > 7 else ()) or ()
337
+ directory = kwargs.get("environment_dir", positional[0] if positional else None)
338
+ return bool(
339
+ getattr(getattr(task_env_config, "os", None), "value", None) == "linux"
340
+ and getattr(getattr(policy, "network_mode", None), "value", None) == "no-network"
341
+ and all(phase == policy for phase in phases)
342
+ and directory is not None
343
+ and not (Path(directory) / "docker-compose.yaml").exists()
344
+ and not kwargs.get("extra_docker_compose")
345
+ )
346
+
347
+
210
348
  def _mapping_names(value: Any, label: str, source: Path) -> set[str]:
211
349
  if value is None:
212
350
  return set()
@@ -23,9 +23,15 @@ from harbor.verifier.verifier import Verifier
23
23
 
24
24
 
25
25
  MAX_LOG_BYTES = 256 * 1024
26
+ HITCH_AGENT_OUTCOME_NAME = "hitch-agent-outcome.json"
26
27
  LOG_NAMES = ("test-stdout.txt", "test-stderr.txt", "stdout.txt", "stderr.txt")
27
- OUTPUT_NAMES = (*LOG_NAMES, "reward.txt", "reward.json", "ctrf.json")
28
- CONTROL_NAMES = ("infrastructure-error.json", "infrastructure-retry-history.json")
28
+ OUTPUT_NAMES = (*LOG_NAMES, "reward.txt", "reward.json", "process.json", "feedback.json", "ctrf.json")
29
+ CONTROL_NAMES = (
30
+ "infrastructure-error.json",
31
+ "infrastructure-retry-history.json",
32
+ "candidate-ineligible.json",
33
+ "eligibility-gate.json",
34
+ )
29
35
 
30
36
  INFRASTRUCTURE_PATTERNS: tuple[tuple[str, tuple[re.Pattern[str], ...]], ...] = (
31
37
  (
@@ -126,8 +132,28 @@ class HitchRetryingVerifier(Verifier):
126
132
  self._write_phase_timing(started_ns)
127
133
 
128
134
  async def _verify_with_retries(self) -> VerifierResult:
135
+ directory = getattr(getattr(getattr(self, "task", None), "paths", None), "environment_dir", None)
136
+ if directory and (directory.parent / ".hitch-benchmark.json").is_file():
137
+ from hitch_benchmark import restore_final_response, validate_collected_submission
138
+ validate_collected_submission(self)
139
+ await restore_final_response(self)
129
140
  # Do not trust a control file left by the candidate or a prior phase.
130
141
  await self._remove_files(CONTROL_NAMES)
142
+ eligibility = self._read_trusted_agent_outcome()
143
+ if eligibility is None:
144
+ self._write_json("eligibility-gate.json", {
145
+ "schema_version": "1", "status": "unavailable", "verifier_executed": True,
146
+ })
147
+ elif eligibility["gradeability"] == "ungradeable":
148
+ self._write_json("candidate-ineligible.json", {
149
+ "schema_version": "1",
150
+ "code": "candidate_evidence_unavailable",
151
+ "run_id": eligibility["run_id"],
152
+ "candidate_bundle": eligibility["candidate_bundle"],
153
+ "reason_code": eligibility.get("reason_code", "candidate_evidence_unavailable"),
154
+ "verifier_executed": False,
155
+ })
156
+ return VerifierResult(rewards={"reward": 0})
131
157
  attempts: list[dict[str, Any]] = []
132
158
  for attempt in range(1, self.infrastructure_retries + 2):
133
159
  result: VerifierResult | None = None
@@ -152,6 +178,10 @@ class HitchRetryingVerifier(Verifier):
152
178
  if attempts:
153
179
  self._write_history("recovered", attempts)
154
180
  assert result is not None
181
+ directory = getattr(getattr(getattr(self, "task", None), "paths", None), "environment_dir", None)
182
+ if directory and (directory.parent / ".hitch-benchmark.json").is_file():
183
+ from hitch_benchmark import normalize_rewards
184
+ return normalize_rewards(self, result)
155
185
  return result
156
186
 
157
187
  attempts.append(
@@ -188,6 +218,34 @@ class HitchRetryingVerifier(Verifier):
188
218
 
189
219
  raise AssertionError("unreachable verifier retry state")
190
220
 
221
+ def _read_trusted_agent_outcome(self) -> dict[str, Any] | None:
222
+ source = self.trial_paths.verifier_dir.parent / "agent" / HITCH_AGENT_OUTCOME_NAME
223
+ try:
224
+ if not source.is_file() or source.stat().st_size <= 0 or source.stat().st_size > 16 * 1024:
225
+ return None
226
+ value = json.loads(source.read_text(encoding="utf-8"))
227
+ except (OSError, UnicodeError, json.JSONDecodeError):
228
+ return None
229
+ if not isinstance(value, dict) or set(value) - {
230
+ "schema_version", "run_id", "status", "candidate_bundle", "submission_snapshot", "gradeability", "reason_code",
231
+ }:
232
+ return None
233
+ if value.get("schema_version") != "1" or re.fullmatch(r"run_[a-f0-9]{32}", str(value.get("run_id", ""))) is None:
234
+ return None
235
+ if value.get("status") not in {"succeeded", "failed", "timed_out", "cancelled"}:
236
+ return None
237
+ if value.get("candidate_bundle") not in {"complete", "missing", "invalid"}:
238
+ return None
239
+ if value.get("submission_snapshot") not in {"complete", "missing", "not-required"}:
240
+ return None
241
+ if value.get("gradeability") not in {"gradeable", "ungradeable"}:
242
+ return None
243
+ if value["gradeability"] == "gradeable" and value["candidate_bundle"] != "complete":
244
+ return None
245
+ if "reason_code" in value and (not isinstance(value["reason_code"], str) or not value["reason_code"]):
246
+ return None
247
+ return value
248
+
191
249
  def _write_phase_timing(self, started_ns: int) -> None:
192
250
  self._write_json(
193
251
  "hitch-phase-timings.json",