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
@@ -0,0 +1,276 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/rsi-gear/agent-hitch/blob/main/docs/schemas/verifier-evidence.schema.json",
4
+ "title": "Hitch Verifier Evidence V1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "kind", "run_id", "verifier"],
8
+ "properties": {
9
+ "schema_version": { "const": "1" },
10
+ "kind": { "const": "verifier-evidence" },
11
+ "run_id": { "type": "string", "pattern": "^run_[a-f0-9]{32}$" },
12
+ "parent": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["eval_id", "trial_id", "attempt"],
16
+ "properties": {
17
+ "eval_id": { "type": "string", "pattern": "^eval_[a-f0-9]{32}$" },
18
+ "trial_id": { "type": "string", "minLength": 1 },
19
+ "attempt": { "type": "integer", "minimum": 1 }
20
+ }
21
+ },
22
+ "observation": { "$ref": "#/$defs/observation" },
23
+ "verifier": { "$ref": "#/$defs/verifier" },
24
+ "redactions": { "type": "array", "items": { "$ref": "#/$defs/redaction" } }
25
+ },
26
+ "$defs": {
27
+ "observation": {
28
+ "oneOf": [
29
+ {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["status", "reward", "verifier_result_ref"],
33
+ "properties": {
34
+ "status": { "const": "valid" },
35
+ "reward": { "type": "number" },
36
+ "verifier_result_ref": { "$ref": "#/$defs/relativePath" }
37
+ }
38
+ },
39
+ {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["status", "invalid_reason"],
43
+ "properties": {
44
+ "status": { "const": "invalid" },
45
+ "invalid_reason": { "type": "string", "minLength": 1 },
46
+ "verifier_result_ref": { "$ref": "#/$defs/relativePath" }
47
+ }
48
+ }
49
+ ]
50
+ },
51
+ "verifier": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["status"],
55
+ "properties": {
56
+ "status": { "enum": ["complete", "result_only", "missing", "corrupt"] },
57
+ "result": {},
58
+ "result_sha256": { "$ref": "#/$defs/sha256" },
59
+ "scores": { "$ref": "#/$defs/scores" },
60
+ "process": { "$ref": "#/$defs/process" },
61
+ "feedback": { "$ref": "#/$defs/feedback" },
62
+ "structured_artifacts": {
63
+ "type": "object",
64
+ "additionalProperties": false,
65
+ "minProperties": 1,
66
+ "properties": {
67
+ "process": { "$ref": "#/$defs/structuredArtifact" },
68
+ "feedback": { "$ref": "#/$defs/structuredArtifact" }
69
+ }
70
+ },
71
+ "diagnostics": { "$ref": "#/$defs/diagnostics" },
72
+ "issues": { "type": "array", "maxItems": 16, "items": { "type": "string", "minLength": 1, "maxLength": 1024 } }
73
+ },
74
+ "allOf": [
75
+ {
76
+ "if": { "properties": { "status": { "const": "complete" } }, "required": ["status"] },
77
+ "then": {
78
+ "required": ["result", "result_sha256", "diagnostics"],
79
+ "properties": {
80
+ "diagnostics": {
81
+ "allOf": [
82
+ { "$ref": "#/$defs/diagnostics" },
83
+ { "anyOf": [{ "required": ["ctrf"] }, { "required": ["stdout"] }, { "required": ["stderr"] }] }
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ },
89
+ {
90
+ "if": { "properties": { "status": { "const": "result_only" } }, "required": ["status"] },
91
+ "then": {
92
+ "required": ["result", "result_sha256"],
93
+ "properties": {
94
+ "diagnostics": {
95
+ "allOf": [
96
+ { "$ref": "#/$defs/diagnostics" },
97
+ { "not": { "anyOf": [{ "required": ["ctrf"] }, { "required": ["stdout"] }, { "required": ["stderr"] }] } }
98
+ ]
99
+ }
100
+ }
101
+ }
102
+ },
103
+ {
104
+ "if": { "properties": { "status": { "const": "missing" } }, "required": ["status"] },
105
+ "then": { "not": { "required": ["result"] } }
106
+ }
107
+ ]
108
+ },
109
+ "scores": {
110
+ "type": "object",
111
+ "additionalProperties": false,
112
+ "required": ["total_score", "normalization"],
113
+ "properties": {
114
+ "total_score": { "type": "number" },
115
+ "process_score": { "type": "number" },
116
+ "normalization": { "enum": ["standard", "legacy-reward"] }
117
+ },
118
+ "allOf": [
119
+ {
120
+ "if": { "properties": { "normalization": { "const": "legacy-reward" } }, "required": ["normalization"] },
121
+ "then": { "not": { "required": ["process_score"] } }
122
+ }
123
+ ]
124
+ },
125
+ "process": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": ["schema_version", "metric", "score", "detail_status"],
129
+ "properties": {
130
+ "schema_version": { "const": "1" },
131
+ "metric": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" },
132
+ "score": { "type": "number" },
133
+ "detail_status": { "enum": ["components", "aggregate-only"] },
134
+ "passed": { "type": "integer", "minimum": 0 },
135
+ "total": { "type": "integer", "minimum": 0 },
136
+ "excluded": { "type": "integer", "minimum": 0 },
137
+ "components": { "type": "array", "items": { "$ref": "#/$defs/processComponent" } }
138
+ },
139
+ "oneOf": [
140
+ {
141
+ "properties": { "detail_status": { "const": "components" } },
142
+ "required": ["detail_status", "passed", "total", "excluded", "components"]
143
+ },
144
+ {
145
+ "properties": { "detail_status": { "const": "aggregate-only" } },
146
+ "required": ["detail_status"],
147
+ "not": { "anyOf": [{ "required": ["passed"] }, { "required": ["total"] }, { "required": ["excluded"] }, { "required": ["components"] }] }
148
+ }
149
+ ]
150
+ },
151
+ "processComponent": {
152
+ "type": "object",
153
+ "additionalProperties": false,
154
+ "required": ["id", "category", "status", "weight"],
155
+ "properties": {
156
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" },
157
+ "category": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" },
158
+ "status": { "enum": ["passed", "failed", "excluded"] },
159
+ "weight": { "type": "number", "exclusiveMinimum": 0 },
160
+ "code": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" },
161
+ "public_details": { "type": "object" },
162
+ "private_details_ref": { "$ref": "#/$defs/relativePath" },
163
+ "trajectory_refs": { "type": "array", "items": { "$ref": "#/$defs/trajectoryRef" } }
164
+ }
165
+ },
166
+ "feedback": {
167
+ "type": "object",
168
+ "additionalProperties": false,
169
+ "required": ["schema_version", "items"],
170
+ "properties": {
171
+ "schema_version": { "const": "1" },
172
+ "items": {
173
+ "type": "array",
174
+ "items": {
175
+ "type": "object",
176
+ "additionalProperties": false,
177
+ "required": ["code", "severity", "message"],
178
+ "properties": {
179
+ "code": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" },
180
+ "severity": { "enum": ["info", "warning", "error"] },
181
+ "message": { "type": "string", "minLength": 1, "maxLength": 16384 },
182
+ "component_ids": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$" } },
183
+ "trajectory_refs": { "type": "array", "items": { "$ref": "#/$defs/trajectoryRef" } }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ },
189
+ "trajectoryRef": {
190
+ "type": "object",
191
+ "additionalProperties": false,
192
+ "required": ["run_id"],
193
+ "properties": {
194
+ "run_id": { "type": "string", "pattern": "^run_[a-f0-9]{32}$" },
195
+ "seq_start": { "type": "integer", "minimum": 0 },
196
+ "seq_end": { "type": "integer", "minimum": 0 }
197
+ }
198
+ },
199
+ "structuredArtifact": {
200
+ "type": "object",
201
+ "additionalProperties": false,
202
+ "required": ["ref", "bytes", "sha256"],
203
+ "properties": {
204
+ "ref": { "enum": ["verifier/process.json", "verifier/feedback.json"] },
205
+ "bytes": { "type": "integer", "minimum": 0 },
206
+ "sha256": { "$ref": "#/$defs/sha256" }
207
+ }
208
+ },
209
+ "diagnostics": {
210
+ "type": "object",
211
+ "additionalProperties": false,
212
+ "minProperties": 1,
213
+ "properties": {
214
+ "ctrf": { "$ref": "#/$defs/artifact" },
215
+ "stdout": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/artifact" } },
216
+ "stderr": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/artifact" } },
217
+ "infrastructure_error": {},
218
+ "retry_history": { "type": "array", "minItems": 1, "items": {} }
219
+ }
220
+ },
221
+ "artifact": {
222
+ "type": "object",
223
+ "additionalProperties": false,
224
+ "required": ["name", "media_type", "bytes", "sha256", "truncated"],
225
+ "properties": {
226
+ "name": { "enum": ["ctrf.json", "test-stdout.txt", "test-stderr.txt", "stdout.txt", "stderr.txt"] },
227
+ "media_type": { "enum": ["application/json", "text/plain"] },
228
+ "bytes": { "type": "integer", "minimum": 0 },
229
+ "sha256": { "$ref": "#/$defs/sha256" },
230
+ "truncated": { "type": "boolean" },
231
+ "json": {},
232
+ "text": { "type": "string" }
233
+ },
234
+ "oneOf": [
235
+ { "required": ["json"], "not": { "required": ["text"] } },
236
+ { "required": ["text"], "not": { "required": ["json"] } }
237
+ ],
238
+ "allOf": [
239
+ {
240
+ "if": { "properties": { "name": { "const": "ctrf.json" } }, "required": ["name"] },
241
+ "then": { "properties": { "media_type": { "const": "application/json" } } },
242
+ "else": { "properties": { "media_type": { "const": "text/plain" } } }
243
+ },
244
+ {
245
+ "if": { "properties": { "truncated": { "const": true } }, "required": ["truncated"] },
246
+ "then": { "required": ["text"], "not": { "required": ["json"] } }
247
+ },
248
+ {
249
+ "if": { "required": ["json"] },
250
+ "then": {
251
+ "properties": {
252
+ "name": { "const": "ctrf.json" },
253
+ "media_type": { "const": "application/json" },
254
+ "truncated": { "const": false }
255
+ }
256
+ }
257
+ }
258
+ ]
259
+ },
260
+ "redaction": {
261
+ "type": "object",
262
+ "additionalProperties": false,
263
+ "required": ["rule_id", "count"],
264
+ "properties": {
265
+ "rule_id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$" },
266
+ "count": { "type": "integer", "minimum": 1 }
267
+ }
268
+ },
269
+ "relativePath": {
270
+ "type": "string",
271
+ "minLength": 1,
272
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.?(?:/|$))(?!.*\\\\).+$"
273
+ },
274
+ "sha256": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
275
+ }
276
+ }
@@ -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}})