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.
- package/README.md +30 -3
- package/README.zh-CN.md +14 -2
- package/dist/bin/hitch.js +25 -3
- package/dist/bin/hitch.js.map +1 -1
- package/dist/scripts/canary-benchmark.js +54 -0
- package/dist/scripts/canary-benchmark.js.map +1 -0
- package/dist/scripts/canary-buildkit-secrets.js +166 -0
- package/dist/scripts/canary-buildkit-secrets.js.map +1 -0
- package/dist/scripts/canary-collision-domains.js +77 -0
- package/dist/scripts/canary-collision-domains.js.map +1 -0
- package/dist/scripts/canary-docker-image-observation.js +181 -0
- package/dist/scripts/canary-docker-image-observation.js.map +1 -0
- package/dist/scripts/canary-eval-scheduler-throughput.js +47 -0
- package/dist/scripts/canary-eval-scheduler-throughput.js.map +1 -0
- package/dist/scripts/canary-gpu-hardware.js +95 -0
- package/dist/scripts/canary-gpu-hardware.js.map +1 -0
- package/dist/scripts/canary-harbor-load.js +365 -0
- package/dist/scripts/canary-harbor-load.js.map +1 -0
- package/dist/scripts/canary-packaged-harness.js +75 -0
- package/dist/scripts/canary-packaged-harness.js.map +1 -0
- package/dist/scripts/canary-resource-load.js +188 -0
- package/dist/scripts/canary-resource-load.js.map +1 -0
- package/dist/scripts/check-architecture.js +10 -5
- package/dist/scripts/check-architecture.js.map +1 -1
- package/dist/scripts/docker-reaper-crash-child.js +21 -0
- package/dist/scripts/docker-reaper-crash-child.js.map +1 -0
- package/dist/src/adapters/catalog.js +3 -1
- package/dist/src/adapters/catalog.js.map +1 -1
- package/dist/src/adapters/discovery.js +1 -1
- package/dist/src/adapters/discovery.js.map +1 -1
- package/dist/src/adapters/providers/claude.js +6 -0
- package/dist/src/adapters/providers/claude.js.map +1 -1
- package/dist/src/adapters/providers/codex-auth.js +29 -0
- package/dist/src/adapters/providers/codex-auth.js.map +1 -0
- package/dist/src/adapters/providers/codex.js +29 -6
- package/dist/src/adapters/providers/codex.js.map +1 -1
- package/dist/src/adapters/providers/deepseek.js +6 -0
- package/dist/src/adapters/providers/deepseek.js.map +1 -1
- package/dist/src/adapters/providers/model-call.js +26 -0
- package/dist/src/adapters/providers/model-call.js.map +1 -0
- package/dist/src/adapters/providers/opencode.js +6 -0
- package/dist/src/adapters/providers/opencode.js.map +1 -1
- package/dist/src/adapters/providers/pi.js +6 -0
- package/dist/src/adapters/providers/pi.js.map +1 -1
- package/dist/src/artifacts/index.js +1 -0
- package/dist/src/artifacts/index.js.map +1 -1
- package/dist/src/artifacts/preparer.js +7 -2
- package/dist/src/artifacts/preparer.js.map +1 -1
- package/dist/src/backends/harbor/agent-budget.js +31 -0
- package/dist/src/backends/harbor/agent-budget.js.map +1 -0
- package/dist/src/backends/harbor/backend.js +59 -107
- package/dist/src/backends/harbor/backend.js.map +1 -1
- package/dist/src/backends/harbor/environment-config.js +92 -0
- package/dist/src/backends/harbor/environment-config.js.map +1 -0
- package/dist/src/backends/harbor/index.js +6 -1
- package/dist/src/backends/harbor/index.js.map +1 -1
- package/dist/src/backends/harbor/model-proxy-config.js +23 -0
- package/dist/src/backends/harbor/model-proxy-config.js.map +1 -0
- package/dist/src/backends/harbor/process.js +177 -0
- package/dist/src/backends/harbor/process.js.map +1 -0
- package/dist/src/backends/harbor/regrade.js +77 -0
- package/dist/src/backends/harbor/regrade.js.map +1 -0
- package/dist/src/backends/harbor/runtime-toolchain.js +30 -0
- package/dist/src/backends/harbor/runtime-toolchain.js.map +1 -0
- package/dist/src/backends/harbor/supervisor.js +74 -0
- package/dist/src/backends/harbor/supervisor.js.map +1 -0
- package/dist/src/backends/harbor/task-resources.js +206 -0
- package/dist/src/backends/harbor/task-resources.js.map +1 -0
- package/dist/src/backends/harbor/tools.js +6 -29
- package/dist/src/backends/harbor/tools.js.map +1 -1
- package/dist/src/backends/index.js +5 -1
- package/dist/src/backends/index.js.map +1 -1
- package/dist/src/benchmarks/index.js +3 -0
- package/dist/src/benchmarks/index.js.map +1 -0
- package/dist/src/benchmarks/loader.js +271 -0
- package/dist/src/benchmarks/loader.js.map +1 -0
- package/dist/src/benchmarks/metrics.js +16 -0
- package/dist/src/benchmarks/metrics.js.map +1 -0
- package/dist/src/benchmarks/toml.js +13 -0
- package/dist/src/benchmarks/toml.js.map +1 -0
- package/dist/src/benchmarks/validation.js +242 -0
- package/dist/src/benchmarks/validation.js.map +1 -0
- package/dist/src/cli/arguments.js +6 -4
- package/dist/src/cli/arguments.js.map +1 -1
- package/dist/src/cli/commands/benchmark.js +22 -0
- package/dist/src/cli/commands/benchmark.js.map +1 -0
- package/dist/src/cli/commands/capabilities.js +18 -0
- package/dist/src/cli/commands/capabilities.js.map +1 -0
- package/dist/src/cli/commands/daemon.js +135 -8
- package/dist/src/cli/commands/daemon.js.map +1 -1
- package/dist/src/cli/commands/eval.js +228 -6
- package/dist/src/cli/commands/eval.js.map +1 -1
- package/dist/src/cli/commands/images.js +40 -0
- package/dist/src/cli/commands/images.js.map +1 -0
- package/dist/src/cli/commands/run.js +42 -17
- package/dist/src/cli/commands/run.js.map +1 -1
- package/dist/src/cli/commands/runs.js +25 -3
- package/dist/src/cli/commands/runs.js.map +1 -1
- package/dist/src/cli/commands/trajectory.js +96 -5
- package/dist/src/cli/commands/trajectory.js.map +1 -1
- package/dist/src/cli/commands/verifier.js +36 -0
- package/dist/src/cli/commands/verifier.js.map +1 -0
- package/dist/src/cli/commands/worker.js +79 -0
- package/dist/src/cli/commands/worker.js.map +1 -0
- package/dist/src/cli/main.js +10 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/output.js +84 -4
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/control-plane/build-admission.js +69 -0
- package/dist/src/control-plane/build-admission.js.map +1 -0
- package/dist/src/control-plane/build-records.js +6 -0
- package/dist/src/control-plane/build-records.js.map +1 -0
- package/dist/src/control-plane/collisions.js +40 -0
- package/dist/src/control-plane/collisions.js.map +1 -0
- package/dist/src/control-plane/eval-control-state.js +22 -0
- package/dist/src/control-plane/eval-control-state.js.map +1 -0
- package/dist/src/control-plane/eval-control-work.js +44 -0
- package/dist/src/control-plane/eval-control-work.js.map +1 -0
- package/dist/src/control-plane/eval-image-resolution.js +52 -0
- package/dist/src/control-plane/eval-image-resolution.js.map +1 -0
- package/dist/src/control-plane/eval-image-services.js +30 -0
- package/dist/src/control-plane/eval-image-services.js.map +1 -0
- package/dist/src/control-plane/eval-records.js +252 -0
- package/dist/src/control-plane/eval-records.js.map +1 -0
- package/dist/src/control-plane/eval-recovery.js +181 -0
- package/dist/src/control-plane/eval-recovery.js.map +1 -0
- package/dist/src/control-plane/eval-scheduler.js +450 -0
- package/dist/src/control-plane/eval-scheduler.js.map +1 -0
- package/dist/src/control-plane/index.js +20 -0
- package/dist/src/control-plane/index.js.map +1 -0
- package/dist/src/control-plane/local-worker.js +49 -0
- package/dist/src/control-plane/local-worker.js.map +1 -0
- package/dist/src/control-plane/model-capture-planning.js +12 -0
- package/dist/src/control-plane/model-capture-planning.js.map +1 -0
- package/dist/src/control-plane/remote-result-transport.js +175 -0
- package/dist/src/control-plane/remote-result-transport.js.map +1 -0
- package/dist/src/control-plane/remote-work-coordinator.js +333 -0
- package/dist/src/control-plane/remote-work-coordinator.js.map +1 -0
- package/dist/src/control-plane/remote-work-inputs.js +266 -0
- package/dist/src/control-plane/remote-work-inputs.js.map +1 -0
- package/dist/src/control-plane/remote-work-item.js +62 -0
- package/dist/src/control-plane/remote-work-item.js.map +1 -0
- package/dist/src/control-plane/remote-work-recovery.js +220 -0
- package/dist/src/control-plane/remote-work-recovery.js.map +1 -0
- package/dist/src/control-plane/remote-worker-artifacts.js +135 -0
- package/dist/src/control-plane/remote-worker-artifacts.js.map +1 -0
- package/dist/src/control-plane/remote-worker-client.js +202 -0
- package/dist/src/control-plane/remote-worker-client.js.map +1 -0
- package/dist/src/control-plane/remote-worker-configuration.js +34 -0
- package/dist/src/control-plane/remote-worker-configuration.js.map +1 -0
- package/dist/src/control-plane/remote-worker-credentials.js +58 -0
- package/dist/src/control-plane/remote-worker-credentials.js.map +1 -0
- package/dist/src/control-plane/remote-worker-protocol.js +489 -0
- package/dist/src/control-plane/remote-worker-protocol.js.map +1 -0
- package/dist/src/control-plane/remote-worker-runner.js +214 -0
- package/dist/src/control-plane/remote-worker-runner.js.map +1 -0
- package/dist/src/control-plane/remote-workers.js +377 -0
- package/dist/src/control-plane/remote-workers.js.map +1 -0
- package/dist/src/control-plane/rerun-scheduler.js +448 -0
- package/dist/src/control-plane/rerun-scheduler.js.map +1 -0
- package/dist/src/control-plane/rerun-submission.js +62 -0
- package/dist/src/control-plane/rerun-submission.js.map +1 -0
- package/dist/src/control-plane/resources.js +182 -0
- package/dist/src/control-plane/resources.js.map +1 -0
- package/dist/src/control-plane/scheduler-eval-entry.js +32 -0
- package/dist/src/control-plane/scheduler-eval-entry.js.map +1 -0
- package/dist/src/control-plane/synthetic-result.js +21 -0
- package/dist/src/control-plane/synthetic-result.js.map +1 -0
- package/dist/src/control-plane/work-admission.js +23 -0
- package/dist/src/control-plane/work-admission.js.map +1 -0
- package/dist/src/control-plane/work-dispatcher.js +237 -0
- package/dist/src/control-plane/work-dispatcher.js.map +1 -0
- package/dist/src/controller-runtime/hash.js +19 -3
- package/dist/src/controller-runtime/hash.js.map +1 -1
- package/dist/src/controller-runtime/index.js +1 -1
- package/dist/src/controller-runtime/index.js.map +1 -1
- package/dist/src/controller-runtime/store.js +15 -6
- package/dist/src/controller-runtime/store.js.map +1 -1
- package/dist/src/daemon/auth.js +5 -0
- package/dist/src/daemon/auth.js.map +1 -1
- package/dist/src/daemon/client.js.map +1 -1
- package/dist/src/daemon/event-stream.js +46 -0
- package/dist/src/daemon/event-stream.js.map +1 -0
- package/dist/src/daemon/health.js +66 -0
- package/dist/src/daemon/health.js.map +1 -0
- package/dist/src/daemon/launcher.js +28 -1
- package/dist/src/daemon/launcher.js.map +1 -1
- package/dist/src/daemon/logging.js +23 -0
- package/dist/src/daemon/logging.js.map +1 -0
- package/dist/src/daemon/scheduler.js +26 -5
- package/dist/src/daemon/scheduler.js.map +1 -1
- package/dist/src/daemon/server.js +255 -98
- package/dist/src/daemon/server.js.map +1 -1
- package/dist/src/daemon/telemetry.js +163 -0
- package/dist/src/daemon/telemetry.js.map +1 -0
- package/dist/src/daemon/worker-routes.js +219 -0
- package/dist/src/daemon/worker-routes.js.map +1 -0
- package/dist/src/domain/benchmarks.js +2 -0
- package/dist/src/domain/benchmarks.js.map +1 -0
- package/dist/src/domain/bundles.js +2 -0
- package/dist/src/domain/bundles.js.map +1 -0
- package/dist/src/domain/control-plane.js +2 -0
- package/dist/src/domain/control-plane.js.map +1 -0
- package/dist/src/domain/execution-evidence.js +2 -0
- package/dist/src/domain/execution-evidence.js.map +1 -0
- package/dist/src/domain/execution-plan.js +2 -0
- package/dist/src/domain/execution-plan.js.map +1 -0
- package/dist/src/domain/images.js +2 -0
- package/dist/src/domain/images.js.map +1 -0
- package/dist/src/domain/index.js +13 -0
- package/dist/src/domain/index.js.map +1 -1
- package/dist/src/domain/interactions.js +2 -0
- package/dist/src/domain/interactions.js.map +1 -0
- package/dist/src/domain/providers.js +2 -0
- package/dist/src/domain/providers.js.map +1 -0
- package/dist/src/domain/resources.js +2 -0
- package/dist/src/domain/resources.js.map +1 -0
- package/dist/src/domain/runs.js.map +1 -1
- package/dist/src/domain/validation.js +11 -4
- package/dist/src/domain/validation.js.map +1 -1
- package/dist/src/domain/verifier-evidence-validation.js +214 -0
- package/dist/src/domain/verifier-evidence-validation.js.map +1 -0
- package/dist/src/domain/verifier-evidence.js +3 -0
- package/dist/src/domain/verifier-evidence.js.map +1 -0
- package/dist/src/domain/verifier-score-contract.js +232 -0
- package/dist/src/domain/verifier-score-contract.js.map +1 -0
- package/dist/src/domain/workers.js +2 -0
- package/dist/src/domain/workers.js.map +1 -0
- package/dist/src/evals/benchmark-adapter-manifest.js +219 -0
- package/dist/src/evals/benchmark-adapter-manifest.js.map +1 -0
- package/dist/src/evals/benchmark-candidate.js +36 -0
- package/dist/src/evals/benchmark-candidate.js.map +1 -0
- package/dist/src/evals/benchmark-run.js +166 -0
- package/dist/src/evals/benchmark-run.js.map +1 -0
- package/dist/src/evals/collect-only-rerun.js +183 -0
- package/dist/src/evals/collect-only-rerun.js.map +1 -0
- package/dist/src/evals/directory.js +30 -0
- package/dist/src/evals/directory.js.map +1 -0
- package/dist/src/evals/docker-engine-stats.js +89 -0
- package/dist/src/evals/docker-engine-stats.js.map +1 -0
- package/dist/src/evals/docker-ownership.js +51 -0
- package/dist/src/evals/docker-ownership.js.map +1 -0
- package/dist/src/evals/docker-reaper.js +144 -0
- package/dist/src/evals/docker-reaper.js.map +1 -0
- package/dist/src/evals/docker-resource-observer.js +207 -0
- package/dist/src/evals/docker-resource-observer.js.map +1 -0
- package/dist/src/evals/duration-estimator.js +126 -0
- package/dist/src/evals/duration-estimator.js.map +1 -0
- package/dist/src/evals/environment-image-planning.js +164 -0
- package/dist/src/evals/environment-image-planning.js.map +1 -0
- package/dist/src/evals/eval-artifact-planning.js +56 -0
- package/dist/src/evals/eval-artifact-planning.js.map +1 -0
- package/dist/src/evals/eval-finalization.js +10 -0
- package/dist/src/evals/eval-finalization.js.map +1 -0
- package/dist/src/evals/eval-lifecycle-events.js +29 -0
- package/dist/src/evals/eval-lifecycle-events.js.map +1 -0
- package/dist/src/evals/eval-logical-plan.js +51 -0
- package/dist/src/evals/eval-logical-plan.js.map +1 -0
- package/dist/src/evals/eval-result-builder.js +81 -0
- package/dist/src/evals/eval-result-builder.js.map +1 -0
- package/dist/src/evals/events.js +53 -5
- package/dist/src/evals/events.js.map +1 -1
- package/dist/src/evals/evolution-baseline.js +191 -0
- package/dist/src/evals/evolution-baseline.js.map +1 -0
- package/dist/src/evals/execution-evidence.js +116 -0
- package/dist/src/evals/execution-evidence.js.map +1 -0
- package/dist/src/evals/execution-leases.js +342 -0
- package/dist/src/evals/execution-leases.js.map +1 -0
- package/dist/src/evals/execution-plan-artifacts.js +72 -0
- package/dist/src/evals/execution-plan-artifacts.js.map +1 -0
- package/dist/src/evals/execution-plan-images.js +69 -0
- package/dist/src/evals/execution-plan-images.js.map +1 -0
- package/dist/src/evals/execution-plan-resources.js +174 -0
- package/dist/src/evals/execution-plan-resources.js.map +1 -0
- package/dist/src/evals/execution-plan.js +451 -0
- package/dist/src/evals/execution-plan.js.map +1 -0
- package/dist/src/evals/failure-classifier.js +61 -0
- package/dist/src/evals/failure-classifier.js.map +1 -0
- package/dist/src/evals/harbor-artifact-builder-image.js +99 -0
- package/dist/src/evals/harbor-artifact-builder-image.js.map +1 -0
- package/dist/src/evals/harbor-artifact-builder.js +358 -0
- package/dist/src/evals/harbor-artifact-builder.js.map +1 -0
- package/dist/src/evals/harbor-bridge-error.js +58 -3
- package/dist/src/evals/harbor-bridge-error.js.map +1 -1
- package/dist/src/evals/harbor-node-runtime.js +146 -0
- package/dist/src/evals/harbor-node-runtime.js.map +1 -0
- package/dist/src/evals/harbor-phase-timings.js +60 -0
- package/dist/src/evals/harbor-phase-timings.js.map +1 -0
- package/dist/src/evals/index.js +40 -2
- package/dist/src/evals/index.js.map +1 -1
- package/dist/src/evals/infrastructure-retry.js +208 -84
- package/dist/src/evals/infrastructure-retry.js.map +1 -1
- package/dist/src/evals/interaction-capture-import.js +49 -0
- package/dist/src/evals/interaction-capture-import.js.map +1 -0
- package/dist/src/evals/local-docker-provider.js +372 -0
- package/dist/src/evals/local-docker-provider.js.map +1 -0
- package/dist/src/evals/local-eval-planning.js +80 -0
- package/dist/src/evals/local-eval-planning.js.map +1 -0
- package/dist/src/evals/model-capture-plan.js +22 -0
- package/dist/src/evals/model-capture-plan.js.map +1 -0
- package/dist/src/evals/model-capture-runtime.js +114 -0
- package/dist/src/evals/model-capture-runtime.js.map +1 -0
- package/dist/src/evals/model-proxy-runtime-state.js +53 -0
- package/dist/src/evals/model-proxy-runtime-state.js.map +1 -0
- package/dist/src/evals/native-phase-evidence.js +440 -0
- package/dist/src/evals/native-phase-evidence.js.map +1 -0
- package/dist/src/evals/physical-retry-work.js +31 -0
- package/dist/src/evals/physical-retry-work.js.map +1 -0
- package/dist/src/evals/planned-execution-support.js +66 -0
- package/dist/src/evals/planned-execution-support.js.map +1 -0
- package/dist/src/evals/planned-execution.js +469 -0
- package/dist/src/evals/planned-execution.js.map +1 -0
- package/dist/src/evals/planned-progress-publisher.js +62 -0
- package/dist/src/evals/planned-progress-publisher.js.map +1 -0
- package/dist/src/evals/planned-remote-execution.js +40 -0
- package/dist/src/evals/planned-remote-execution.js.map +1 -0
- package/dist/src/evals/planned-retry-execution.js +174 -0
- package/dist/src/evals/planned-retry-execution.js.map +1 -0
- package/dist/src/evals/planned-retry-lifecycle.js +188 -0
- package/dist/src/evals/planned-retry-lifecycle.js.map +1 -0
- package/dist/src/evals/preparation-rerun.js +225 -0
- package/dist/src/evals/preparation-rerun.js.map +1 -0
- package/dist/src/evals/prepared-harness.js +76 -0
- package/dist/src/evals/prepared-harness.js.map +1 -0
- package/dist/src/evals/progress.js +52 -12
- package/dist/src/evals/progress.js.map +1 -1
- package/dist/src/evals/records.js +20 -3
- package/dist/src/evals/records.js.map +1 -1
- package/dist/src/evals/recovery.js +242 -0
- package/dist/src/evals/recovery.js.map +1 -0
- package/dist/src/evals/regrade-evidence.js +77 -0
- package/dist/src/evals/regrade-evidence.js.map +1 -0
- package/dist/src/evals/remote-infrastructure-retry.js +165 -0
- package/dist/src/evals/remote-infrastructure-retry.js.map +1 -0
- package/dist/src/evals/request.js +21 -3
- package/dist/src/evals/request.js.map +1 -1
- package/dist/src/evals/rerun-inputs.js +108 -0
- package/dist/src/evals/rerun-inputs.js.map +1 -0
- package/dist/src/evals/rerun-slots.js +4 -3
- package/dist/src/evals/rerun-slots.js.map +1 -1
- package/dist/src/evals/rerun-types.js +45 -0
- package/dist/src/evals/rerun-types.js.map +1 -0
- package/dist/src/evals/rerun.js +133 -88
- package/dist/src/evals/rerun.js.map +1 -1
- package/dist/src/evals/result-helpers.js +78 -0
- package/dist/src/evals/result-helpers.js.map +1 -0
- package/dist/src/evals/resume-state.js +36 -0
- package/dist/src/evals/resume-state.js.map +1 -0
- package/dist/src/evals/retry-backoff.js +20 -0
- package/dist/src/evals/retry-backoff.js.map +1 -0
- package/dist/src/evals/retry-state.js +195 -0
- package/dist/src/evals/retry-state.js.map +1 -0
- package/dist/src/evals/scheduler-metrics.js +95 -0
- package/dist/src/evals/scheduler-metrics.js.map +1 -0
- package/dist/src/evals/scheduler-trace-replay.js +107 -0
- package/dist/src/evals/scheduler-trace-replay.js.map +1 -0
- package/dist/src/evals/service-types.js +2 -0
- package/dist/src/evals/service-types.js.map +1 -0
- package/dist/src/evals/service.js +331 -347
- package/dist/src/evals/service.js.map +1 -1
- package/dist/src/evals/task-resources.js +209 -0
- package/dist/src/evals/task-resources.js.map +1 -0
- package/dist/src/evals/trial-environment-evidence.js +87 -0
- package/dist/src/evals/trial-environment-evidence.js.map +1 -0
- package/dist/src/evals/trial-execution-evidence.js +13 -0
- package/dist/src/evals/trial-execution-evidence.js.map +1 -0
- package/dist/src/evals/trial-import-identity.js +31 -0
- package/dist/src/evals/trial-import-identity.js.map +1 -0
- package/dist/src/evals/trial-import.js +183 -188
- package/dist/src/evals/trial-import.js.map +1 -1
- package/dist/src/evals/trial-publication-recovery.js +144 -0
- package/dist/src/evals/trial-publication-recovery.js.map +1 -0
- package/dist/src/evals/trial-publication.js +25 -0
- package/dist/src/evals/trial-publication.js.map +1 -0
- package/dist/src/evals/trial-reference-validation.js +49 -0
- package/dist/src/evals/trial-reference-validation.js.map +1 -0
- package/dist/src/evals/verifier-artifacts.js +317 -0
- package/dist/src/evals/verifier-artifacts.js.map +1 -0
- package/dist/src/evals/verifier-eligibility.js +25 -0
- package/dist/src/evals/verifier-eligibility.js.map +1 -0
- package/dist/src/evals/verifier-only-rerun.js +210 -0
- package/dist/src/evals/verifier-only-rerun.js.map +1 -0
- package/dist/src/evals/verifier-runtime.js +61 -0
- package/dist/src/evals/verifier-runtime.js.map +1 -0
- package/dist/src/evals/verifier-score-artifacts.js +119 -0
- package/dist/src/evals/verifier-score-artifacts.js.map +1 -0
- package/dist/src/evals/work-item-artifacts.js +23 -0
- package/dist/src/evals/work-item-artifacts.js.map +1 -0
- package/dist/src/foundation/config.js +17 -0
- package/dist/src/foundation/config.js.map +1 -1
- package/dist/src/foundation/contained-file.js +85 -0
- package/dist/src/foundation/contained-file.js.map +1 -0
- package/dist/src/foundation/credential-redaction.js +162 -0
- package/dist/src/foundation/credential-redaction.js.map +1 -0
- package/dist/src/foundation/environment-image-references.js +59 -0
- package/dist/src/foundation/environment-image-references.js.map +1 -0
- package/dist/src/foundation/executable.js +45 -5
- package/dist/src/foundation/executable.js.map +1 -1
- package/dist/src/foundation/fs.js +2 -1
- package/dist/src/foundation/fs.js.map +1 -1
- package/dist/src/foundation/index.js +6 -2
- package/dist/src/foundation/index.js.map +1 -1
- package/dist/src/foundation/process-identity.js +69 -0
- package/dist/src/foundation/process-identity.js.map +1 -0
- package/dist/src/images/context.js +47 -0
- package/dist/src/images/context.js.map +1 -0
- package/dist/src/images/docker-buildkit.js +137 -0
- package/dist/src/images/docker-buildkit.js.map +1 -0
- package/dist/src/images/dockerfile.js +67 -0
- package/dist/src/images/dockerfile.js.map +1 -0
- package/dist/src/images/gc.js +261 -0
- package/dist/src/images/gc.js.map +1 -0
- package/dist/src/images/index.js +10 -0
- package/dist/src/images/index.js.map +1 -0
- package/dist/src/images/manifest.js +109 -0
- package/dist/src/images/manifest.js.map +1 -0
- package/dist/src/images/ownership.js +5 -0
- package/dist/src/images/ownership.js.map +1 -0
- package/dist/src/images/records.js +32 -0
- package/dist/src/images/records.js.map +1 -0
- package/dist/src/images/registry.js +154 -0
- package/dist/src/images/registry.js.map +1 -0
- package/dist/src/images/service.js +224 -0
- package/dist/src/images/service.js.map +1 -0
- package/dist/src/model-access/capture.js +255 -0
- package/dist/src/model-access/capture.js.map +1 -0
- package/dist/src/model-access/index.js +5 -0
- package/dist/src/model-access/index.js.map +1 -0
- package/dist/src/model-access/policy.js +49 -0
- package/dist/src/model-access/policy.js.map +1 -0
- package/dist/src/model-access/proxy.js +336 -0
- package/dist/src/model-access/proxy.js.map +1 -0
- package/dist/src/model-access/records.js +167 -0
- package/dist/src/model-access/records.js.map +1 -0
- package/dist/src/runs/adapter-process.js +12 -0
- package/dist/src/runs/adapter-process.js.map +1 -0
- package/dist/src/runs/bundle.js +450 -0
- package/dist/src/runs/bundle.js.map +1 -0
- package/dist/src/runs/events.js +12 -1
- package/dist/src/runs/events.js.map +1 -1
- package/dist/src/runs/executor.js +46 -45
- package/dist/src/runs/executor.js.map +1 -1
- package/dist/src/runs/finalizer.js +17 -0
- package/dist/src/runs/finalizer.js.map +1 -1
- package/dist/src/runs/index.js +8 -1
- package/dist/src/runs/index.js.map +1 -1
- package/dist/src/runs/manifest.js +5 -5
- package/dist/src/runs/manifest.js.map +1 -1
- package/dist/src/runs/phase-bundle.js +86 -0
- package/dist/src/runs/phase-bundle.js.map +1 -0
- package/dist/src/runs/phase-cancellation.js +107 -0
- package/dist/src/runs/phase-cancellation.js.map +1 -0
- package/dist/src/runs/phase-group.js +113 -0
- package/dist/src/runs/phase-group.js.map +1 -0
- package/dist/src/runs/query.js +1 -1
- package/dist/src/runs/query.js.map +1 -1
- package/dist/src/runs/records.js +3 -0
- package/dist/src/runs/records.js.map +1 -1
- package/dist/src/runs/request.js +10 -3
- package/dist/src/runs/request.js.map +1 -1
- package/dist/src/runs/training-candidate.js +161 -0
- package/dist/src/runs/training-candidate.js.map +1 -0
- package/dist/src/runs/verifier-evidence-redaction.js +61 -0
- package/dist/src/runs/verifier-evidence-redaction.js.map +1 -0
- package/dist/src/runs/verifier-evidence.js +459 -0
- package/dist/src/runs/verifier-evidence.js.map +1 -0
- package/dist/src/runs/verifier-structured-evidence.js +83 -0
- package/dist/src/runs/verifier-structured-evidence.js.map +1 -0
- package/dist/src/trajectories/analysis.js +288 -0
- package/dist/src/trajectories/analysis.js.map +1 -0
- package/dist/src/trajectories/chunk-projection.js +148 -0
- package/dist/src/trajectories/chunk-projection.js.map +1 -0
- package/dist/src/trajectories/content-projection.js +389 -0
- package/dist/src/trajectories/content-projection.js.map +1 -0
- package/dist/src/trajectories/dsh-chunk-contract.js +176 -0
- package/dist/src/trajectories/dsh-chunk-contract.js.map +1 -0
- package/dist/src/trajectories/dsh-contract.js +284 -0
- package/dist/src/trajectories/dsh-contract.js.map +1 -0
- package/dist/src/trajectories/events-chunk-drill.js +95 -0
- package/dist/src/trajectories/events-chunk-drill.js.map +1 -0
- package/dist/src/trajectories/events-page.js +367 -0
- package/dist/src/trajectories/events-page.js.map +1 -0
- package/dist/src/trajectories/index.js +3 -0
- package/dist/src/trajectories/index.js.map +1 -1
- package/dist/src/trajectories/provider-capture.js +15 -24
- package/dist/src/trajectories/provider-capture.js.map +1 -1
- package/dist/src/trajectories/providers/deepseek.js +3 -3
- package/dist/src/trajectories/providers/deepseek.js.map +1 -1
- package/dist/src/trajectories/request-attempt.js +47 -0
- package/dist/src/trajectories/request-attempt.js.map +1 -0
- package/dist/src/trajectories/stream-reader.js +311 -0
- package/dist/src/trajectories/stream-reader.js.map +1 -0
- package/dist/src/trajectories/surface-fold.js +126 -0
- package/dist/src/trajectories/surface-fold.js.map +1 -0
- package/dist/src/workers/index.js +3 -0
- package/dist/src/workers/index.js.map +1 -0
- package/dist/src/workers/remote-harbor-work-spec.js +97 -0
- package/dist/src/workers/remote-harbor-work-spec.js.map +1 -0
- package/dist/src/workers/remote-harbor-worker.js +223 -0
- package/dist/src/workers/remote-harbor-worker.js.map +1 -0
- package/docs/schemas/benchmark-hook-request-v1.schema.json +65 -0
- package/docs/schemas/benchmark-hook-response-v1.schema.json +75 -0
- package/docs/schemas/benchmark-hook-v1.schema.json +33 -0
- package/docs/schemas/benchmark-lock-v1.schema.json +351 -0
- package/docs/schemas/benchmark-metric-v1.schema.json +37 -0
- package/docs/schemas/benchmark-package-v1.schema.json +167 -0
- package/docs/schemas/benchmark-phase-group.schema.json +41 -0
- package/docs/schemas/benchmark-profile-v1.schema.json +141 -0
- package/docs/schemas/benchmark-task-v1.schema.json +384 -0
- package/docs/schemas/benchmark-tool-result-v1.schema.json +30 -0
- package/docs/schemas/environment-build-index.schema.json +13 -0
- package/docs/schemas/environment-build-record.schema.json +25 -0
- package/docs/schemas/environment-image-manifest.schema.json +62 -0
- package/docs/schemas/error.schema.json +22 -0
- package/docs/schemas/eval-control.schema.json +36 -0
- package/docs/schemas/eval-execution-plan.schema.json +216 -0
- package/docs/schemas/eval-progress.schema.json +1 -15
- package/docs/schemas/eval-rerun-result.schema.json +58 -0
- package/docs/schemas/eval-rerun-state.schema.json +61 -0
- package/docs/schemas/eval-rerun-submission.schema.json +60 -0
- package/docs/schemas/eval-result.schema.json +32 -13
- package/docs/schemas/eval-submission-input.schema.json +14 -0
- package/docs/schemas/eval-submission.schema.json +68 -0
- package/docs/schemas/eval-trial-publication.schema.json +15 -0
- package/docs/schemas/eval-trial-reference.schema.json +129 -0
- package/docs/schemas/execution-evidence.schema.json +96 -0
- package/docs/schemas/execution-lease.schema.json +50 -0
- package/docs/schemas/execution-provider-status.schema.json +65 -0
- package/docs/schemas/execution-worker.schema.json +53 -0
- package/docs/schemas/harbor-process-exit.schema.json +14 -0
- package/docs/schemas/interaction-capture-ref.schema.json +45 -0
- package/docs/schemas/local-provider-execution.schema.json +33 -0
- package/docs/schemas/model-interaction.schema.json +41 -0
- package/docs/schemas/regrade-assessment-reference.schema.json +11 -0
- package/docs/schemas/remote-result-envelope.schema.json +32 -0
- package/docs/schemas/remote-tree-envelope.schema.json +25 -0
- package/docs/schemas/remote-work-offer.schema.json +58 -0
- package/docs/schemas/remote-work-receipt.schema.json +50 -0
- package/docs/schemas/remote-worker-artifact.schema.json +17 -0
- package/docs/schemas/remote-worker-event.schema.json +18 -0
- package/docs/schemas/remote-worker-heartbeat.schema.json +37 -0
- package/docs/schemas/remote-worker-registration.schema.json +52 -0
- package/docs/schemas/result-bundle-index.schema.json +133 -0
- package/docs/schemas/run-context.schema.json +15 -0
- package/docs/schemas/run-environment-images.schema.json +39 -0
- package/docs/schemas/run-manifest.schema.json +4 -0
- package/docs/schemas/run-request.schema.json +4 -0
- package/docs/schemas/training-data-candidate.schema.json +46 -0
- package/docs/schemas/trajectory-analysis.schema.json +258 -0
- package/docs/schemas/trajectory-events-page.schema.json +40 -0
- package/docs/schemas/verifier-assessment.schema.json +77 -0
- package/docs/schemas/verifier-evidence.schema.json +276 -0
- package/integrations/harbor/hitch_benchmark.py +252 -0
- package/integrations/harbor/hitch_candidate_recycle.py +262 -0
- package/integrations/harbor/hitch_harbor_agent.py +698 -568
- package/integrations/harbor/hitch_harbor_environment.py +469 -0
- package/integrations/harbor/hitch_harbor_task_resources.py +275 -0
- package/integrations/harbor/hitch_harbor_verifier.py +83 -2
- package/integrations/harbor/hitch_phase_supervisor.py +452 -0
- package/integrations/harbor/hitch_tool_client.mjs +91 -0
- package/integrations/model-call/cli.js +57 -0
- 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
|