agent-hitch 0.2.8 → 0.2.10
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 +206 -137
- package/README.zh-CN.md +193 -119
- package/dist/scripts/canary-native-training.js +221 -0
- package/dist/scripts/canary-native-training.js.map +1 -0
- package/dist/scripts/canary-remote-training.js +246 -0
- package/dist/scripts/canary-remote-training.js.map +1 -0
- package/dist/scripts/canary-remote-verifier.js +124 -0
- package/dist/scripts/canary-remote-verifier.js.map +1 -0
- package/dist/scripts/canary-worker-host.js +113 -0
- package/dist/scripts/canary-worker-host.js.map +1 -0
- package/dist/scripts/canary-worker-peer.js +188 -0
- package/dist/scripts/canary-worker-peer.js.map +1 -0
- package/dist/scripts/check-architecture.js +4 -3
- package/dist/scripts/check-architecture.js.map +1 -1
- package/dist/src/adapters/catalog.js +2 -1
- package/dist/src/adapters/catalog.js.map +1 -1
- package/dist/src/adapters/providers/codex.js +65 -2
- package/dist/src/adapters/providers/codex.js.map +1 -1
- package/dist/src/adapters/providers/model-call.js +17 -1
- package/dist/src/adapters/providers/model-call.js.map +1 -1
- package/dist/src/adapters/providers/training-tool.js +29 -0
- package/dist/src/adapters/providers/training-tool.js.map +1 -0
- package/dist/src/artifacts/index.js +1 -1
- package/dist/src/artifacts/index.js.map +1 -1
- package/dist/src/artifacts/store.js +15 -10
- package/dist/src/artifacts/store.js.map +1 -1
- package/dist/src/backends/harbor/backend.js +24 -15
- package/dist/src/backends/harbor/backend.js.map +1 -1
- package/dist/src/backends/harbor/bridge-environment.js +12 -0
- package/dist/src/backends/harbor/bridge-environment.js.map +1 -0
- package/dist/src/backends/harbor/environment-config.js +2 -2
- package/dist/src/backends/harbor/environment-config.js.map +1 -1
- package/dist/src/backends/harbor/host-credential-helper.js +81 -0
- package/dist/src/backends/harbor/host-credential-helper.js.map +1 -0
- package/dist/src/backends/harbor/index.js +3 -0
- package/dist/src/backends/harbor/index.js.map +1 -1
- package/dist/src/backends/harbor/model-proxy-config.js +18 -1
- package/dist/src/backends/harbor/model-proxy-config.js.map +1 -1
- package/dist/src/backends/harbor/observation.js +61 -0
- package/dist/src/backends/harbor/observation.js.map +1 -0
- package/dist/src/backends/harbor/portable-regrade.js +150 -0
- package/dist/src/backends/harbor/portable-regrade.js.map +1 -0
- package/dist/src/backends/harbor/process.js +26 -5
- package/dist/src/backends/harbor/process.js.map +1 -1
- package/dist/src/backends/harbor/regrade.js +4 -1
- package/dist/src/backends/harbor/regrade.js.map +1 -1
- package/dist/src/backends/harbor/supervisor.js +22 -0
- package/dist/src/backends/harbor/supervisor.js.map +1 -1
- package/dist/src/backends/harbor/tools.js +2 -0
- package/dist/src/backends/harbor/tools.js.map +1 -1
- package/dist/src/backends/index.js +3 -0
- package/dist/src/backends/index.js.map +1 -1
- package/dist/src/cli/arguments.js +33 -0
- package/dist/src/cli/arguments.js.map +1 -1
- package/dist/src/cli/commands/capabilities.js +14 -1
- package/dist/src/cli/commands/capabilities.js.map +1 -1
- package/dist/src/cli/commands/daemon.js +19 -0
- package/dist/src/cli/commands/daemon.js.map +1 -1
- package/dist/src/cli/commands/eval.js +38 -10
- package/dist/src/cli/commands/eval.js.map +1 -1
- package/dist/src/cli/commands/local.js +130 -0
- package/dist/src/cli/commands/local.js.map +1 -0
- package/dist/src/cli/commands/model-node.js +26 -0
- package/dist/src/cli/commands/model-node.js.map +1 -0
- package/dist/src/cli/commands/models.js +72 -0
- package/dist/src/cli/commands/models.js.map +1 -0
- package/dist/src/cli/commands/run.js +36 -1
- package/dist/src/cli/commands/run.js.map +1 -1
- package/dist/src/cli/commands/training.js +32 -0
- package/dist/src/cli/commands/training.js.map +1 -0
- package/dist/src/cli/commands/worker.js +30 -2
- package/dist/src/cli/commands/worker.js.map +1 -1
- package/dist/src/cli/main.js +8 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/output.js +24 -2
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/control-plane/eval-records.js +2 -0
- package/dist/src/control-plane/eval-records.js.map +1 -1
- package/dist/src/control-plane/eval-recovery.js +7 -1
- package/dist/src/control-plane/eval-recovery.js.map +1 -1
- package/dist/src/control-plane/eval-scheduler.js +24 -50
- package/dist/src/control-plane/eval-scheduler.js.map +1 -1
- package/dist/src/control-plane/eval-submission-persist.js +30 -0
- package/dist/src/control-plane/eval-submission-persist.js.map +1 -0
- package/dist/src/control-plane/execution-observation-contract.js +60 -0
- package/dist/src/control-plane/execution-observation-contract.js.map +1 -0
- package/dist/src/control-plane/index.js +10 -0
- package/dist/src/control-plane/index.js.map +1 -1
- package/dist/src/control-plane/inference-manager.js +357 -0
- package/dist/src/control-plane/inference-manager.js.map +1 -0
- package/dist/src/control-plane/managed-gateway-receipt.js +26 -0
- package/dist/src/control-plane/managed-gateway-receipt.js.map +1 -0
- package/dist/src/control-plane/managed-service-recovery.js +158 -0
- package/dist/src/control-plane/managed-service-recovery.js.map +1 -0
- package/dist/src/control-plane/model-capture-planning.js +15 -1
- package/dist/src/control-plane/model-capture-planning.js.map +1 -1
- package/dist/src/control-plane/ordered-eval-control.js +221 -0
- package/dist/src/control-plane/ordered-eval-control.js.map +1 -0
- package/dist/src/control-plane/remote-backend-result.js +18 -0
- package/dist/src/control-plane/remote-backend-result.js.map +1 -0
- package/dist/src/control-plane/remote-execution-lease.js +15 -0
- package/dist/src/control-plane/remote-execution-lease.js.map +1 -0
- package/dist/src/control-plane/remote-execution-observation.js +108 -0
- package/dist/src/control-plane/remote-execution-observation.js.map +1 -0
- package/dist/src/control-plane/remote-model-routes.js +107 -0
- package/dist/src/control-plane/remote-model-routes.js.map +1 -0
- package/dist/src/control-plane/remote-observation-reporter.js +37 -0
- package/dist/src/control-plane/remote-observation-reporter.js.map +1 -0
- package/dist/src/control-plane/remote-offer-contract.js +48 -0
- package/dist/src/control-plane/remote-offer-contract.js.map +1 -0
- package/dist/src/control-plane/remote-rerun-journal.js +61 -0
- package/dist/src/control-plane/remote-rerun-journal.js.map +1 -0
- package/dist/src/control-plane/remote-result-transport.js +55 -9
- package/dist/src/control-plane/remote-result-transport.js.map +1 -1
- package/dist/src/control-plane/remote-verifier-import.js +52 -0
- package/dist/src/control-plane/remote-verifier-import.js.map +1 -0
- package/dist/src/control-plane/remote-verifier-result.js +61 -0
- package/dist/src/control-plane/remote-verifier-result.js.map +1 -0
- package/dist/src/control-plane/remote-verifier-source-transport.js +68 -0
- package/dist/src/control-plane/remote-verifier-source-transport.js.map +1 -0
- package/dist/src/control-plane/remote-work-coordinator.js +103 -47
- package/dist/src/control-plane/remote-work-coordinator.js.map +1 -1
- package/dist/src/control-plane/remote-work-inputs.js +26 -5
- package/dist/src/control-plane/remote-work-inputs.js.map +1 -1
- package/dist/src/control-plane/remote-work-recovery.js +123 -34
- package/dist/src/control-plane/remote-work-recovery.js.map +1 -1
- package/dist/src/control-plane/remote-worker-artifacts.js +10 -7
- package/dist/src/control-plane/remote-worker-artifacts.js.map +1 -1
- package/dist/src/control-plane/remote-worker-cleanup.js +104 -0
- package/dist/src/control-plane/remote-worker-cleanup.js.map +1 -0
- package/dist/src/control-plane/remote-worker-client.js +156 -14
- package/dist/src/control-plane/remote-worker-client.js.map +1 -1
- package/dist/src/control-plane/remote-worker-codecs.js +160 -0
- package/dist/src/control-plane/remote-worker-codecs.js.map +1 -0
- package/dist/src/control-plane/remote-worker-generation-recovery.js +67 -0
- package/dist/src/control-plane/remote-worker-generation-recovery.js.map +1 -0
- package/dist/src/control-plane/remote-worker-ownership.js +51 -0
- package/dist/src/control-plane/remote-worker-ownership.js.map +1 -0
- package/dist/src/control-plane/remote-worker-protocol.js +85 -119
- package/dist/src/control-plane/remote-worker-protocol.js.map +1 -1
- package/dist/src/control-plane/remote-worker-runner.js +95 -18
- package/dist/src/control-plane/remote-worker-runner.js.map +1 -1
- package/dist/src/control-plane/remote-workers.js +33 -5
- package/dist/src/control-plane/remote-workers.js.map +1 -1
- package/dist/src/control-plane/rerun-recovery.js +89 -0
- package/dist/src/control-plane/rerun-recovery.js.map +1 -0
- package/dist/src/control-plane/rerun-scheduler.js +70 -96
- package/dist/src/control-plane/rerun-scheduler.js.map +1 -1
- package/dist/src/control-plane/rerun-source.js +57 -0
- package/dist/src/control-plane/rerun-source.js.map +1 -0
- package/dist/src/control-plane/worker-model-relay.js +76 -0
- package/dist/src/control-plane/worker-model-relay.js.map +1 -0
- package/dist/src/controller-runtime/hash.js +3 -0
- package/dist/src/controller-runtime/hash.js.map +1 -1
- package/dist/src/controller-runtime/index.js +2 -0
- package/dist/src/controller-runtime/index.js.map +1 -1
- package/dist/src/controller-runtime/observation.js +29 -0
- package/dist/src/controller-runtime/observation.js.map +1 -0
- package/dist/src/controller-runtime/resident.js +22 -0
- package/dist/src/controller-runtime/resident.js.map +1 -0
- package/dist/src/daemon/auth.js +7 -2
- package/dist/src/daemon/auth.js.map +1 -1
- package/dist/src/daemon/client.js +34 -1
- package/dist/src/daemon/client.js.map +1 -1
- package/dist/src/daemon/execution-observation.js +38 -0
- package/dist/src/daemon/execution-observation.js.map +1 -0
- package/dist/src/daemon/http.js +57 -0
- package/dist/src/daemon/http.js.map +1 -0
- package/dist/src/daemon/inference-prepare.js +47 -0
- package/dist/src/daemon/inference-prepare.js.map +1 -0
- package/dist/src/daemon/scheduler.js +4 -1
- package/dist/src/daemon/scheduler.js.map +1 -1
- package/dist/src/daemon/server.js +80 -82
- package/dist/src/daemon/server.js.map +1 -1
- package/dist/src/daemon/worker-observation.js +34 -0
- package/dist/src/daemon/worker-observation.js.map +1 -0
- package/dist/src/daemon/worker-ownership.js +71 -0
- package/dist/src/daemon/worker-ownership.js.map +1 -0
- package/dist/src/daemon/worker-routes.js +83 -15
- package/dist/src/daemon/worker-routes.js.map +1 -1
- package/dist/src/domain/execution-observation.js +2 -0
- package/dist/src/domain/execution-observation.js.map +1 -0
- package/dist/src/domain/index.js +1 -0
- package/dist/src/domain/index.js.map +1 -1
- package/dist/src/domain/inference.js +2 -0
- package/dist/src/domain/inference.js.map +1 -0
- package/dist/src/domain/runs.js.map +1 -1
- package/dist/src/domain/training.js +2 -0
- package/dist/src/domain/training.js.map +1 -0
- package/dist/src/domain/validation.js +11 -0
- package/dist/src/domain/validation.js.map +1 -1
- package/dist/src/domain/verifier-source.js +2 -0
- package/dist/src/domain/verifier-source.js.map +1 -0
- package/dist/src/domain/worker-recovery.js +2 -0
- package/dist/src/domain/worker-recovery.js.map +1 -0
- package/dist/src/evals/eval-logical-plan.js +8 -1
- package/dist/src/evals/eval-logical-plan.js.map +1 -1
- package/dist/src/evals/execution-leases.js +15 -2
- package/dist/src/evals/execution-leases.js.map +1 -1
- package/dist/src/evals/execution-plan.js +11 -1
- package/dist/src/evals/execution-plan.js.map +1 -1
- package/dist/src/evals/harbor-bridge-error.js +9 -0
- package/dist/src/evals/harbor-bridge-error.js.map +1 -1
- package/dist/src/evals/index.js +19 -0
- package/dist/src/evals/index.js.map +1 -1
- package/dist/src/evals/model-capture-plan.js +12 -0
- package/dist/src/evals/model-capture-plan.js.map +1 -1
- package/dist/src/evals/model-capture-runtime.js +25 -0
- package/dist/src/evals/model-capture-runtime.js.map +1 -1
- package/dist/src/evals/physical-work-plan.js +57 -0
- package/dist/src/evals/physical-work-plan.js.map +1 -0
- package/dist/src/evals/preparation-rerun.js +6 -0
- package/dist/src/evals/preparation-rerun.js.map +1 -1
- package/dist/src/evals/recovery.js +8 -1
- package/dist/src/evals/recovery.js.map +1 -1
- package/dist/src/evals/regrade-evidence.js +4 -4
- package/dist/src/evals/regrade-evidence.js.map +1 -1
- package/dist/src/evals/remote-execution-ownership.js +50 -0
- package/dist/src/evals/remote-execution-ownership.js.map +1 -0
- package/dist/src/evals/remote-infrastructure-retry.js +2 -0
- package/dist/src/evals/remote-infrastructure-retry.js.map +1 -1
- package/dist/src/evals/remote-lease-release.js +58 -0
- package/dist/src/evals/remote-lease-release.js.map +1 -0
- package/dist/src/evals/remote-rerun-execution.js +85 -0
- package/dist/src/evals/remote-rerun-execution.js.map +1 -0
- package/dist/src/evals/remote-trial.js +6 -0
- package/dist/src/evals/remote-trial.js.map +1 -0
- package/dist/src/evals/remote-verifier-assessment.js +85 -0
- package/dist/src/evals/remote-verifier-assessment.js.map +1 -0
- package/dist/src/evals/remote-verifier-contract.js +43 -0
- package/dist/src/evals/remote-verifier-contract.js.map +1 -0
- package/dist/src/evals/remote-verifier-execution.js +105 -0
- package/dist/src/evals/remote-verifier-execution.js.map +1 -0
- package/dist/src/evals/remote-verifier-journal.js +21 -0
- package/dist/src/evals/remote-verifier-journal.js.map +1 -0
- package/dist/src/evals/remote-verifier-outcome.js +51 -0
- package/dist/src/evals/remote-verifier-outcome.js.map +1 -0
- package/dist/src/evals/remote-verifier-rerun.js +134 -0
- package/dist/src/evals/remote-verifier-rerun.js.map +1 -0
- package/dist/src/evals/remote-verifier-selection.js +61 -0
- package/dist/src/evals/remote-verifier-selection.js.map +1 -0
- package/dist/src/evals/remote-verifier-source.js +33 -0
- package/dist/src/evals/remote-verifier-source.js.map +1 -0
- package/dist/src/evals/remote-worker-cleanup.js +46 -0
- package/dist/src/evals/remote-worker-cleanup.js.map +1 -0
- package/dist/src/evals/request.js +58 -1
- package/dist/src/evals/request.js.map +1 -1
- package/dist/src/evals/rerun-completion.js +39 -0
- package/dist/src/evals/rerun-completion.js.map +1 -0
- package/dist/src/evals/rerun-inputs.js +54 -0
- package/dist/src/evals/rerun-inputs.js.map +1 -1
- package/dist/src/evals/rerun-resume.js +70 -0
- package/dist/src/evals/rerun-resume.js.map +1 -0
- package/dist/src/evals/rerun-state.js +25 -0
- package/dist/src/evals/rerun-state.js.map +1 -0
- package/dist/src/evals/rerun-types.js.map +1 -1
- package/dist/src/evals/rerun.js +94 -101
- package/dist/src/evals/rerun.js.map +1 -1
- package/dist/src/evals/service.js +59 -5
- package/dist/src/evals/service.js.map +1 -1
- package/dist/src/evals/verifier-only-rerun.js +2 -16
- package/dist/src/evals/verifier-only-rerun.js.map +1 -1
- package/dist/src/evals/verifier-rerun-result.js +24 -0
- package/dist/src/evals/verifier-rerun-result.js.map +1 -0
- package/dist/src/evals/verifier-source-import.js +48 -0
- package/dist/src/evals/verifier-source-import.js.map +1 -0
- package/dist/src/evals/verifier-source.js +139 -0
- package/dist/src/evals/verifier-source.js.map +1 -0
- package/dist/src/foundation/base64.js +19 -0
- package/dist/src/foundation/base64.js.map +1 -0
- package/dist/src/foundation/config.js +10 -0
- package/dist/src/foundation/config.js.map +1 -1
- package/dist/src/foundation/credential-redaction.js +22 -9
- package/dist/src/foundation/credential-redaction.js.map +1 -1
- package/dist/src/foundation/index.js +1 -0
- package/dist/src/foundation/index.js.map +1 -1
- package/dist/src/foundation/process-identity.js +13 -9
- package/dist/src/foundation/process-identity.js.map +1 -1
- package/dist/src/inference/daemon-capacity.js +41 -0
- package/dist/src/inference/daemon-capacity.js.map +1 -0
- package/dist/src/inference/device-reservation.js +58 -0
- package/dist/src/inference/device-reservation.js.map +1 -0
- package/dist/src/inference/doctor.js +95 -0
- package/dist/src/inference/doctor.js.map +1 -0
- package/dist/src/inference/generation-recovery.js +83 -0
- package/dist/src/inference/generation-recovery.js.map +1 -0
- package/dist/src/inference/index.js +24 -0
- package/dist/src/inference/index.js.map +1 -0
- package/dist/src/inference/lock.js +331 -0
- package/dist/src/inference/lock.js.map +1 -0
- package/dist/src/inference/managed-launcher.js +35 -0
- package/dist/src/inference/managed-launcher.js.map +1 -0
- package/dist/src/inference/manifest.js +262 -0
- package/dist/src/inference/manifest.js.map +1 -0
- package/dist/src/inference/materialize.js +66 -0
- package/dist/src/inference/materialize.js.map +1 -0
- package/dist/src/inference/model-location.js +34 -0
- package/dist/src/inference/model-location.js.map +1 -0
- package/dist/src/inference/model-store.js +439 -0
- package/dist/src/inference/model-store.js.map +1 -0
- package/dist/src/inference/node-client.js +167 -0
- package/dist/src/inference/node-client.js.map +1 -0
- package/dist/src/inference/node-registry.js +79 -0
- package/dist/src/inference/node-registry.js.map +1 -0
- package/dist/src/inference/observation.js +79 -0
- package/dist/src/inference/observation.js.map +1 -0
- package/dist/src/inference/preflight.js +132 -0
- package/dist/src/inference/preflight.js.map +1 -0
- package/dist/src/inference/process-sglang.js +169 -0
- package/dist/src/inference/process-sglang.js.map +1 -0
- package/dist/src/inference/protocol-probe.js +29 -0
- package/dist/src/inference/protocol-probe.js.map +1 -0
- package/dist/src/inference/remote-model-store.js +24 -0
- package/dist/src/inference/remote-model-store.js.map +1 -0
- package/dist/src/inference/runtime-catalog.js +41 -0
- package/dist/src/inference/runtime-catalog.js.map +1 -0
- package/dist/src/inference/runtime-store.js +93 -0
- package/dist/src/inference/runtime-store.js.map +1 -0
- package/dist/src/inference/service-attachment.js +46 -0
- package/dist/src/inference/service-attachment.js.map +1 -0
- package/dist/src/inference/service-inspection.js +38 -0
- package/dist/src/inference/service-inspection.js.map +1 -0
- package/dist/src/inference/sglang.js +279 -0
- package/dist/src/inference/sglang.js.map +1 -0
- package/dist/src/inference/supervisor.js +460 -0
- package/dist/src/inference/supervisor.js.map +1 -0
- package/dist/src/model-access/environment.js +28 -0
- package/dist/src/model-access/environment.js.map +1 -0
- package/dist/src/model-access/index.js +4 -0
- package/dist/src/model-access/index.js.map +1 -1
- package/dist/src/model-access/local-gateway-request.js +139 -0
- package/dist/src/model-access/local-gateway-request.js.map +1 -0
- package/dist/src/model-access/local-gateway.js +323 -0
- package/dist/src/model-access/local-gateway.js.map +1 -0
- package/dist/src/model-access/proxy.js +90 -5
- package/dist/src/model-access/proxy.js.map +1 -1
- package/dist/src/model-access/remote-model.js +58 -0
- package/dist/src/model-access/remote-model.js.map +1 -0
- package/dist/src/model-access/training.js +115 -0
- package/dist/src/model-access/training.js.map +1 -0
- package/dist/src/runs/adapter-process.js +2 -2
- package/dist/src/runs/adapter-process.js.map +1 -1
- package/dist/src/runs/executor-types.js +2 -0
- package/dist/src/runs/executor-types.js.map +1 -0
- package/dist/src/runs/executor.js +54 -34
- package/dist/src/runs/executor.js.map +1 -1
- package/dist/src/runs/index.js +1 -0
- package/dist/src/runs/index.js.map +1 -1
- package/dist/src/runs/local-inference-environment.js +59 -0
- package/dist/src/runs/local-inference-environment.js.map +1 -0
- package/dist/src/runs/local-inference-run.js +53 -0
- package/dist/src/runs/local-inference-run.js.map +1 -0
- package/dist/src/runs/outcome.js +22 -0
- package/dist/src/runs/outcome.js.map +1 -1
- package/dist/src/runs/records.js +9 -0
- package/dist/src/runs/records.js.map +1 -1
- package/dist/src/runs/request.js +58 -3
- package/dist/src/runs/request.js.map +1 -1
- package/dist/src/runs/training-evidence.js +22 -0
- package/dist/src/runs/training-evidence.js.map +1 -0
- package/dist/src/runs/training-runtime.js +20 -0
- package/dist/src/runs/training-runtime.js.map +1 -0
- package/dist/src/workers/execution-observation.js +32 -0
- package/dist/src/workers/execution-observation.js.map +1 -0
- package/dist/src/workers/index.js +2 -0
- package/dist/src/workers/index.js.map +1 -1
- package/dist/src/workers/remote-harbor-ownership.js +266 -0
- package/dist/src/workers/remote-harbor-ownership.js.map +1 -0
- package/dist/src/workers/remote-harbor-verifier.js +76 -0
- package/dist/src/workers/remote-harbor-verifier.js.map +1 -0
- package/dist/src/workers/remote-harbor-work-spec.js +66 -10
- package/dist/src/workers/remote-harbor-work-spec.js.map +1 -1
- package/dist/src/workers/remote-harbor-worker.js +56 -49
- package/dist/src/workers/remote-harbor-worker.js.map +1 -1
- package/dist/src/workers/remote-worker-host.js +33 -0
- package/dist/src/workers/remote-worker-host.js.map +1 -0
- package/docs/local-model-inference-spec.zh-CN.md +666 -0
- package/docs/schemas/controller-runtime-observation.schema.json +21 -0
- package/docs/schemas/eval-execution-plan.schema.json +1 -0
- package/docs/schemas/eval-request.schema.json +3 -1
- package/docs/schemas/execution-lease.schema.json +95 -1
- package/docs/schemas/execution-provider-status.schema.json +162 -28
- package/docs/schemas/inference-execution-evidence.schema.json +487 -0
- package/docs/schemas/inference-lock.schema.json +487 -0
- package/docs/schemas/inference-runtime-manifest.schema.json +156 -0
- package/docs/schemas/inference-service-record.schema.json +227 -0
- package/docs/schemas/local-inference-selection.schema.json +98 -0
- package/docs/schemas/local-model-manifest.schema.json +43 -0
- package/docs/schemas/model-node-binding.schema.json +34 -0
- package/docs/schemas/model-node-generation-release.schema.json +46 -0
- package/docs/schemas/ordered-eval-control.schema.json +39 -0
- package/docs/schemas/remote-execution-lease-grant.schema.json +45 -0
- package/docs/schemas/remote-harbor-work-spec.schema.json +325 -0
- package/docs/schemas/remote-model-binding.schema.json +21 -0
- package/docs/schemas/remote-physical-execution.schema.json +108 -0
- package/docs/schemas/remote-result-envelope.schema.json +9 -3
- package/docs/schemas/remote-tree-envelope.schema.json +17 -3
- package/docs/schemas/remote-verifier-result.schema.json +203 -0
- package/docs/schemas/remote-verifier-source.schema.json +52 -0
- package/docs/schemas/remote-verifier-work.schema.json +125 -0
- package/docs/schemas/remote-work-offer.schema.json +170 -34
- package/docs/schemas/remote-worker-execution-admission.schema.json +214 -0
- package/docs/schemas/remote-worker-generation-cleanup.schema.json +199 -0
- package/docs/schemas/remote-worker-registration.schema.json +226 -25
- package/docs/schemas/run-manifest.schema.json +211 -44
- package/docs/schemas/run-request.schema.json +2 -1
- package/docs/schemas/training-external-binding.schema.json +26 -0
- package/integrations/harbor/hitch_harbor_agent.py +366 -38
- package/integrations/harbor/hitch_harbor_environment.py +22 -1
- package/integrations/harbor/hitch_host_credentials.py +354 -0
- package/integrations/harbor/hitch_private_exec.py +150 -0
- package/integrations/model-call/cli.js +13 -5
- package/integrations/training-tool/cli.js +72 -0
- package/package.json +8 -2
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import hashlib
|
|
7
7
|
import json
|
|
8
|
+
import math
|
|
8
9
|
import os
|
|
9
10
|
import re
|
|
10
11
|
import secrets
|
|
@@ -127,6 +128,7 @@ class HitchHarborAgent(BaseAgent):
|
|
|
127
128
|
verifier_identity: str | None = None,
|
|
128
129
|
logical_attempt: int | None = None,
|
|
129
130
|
model_capture: dict[str, Any] | None = None,
|
|
131
|
+
managed_local_inference: dict[str, Any] | None = None,
|
|
130
132
|
**kwargs: Any,
|
|
131
133
|
) -> None:
|
|
132
134
|
super().__init__(logs_dir=logs_dir, **kwargs)
|
|
@@ -165,6 +167,14 @@ class HitchHarborAgent(BaseAgent):
|
|
|
165
167
|
raise ValueError("logical_attempt must be a positive integer")
|
|
166
168
|
self.logical_attempt = logical_attempt
|
|
167
169
|
self.model_capture = _validate_model_capture(model_capture)
|
|
170
|
+
if managed_local_inference is not None and not _valid_managed_model_identity(managed_local_inference):
|
|
171
|
+
raise ValueError("managed_local_inference identity is invalid")
|
|
172
|
+
if managed_local_inference is not None and self.model_capture is None:
|
|
173
|
+
raise ValueError("managed_local_inference requires model_capture")
|
|
174
|
+
self.managed_local_inference = dict(managed_local_inference) if managed_local_inference else None
|
|
175
|
+
capture_inference = self.model_capture.get("managed_inference") if self.model_capture else None
|
|
176
|
+
if capture_inference != self.managed_local_inference:
|
|
177
|
+
raise ValueError("managed_local_inference must match model_capture identity")
|
|
168
178
|
self._hitch_version: str | None = None
|
|
169
179
|
self._entrypoint: str | None = None
|
|
170
180
|
self._artifact_manifest: dict[str, Any] | None = None
|
|
@@ -241,8 +251,7 @@ class HitchHarborAgent(BaseAgent):
|
|
|
241
251
|
# bookkeeping and is not identity (spec §4.2).
|
|
242
252
|
await environment.upload_dir(payload_dir, "/opt/hitch")
|
|
243
253
|
await self._ensure_node(environment)
|
|
244
|
-
platform = await self.
|
|
245
|
-
node_version = await self._container_node_version(environment)
|
|
254
|
+
platform, node_version = await self._container_node_identity(environment)
|
|
246
255
|
if self._artifact_manifest is None or not self._artifact_compatible(
|
|
247
256
|
self._artifact_manifest, platform, node_version
|
|
248
257
|
):
|
|
@@ -447,22 +456,43 @@ class HitchHarborAgent(BaseAgent):
|
|
|
447
456
|
raise RuntimeError(f"{label} exceeds the size limit ({maximum} bytes)")
|
|
448
457
|
return info
|
|
449
458
|
|
|
450
|
-
async def
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
459
|
+
async def _container_node_identity(self, environment: BaseEnvironment) -> tuple[str, str]:
|
|
460
|
+
# Harbor may merge stderr into stdout. Frame only the machine value so
|
|
461
|
+
# startup warnings cannot be mistaken for the platform or version.
|
|
462
|
+
marker = "__HITCH_NODE_IDENTITY__"
|
|
463
|
+
script = (
|
|
464
|
+
f"process.stdout.write('\\n{marker}' + process.platform + '-' + "
|
|
465
|
+
"process.arch + ' ' + process.version + '\\n')"
|
|
454
466
|
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
command = f"{self._node_prefix()} node -e {shlex.quote(script)}"
|
|
468
|
+
result = await environment.exec(command)
|
|
469
|
+
lines = [line for line in (result.stdout or "").splitlines() if line.startswith(marker)]
|
|
470
|
+
identity = re.fullmatch(
|
|
471
|
+
re.escape(marker) + r"((?:linux|darwin|win32)-[a-z0-9_]+) (v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)",
|
|
472
|
+
lines[0],
|
|
473
|
+
) if len(lines) == 1 else None
|
|
474
|
+
if result.return_code == 0 and identity is not None:
|
|
475
|
+
return identity.group(1), identity.group(2)
|
|
476
|
+
|
|
477
|
+
reason = f"exit={result.return_code}" if result.return_code != 0 else "expected one valid identity marker"
|
|
478
|
+
failure = self._node_runtime_error(
|
|
479
|
+
"hitch_node_runtime_identity_invalid",
|
|
480
|
+
f"container returned an invalid Node.js identity ({reason}); "
|
|
481
|
+
f"stdout={self._bounded_tail(result.stdout or '', 512)!r}; "
|
|
482
|
+
f"stderr={self._bounded_tail(result.stderr or '', 512)!r}",
|
|
483
|
+
)
|
|
484
|
+
failure.evidence["probe"] = {
|
|
485
|
+
"command": command,
|
|
486
|
+
"return_code": result.return_code,
|
|
487
|
+
"stdout_tail": self._bounded_tail(result.stdout or ""),
|
|
488
|
+
"stderr_tail": self._bounded_tail(result.stderr or ""),
|
|
489
|
+
}
|
|
490
|
+
self._record_node_runtime({"source": "failed", "bin_directory": self._node_bin_directory, **failure.evidence})
|
|
491
|
+
try:
|
|
492
|
+
await self._write_bridge_error(environment, failure.evidence)
|
|
493
|
+
except Exception:
|
|
494
|
+
pass # Keep the probe failure if the container cannot export logs.
|
|
495
|
+
raise failure
|
|
466
496
|
|
|
467
497
|
@staticmethod
|
|
468
498
|
def _artifact_compatible(manifest: dict[str, Any], platform: str, node_version: str) -> bool:
|
|
@@ -864,22 +894,43 @@ class HitchHarborAgent(BaseAgent):
|
|
|
864
894
|
if timeout_ms <= 0:
|
|
865
895
|
raise RuntimeError("candidate whole-task budget expired during phase binding/upload")
|
|
866
896
|
elif session:
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
"schema_version": "hitch-agent-budget@1", "run_id": run_id,
|
|
871
|
-
"task_budget_ms": task_budget_ms, "preparation_ms": preparation_ms,
|
|
872
|
-
"hitch_timeout_ms": max(0, timeout_ms),
|
|
873
|
-
"collection_timeout_ms": session.config["profile"]["budget"]["collection_timeout_ms"],
|
|
874
|
-
"scope": "invocation-budget-and-collection-allowance",
|
|
875
|
-
}))
|
|
897
|
+
timeout_ms = self._record_session_budget(
|
|
898
|
+
session, run_id, invocation_started_ns, task_budget_ms
|
|
899
|
+
)
|
|
876
900
|
if timeout_ms <= 0:
|
|
877
901
|
raise RuntimeError("candidate budget expired during input preparation; no model was launched")
|
|
902
|
+
private_environment, native_deadline_ns = await self._host_task_credentials(
|
|
903
|
+
environment,
|
|
904
|
+
context,
|
|
905
|
+
run_id,
|
|
906
|
+
timeout_ms,
|
|
907
|
+
invocation_started_ns=(
|
|
908
|
+
invocation_started_ns
|
|
909
|
+
if prepared_phase is None and session is None
|
|
910
|
+
else None
|
|
911
|
+
),
|
|
912
|
+
)
|
|
913
|
+
if private_environment is not None:
|
|
914
|
+
if prepared_phase is not None:
|
|
915
|
+
timeout_ms = (prepared_phase.deadline_ns - time.monotonic_ns()) // 1_000_000
|
|
916
|
+
if native_deadline_ns is not None:
|
|
917
|
+
native_remaining_ms = (native_deadline_ns - time.monotonic_ns()) // 1_000_000
|
|
918
|
+
if native_remaining_ms <= 0:
|
|
919
|
+
raise RuntimeError("candidate native Harbor budget expired during host credential preparation; no model was launched")
|
|
920
|
+
elif timeout_ms <= 0:
|
|
921
|
+
raise RuntimeError("candidate budget expired during host credential preparation; no model was launched")
|
|
922
|
+
if prepared_phase is None and session:
|
|
923
|
+
timeout_ms = self._record_session_budget(
|
|
924
|
+
session, run_id, invocation_started_ns, task_budget_ms
|
|
925
|
+
)
|
|
926
|
+
if timeout_ms <= 0:
|
|
927
|
+
stage = "host credential preparation" if private_environment is not None else "input preparation"
|
|
928
|
+
raise RuntimeError(f"candidate budget expired during {stage}; no model was launched")
|
|
878
929
|
arguments = [
|
|
879
930
|
self._node_prefix(),
|
|
880
931
|
"HITCH_ROOT=/tmp/hitch-state",
|
|
881
932
|
*proxy_environment,
|
|
882
|
-
|
|
933
|
+
"HITCH_HARBOR_INTERNAL=1",
|
|
883
934
|
f"node {entry} run",
|
|
884
935
|
"--harness",
|
|
885
936
|
shlex.quote(self.harness_ref),
|
|
@@ -896,11 +947,11 @@ class HitchHarborAgent(BaseAgent):
|
|
|
896
947
|
str(timeout_ms),
|
|
897
948
|
"--output",
|
|
898
949
|
"jsonl",
|
|
950
|
+
"--internal-run-id", run_id,
|
|
899
951
|
]
|
|
900
952
|
if parent_payload is not None:
|
|
901
953
|
arguments.extend([
|
|
902
954
|
"--parent-file", "/tmp/hitch-parent.json",
|
|
903
|
-
"--internal-run-id", run_id,
|
|
904
955
|
])
|
|
905
956
|
if prepared_phase is None:
|
|
906
957
|
arguments.append("--internal-defer-benchmark-observation")
|
|
@@ -912,13 +963,22 @@ class HitchHarborAgent(BaseAgent):
|
|
|
912
963
|
arguments.extend(["--agent-arg", shlex.quote(value)])
|
|
913
964
|
for name in self.credential_names:
|
|
914
965
|
arguments.extend(["--internal-credential-name", shlex.quote(name)])
|
|
915
|
-
command = (
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
966
|
+
command = self._logged_run_command(" ".join(arguments))
|
|
967
|
+
try:
|
|
968
|
+
if private_environment is None:
|
|
969
|
+
execution = await environment.exec(command, cwd=workdir)
|
|
970
|
+
else:
|
|
971
|
+
execution = await environment.hitch_exec_with_private_env(
|
|
972
|
+
command,
|
|
973
|
+
cwd=workdir,
|
|
974
|
+
env=private_environment,
|
|
975
|
+
)
|
|
976
|
+
except (Exception, asyncio.CancelledError):
|
|
977
|
+
# Harbor cancels this await on its agent deadline. Collect what is
|
|
978
|
+
# already durable before it removes the container, without turning
|
|
979
|
+
# a cancelled execution into a successful observation.
|
|
980
|
+
await self._preserve_interrupted_run(environment, assigned_run_id)
|
|
981
|
+
raise
|
|
922
982
|
collection = self._collect_run(
|
|
923
983
|
environment, context, execution, assigned_run_id=assigned_run_id,
|
|
924
984
|
context_payload=context_payload, parent_payload=parent_payload, prepared_phase=prepared_phase,
|
|
@@ -937,6 +997,231 @@ class HitchHarborAgent(BaseAgent):
|
|
|
937
997
|
(self.logs_dir / "hitch-collection-timeout.json").write_text(json.dumps(receipt))
|
|
938
998
|
raise RuntimeError("hitch_run_collection_timeout: terminal evidence export exceeded its allowance") from error
|
|
939
999
|
|
|
1000
|
+
async def _host_task_credentials(
|
|
1001
|
+
self,
|
|
1002
|
+
environment: BaseEnvironment,
|
|
1003
|
+
context: AgentContext,
|
|
1004
|
+
run_id: str,
|
|
1005
|
+
remaining_timeout_ms: int,
|
|
1006
|
+
*,
|
|
1007
|
+
invocation_started_ns: int | None,
|
|
1008
|
+
) -> tuple[dict[str, str] | None, int | None]:
|
|
1009
|
+
# Preserve the original single-file bridge behavior when this optional
|
|
1010
|
+
# host feature is not configured. An empty value remains configured and
|
|
1011
|
+
# reaches the strict parser below.
|
|
1012
|
+
if "HITCH_HOST_CREDENTIAL_HELPER_JSON" not in os.environ:
|
|
1013
|
+
return None, None
|
|
1014
|
+
from hitch_host_credentials import (
|
|
1015
|
+
HOST_CREDENTIAL_VALIDITY_MARGIN_MS,
|
|
1016
|
+
HostCredentialHelperError,
|
|
1017
|
+
load_host_credential_helper,
|
|
1018
|
+
prepare_host_credentials,
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
try:
|
|
1022
|
+
config = load_host_credential_helper(self.credential_names)
|
|
1023
|
+
if config is None:
|
|
1024
|
+
return None, None
|
|
1025
|
+
if not callable(getattr(environment, "hitch_exec_with_private_env", None)):
|
|
1026
|
+
raise HostCredentialHelperError(
|
|
1027
|
+
"host_credential_transport_unsupported",
|
|
1028
|
+
"Harbor environment does not support private Target credentials",
|
|
1029
|
+
)
|
|
1030
|
+
native_deadline_ns: int | None = None
|
|
1031
|
+
if invocation_started_ns is not None and remaining_timeout_ms <= 0:
|
|
1032
|
+
native_timeout_ms = self._native_harbor_agent_timeout_ms(environment)
|
|
1033
|
+
if native_timeout_ms is None:
|
|
1034
|
+
raise HostCredentialHelperError(
|
|
1035
|
+
"host_credential_helper_request_invalid",
|
|
1036
|
+
"native Harbor agent timeout is not finite",
|
|
1037
|
+
)
|
|
1038
|
+
native_deadline_ns = invocation_started_ns + native_timeout_ms * 1_000_000
|
|
1039
|
+
native_remaining_ms = (native_deadline_ns - time.monotonic_ns()) // 1_000_000
|
|
1040
|
+
if native_remaining_ms <= 0:
|
|
1041
|
+
raise RuntimeError("candidate native Harbor budget expired during input preparation; no model was launched")
|
|
1042
|
+
remaining_timeout_ms = native_remaining_ms
|
|
1043
|
+
credentials = await prepare_host_credentials(
|
|
1044
|
+
config,
|
|
1045
|
+
remaining_timeout_ms + HOST_CREDENTIAL_VALIDITY_MARGIN_MS,
|
|
1046
|
+
)
|
|
1047
|
+
return credentials, native_deadline_ns
|
|
1048
|
+
except HostCredentialHelperError as error:
|
|
1049
|
+
trial_id, task_id, attempt = self._trial_identity()
|
|
1050
|
+
evidence = {
|
|
1051
|
+
"schema_version": "1",
|
|
1052
|
+
"code": error.code,
|
|
1053
|
+
"message": error.message,
|
|
1054
|
+
"recorded_at": datetime.now(timezone.utc).isoformat(),
|
|
1055
|
+
"scope": "host-task-credential-helper",
|
|
1056
|
+
"eval_id": self.eval_id,
|
|
1057
|
+
"trial_id": trial_id,
|
|
1058
|
+
"task_id": task_id,
|
|
1059
|
+
"attempt": attempt,
|
|
1060
|
+
"assigned_run_id": run_id,
|
|
1061
|
+
}
|
|
1062
|
+
if context.metadata is None:
|
|
1063
|
+
context.metadata = {}
|
|
1064
|
+
context.metadata["hitch_bridge_error_code"] = error.code
|
|
1065
|
+
context.metadata["hitch_bridge_error_artifact"] = "hitch-bridge-error.json"
|
|
1066
|
+
try:
|
|
1067
|
+
await self._write_bridge_error(environment, evidence)
|
|
1068
|
+
except Exception:
|
|
1069
|
+
pass
|
|
1070
|
+
raise HitchBridgeError(error.code, error.message, evidence) from None
|
|
1071
|
+
|
|
1072
|
+
def _native_harbor_agent_timeout_ms(self, environment: BaseEnvironment) -> int | None:
|
|
1073
|
+
"""Resolve Harbor 0.21's effective native agent timeout from its own inputs."""
|
|
1074
|
+
try:
|
|
1075
|
+
from harbor.models.task.config import TaskConfig
|
|
1076
|
+
from harbor.models.trial.config import TrialConfig
|
|
1077
|
+
|
|
1078
|
+
environment_dir = Path(getattr(environment, "environment_dir"))
|
|
1079
|
+
trial_paths = getattr(environment, "trial_paths")
|
|
1080
|
+
config_path = Path(getattr(trial_paths, "config_path"))
|
|
1081
|
+
task_config = TaskConfig.model_validate_toml(
|
|
1082
|
+
(environment_dir.parent / "task.toml").read_text(encoding="utf-8")
|
|
1083
|
+
)
|
|
1084
|
+
trial_config = TrialConfig.model_validate_json(
|
|
1085
|
+
config_path.read_text(encoding="utf-8")
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
def resolve(default_timeout_sec: float | None) -> float | None:
|
|
1089
|
+
base_timeout_sec = trial_config.agent.override_timeout_sec or default_timeout_sec
|
|
1090
|
+
if base_timeout_sec is None:
|
|
1091
|
+
return None
|
|
1092
|
+
multiplier = trial_config.agent_timeout_multiplier
|
|
1093
|
+
if multiplier is None:
|
|
1094
|
+
multiplier = trial_config.timeout_multiplier
|
|
1095
|
+
return min(
|
|
1096
|
+
base_timeout_sec,
|
|
1097
|
+
trial_config.agent.max_timeout_sec or float("inf"),
|
|
1098
|
+
) * multiplier
|
|
1099
|
+
|
|
1100
|
+
if task_config.steps:
|
|
1101
|
+
resolved = [
|
|
1102
|
+
resolve(
|
|
1103
|
+
step.agent.timeout_sec
|
|
1104
|
+
if step.agent.timeout_sec is not None
|
|
1105
|
+
else task_config.agent.timeout_sec
|
|
1106
|
+
)
|
|
1107
|
+
for step in task_config.steps
|
|
1108
|
+
]
|
|
1109
|
+
# Harbor does not identify the current step in BaseAgent.run().
|
|
1110
|
+
# Requiring the largest finite step budget prevents a credential
|
|
1111
|
+
# from expiring during any native step without guessing order.
|
|
1112
|
+
timeout_sec = None if any(value is None for value in resolved) else max(resolved)
|
|
1113
|
+
else:
|
|
1114
|
+
timeout_sec = resolve(task_config.agent.timeout_sec)
|
|
1115
|
+
if timeout_sec is None:
|
|
1116
|
+
return None
|
|
1117
|
+
timeout_ms = math.ceil(timeout_sec * 1_000)
|
|
1118
|
+
if not math.isfinite(timeout_sec) or not 1 <= timeout_ms <= 9_007_199_254_740_991:
|
|
1119
|
+
raise ValueError("native Harbor timeout is outside the supported range")
|
|
1120
|
+
return timeout_ms
|
|
1121
|
+
except Exception:
|
|
1122
|
+
# Task/config contents and host paths are not safe diagnostic text.
|
|
1123
|
+
from hitch_host_credentials import HostCredentialHelperError
|
|
1124
|
+
raise HostCredentialHelperError(
|
|
1125
|
+
"host_credential_helper_request_invalid",
|
|
1126
|
+
"native Harbor agent timeout could not be resolved",
|
|
1127
|
+
) from None
|
|
1128
|
+
|
|
1129
|
+
def _record_session_budget(
|
|
1130
|
+
self,
|
|
1131
|
+
session,
|
|
1132
|
+
run_id: str,
|
|
1133
|
+
invocation_started_ns: int,
|
|
1134
|
+
task_budget_ms: int,
|
|
1135
|
+
) -> int:
|
|
1136
|
+
preparation_ms = (time.monotonic_ns() - invocation_started_ns) // 1_000_000
|
|
1137
|
+
timeout_ms = task_budget_ms - preparation_ms
|
|
1138
|
+
(self.logs_dir / "hitch-agent-budget.json").write_text(json.dumps({
|
|
1139
|
+
"schema_version": "hitch-agent-budget@1", "run_id": run_id,
|
|
1140
|
+
"task_budget_ms": task_budget_ms, "preparation_ms": preparation_ms,
|
|
1141
|
+
"hitch_timeout_ms": max(0, timeout_ms),
|
|
1142
|
+
"collection_timeout_ms": session.config["profile"]["budget"]["collection_timeout_ms"],
|
|
1143
|
+
"scope": "invocation-budget-and-collection-allowance",
|
|
1144
|
+
}))
|
|
1145
|
+
return timeout_ms
|
|
1146
|
+
|
|
1147
|
+
@staticmethod
|
|
1148
|
+
def _logged_run_command(invocation: str) -> str:
|
|
1149
|
+
# Bash process substitution leaves an extra pipe FD in the harness and
|
|
1150
|
+
# its descendants. A task's background service may outlive the harness.
|
|
1151
|
+
# Regular files keep live logs without tying exec completion to that FD.
|
|
1152
|
+
return (
|
|
1153
|
+
"hitch_run_exit=0; " + invocation
|
|
1154
|
+
+ " > /logs/agent/hitch-events.jsonl 2> /logs/agent/hitch-stderr.log || hitch_run_exit=$?; "
|
|
1155
|
+
+ "cat /logs/agent/hitch-events.jsonl || exit $?; "
|
|
1156
|
+
+ "cat /logs/agent/hitch-stderr.log >&2 || exit $?; "
|
|
1157
|
+
+ 'exit "$hitch_run_exit"'
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
async def _preserve_interrupted_run(self, environment: BaseEnvironment, run_id: str) -> None:
|
|
1161
|
+
# This is a diagnostic directory, never a complete/sealed run bundle.
|
|
1162
|
+
# runtime-home contains credentials and must not be copied. Only files
|
|
1163
|
+
# already produced by Hitch's redacted evidence writers are eligible.
|
|
1164
|
+
destination = f"/logs/agent/hitch-interrupted-run-{uuid.uuid4().hex}"
|
|
1165
|
+
script = r"""
|
|
1166
|
+
import {lstat,mkdir,open,readFile,writeFile} from 'node:fs/promises';
|
|
1167
|
+
import {constants} from 'node:fs';
|
|
1168
|
+
import path from 'node:path';
|
|
1169
|
+
const [source,target]=process.argv.slice(1);
|
|
1170
|
+
let remaining=64*1024*1024;
|
|
1171
|
+
const copied=[];
|
|
1172
|
+
await mkdir(target,{mode:0o700});
|
|
1173
|
+
async function copy(relative){
|
|
1174
|
+
if(copied.includes(relative))return;
|
|
1175
|
+
if(typeof relative!=='string'||relative.includes('\\')||path.isAbsolute(relative)
|
|
1176
|
+
||relative.split('/').some(p=>!p||p==='.'||p==='..')) return;
|
|
1177
|
+
const file=path.join(source,relative);
|
|
1178
|
+
try {
|
|
1179
|
+
for(let p=path.dirname(file);p!=='/';p=path.dirname(p)) {
|
|
1180
|
+
const info=await lstat(p); if(!info.isDirectory()||info.isSymbolicLink()) return;
|
|
1181
|
+
}
|
|
1182
|
+
if(!(await lstat(file)).isFile())return;
|
|
1183
|
+
const input=await open(file,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);
|
|
1184
|
+
try {
|
|
1185
|
+
const info=await input.stat();
|
|
1186
|
+
if(!info.isFile()||info.nlink!==1||info.size>remaining) return;
|
|
1187
|
+
const bytes=Buffer.alloc(info.size);
|
|
1188
|
+
let offset=0;
|
|
1189
|
+
while(offset<bytes.length){const r=await input.read(bytes,offset,bytes.length-offset,offset);if(!r.bytesRead)break;offset+=r.bytesRead;}
|
|
1190
|
+
remaining-=offset;
|
|
1191
|
+
const output=path.join(target,relative);
|
|
1192
|
+
await mkdir(path.dirname(output),{recursive:true,mode:0o700});
|
|
1193
|
+
await writeFile(output,bytes.subarray(0,offset),{flag:'wx',mode:0o600});
|
|
1194
|
+
copied.push(relative);
|
|
1195
|
+
} finally {await input.close();}
|
|
1196
|
+
} catch(e) {if(!['ENOENT','ELOOP','ENOTDIR'].includes(e.code))throw e;}
|
|
1197
|
+
}
|
|
1198
|
+
for(const name of ['result.json','manifest.json','events.jsonl','stdout.log','stderr.log','trajectory.ref.json']) await copy(name);
|
|
1199
|
+
if(copied.includes('trajectory.ref.json')) {
|
|
1200
|
+
try {
|
|
1201
|
+
const ref=JSON.parse(await readFile(path.join(target,'trajectory.ref.json'),'utf8'));
|
|
1202
|
+
const files=Array.isArray(ref.files)?ref.files.map(f=>f?.path):[ref.path];
|
|
1203
|
+
for(const file of files.slice(0,256)) if(typeof file==='string'&&file.startsWith('trajectory/'))await copy(file);
|
|
1204
|
+
} catch(e) {if(!(e instanceof SyntaxError))throw e;}
|
|
1205
|
+
}
|
|
1206
|
+
// ProviderCaptureWriter persists redacted output before finalization creates the reference.
|
|
1207
|
+
for(const name of ['trajectory/provider/events.jsonl','trajectory/provider/transcript.txt']) await copy(name);
|
|
1208
|
+
await writeFile(path.join(target,'diagnostic.json'),JSON.stringify({complete:false,copied}),{flag:'wx',mode:0o600});
|
|
1209
|
+
""".strip()
|
|
1210
|
+
receipt: dict[str, Any] = {"run_id": run_id, "complete": False, "directory": destination}
|
|
1211
|
+
try:
|
|
1212
|
+
result = await asyncio.wait_for(environment.exec(
|
|
1213
|
+
f"{self._node_prefix()} node --input-type=module -e {shlex.quote(script)} "
|
|
1214
|
+
f"{shlex.quote(f'/tmp/hitch-state/runs/{run_id}')} {shlex.quote(destination)}"
|
|
1215
|
+
), timeout=5)
|
|
1216
|
+
receipt["export_return_code"] = result.return_code
|
|
1217
|
+
except (Exception, asyncio.CancelledError) as error:
|
|
1218
|
+
# Preserve the original exception, not a best-effort export failure.
|
|
1219
|
+
receipt["export_error"] = type(error).__name__
|
|
1220
|
+
try:
|
|
1221
|
+
(self.logs_dir / "hitch-interrupted-run.json").write_text(json.dumps(receipt) + "\n")
|
|
1222
|
+
except OSError:
|
|
1223
|
+
pass
|
|
1224
|
+
|
|
940
1225
|
async def _collect_run(
|
|
941
1226
|
self, environment: BaseEnvironment, context: AgentContext, execution: ExecResult, *,
|
|
942
1227
|
assigned_run_id: str, context_payload: dict[str, Any], parent_payload: dict[str, Any] | None,
|
|
@@ -1131,9 +1416,25 @@ mv "$stage_dir" "$target_dir"
|
|
|
1131
1416
|
raise RuntimeError("hitch-model-proxy-health: required model proxy is unreachable")
|
|
1132
1417
|
return [], "degraded-unreachable"
|
|
1133
1418
|
base = self.model_capture["base_url_template"].replace("{run_id}", run_id)
|
|
1419
|
+
training = self.model_capture.get("training_external")
|
|
1134
1420
|
return [
|
|
1135
1421
|
f"OPENAI_BASE_URL={shlex.quote(base.replace('{provider}', 'openai'))}",
|
|
1136
1422
|
f"ANTHROPIC_BASE_URL={shlex.quote(base.replace('{provider}', 'anthropic'))}",
|
|
1423
|
+
*([
|
|
1424
|
+
"HITCH_TRAINING_EXTERNAL=1",
|
|
1425
|
+
f"HITCH_TRAINING_RUN_ID={run_id}",
|
|
1426
|
+
f"HITCH_TRAINING_BINDING={shlex.quote(json.dumps(training, separators=(',', ':')))}",
|
|
1427
|
+
"OPENAI_API_KEY=hitch-training-external",
|
|
1428
|
+
] if training else []),
|
|
1429
|
+
*([
|
|
1430
|
+
"HITCH_MANAGED_LOCAL_INFERENCE=1",
|
|
1431
|
+
f"HITCH_MANAGED_RUN_ID={run_id}",
|
|
1432
|
+
f"HITCH_MANAGED_INFERENCE_ID={self.managed_local_inference['inference_id']}",
|
|
1433
|
+
f"HITCH_MANAGED_MODEL_ID={self.managed_local_inference['model_id']}",
|
|
1434
|
+
"OPENAI_API_KEY=hitch-managed-local",
|
|
1435
|
+
*([f"HITCH_MANAGED_NODE_BINDING={shlex.quote(json.dumps(self.managed_local_inference['model_node'], separators=(',', ':')))}"]
|
|
1436
|
+
if self.managed_local_inference.get("model_node") else []),
|
|
1437
|
+
] if self.managed_local_inference else []),
|
|
1137
1438
|
], "healthy"
|
|
1138
1439
|
|
|
1139
1440
|
@staticmethod
|
|
@@ -1595,15 +1896,16 @@ esac""", "hitch_node_runtime_incompatible")
|
|
|
1595
1896
|
def _validate_model_capture(value: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
1596
1897
|
if value is None:
|
|
1597
1898
|
return None
|
|
1598
|
-
|
|
1899
|
+
required_fields = {
|
|
1599
1900
|
"schema_version", "mode", "required", "topology", "base_url_template", "health_url_template"
|
|
1600
|
-
}
|
|
1901
|
+
}
|
|
1902
|
+
if not isinstance(value, dict) or not required_fields.issubset(value) or set(value) - required_fields - {"managed_inference", "training_external"}:
|
|
1601
1903
|
raise ValueError("model_capture fields are invalid")
|
|
1602
1904
|
if (
|
|
1603
1905
|
value.get("schema_version") != "1"
|
|
1604
1906
|
or value.get("mode") not in {"proxy", "hybrid"}
|
|
1605
1907
|
or not isinstance(value.get("required"), bool)
|
|
1606
|
-
or value.get("topology")
|
|
1908
|
+
or value.get("topology") not in {"host-side", "in-sandbox"}
|
|
1607
1909
|
):
|
|
1608
1910
|
raise ValueError("model_capture identity is invalid")
|
|
1609
1911
|
for field, provider_count in (("base_url_template", 1), ("health_url_template", 0)):
|
|
@@ -1622,9 +1924,35 @@ def _validate_model_capture(value: dict[str, Any] | None) -> dict[str, Any] | No
|
|
|
1622
1924
|
)
|
|
1623
1925
|
if parsed.scheme not in {"http", "https"} or not parsed.hostname or parsed.username or parsed.password:
|
|
1624
1926
|
raise ValueError(f"model_capture {field} URL is invalid")
|
|
1927
|
+
managed = value.get("managed_inference")
|
|
1928
|
+
training = value.get("training_external")
|
|
1929
|
+
if training is not None:
|
|
1930
|
+
if (managed is not None or not isinstance(training, dict)
|
|
1931
|
+
or set(training) != {"binding_id", "training_run_id", "policy_version", "generation_contract_digest", "api", "max_output_tokens", "max_episode_steps"}
|
|
1932
|
+
or training.get("api") != "chat-completions" or value.get("required") is not True
|
|
1933
|
+
or any(not isinstance(training.get(k), str) or not training[k] for k in ("binding_id", "training_run_id", "policy_version"))
|
|
1934
|
+
or re.fullmatch(r"sha256:[a-f0-9]{64}", str(training.get("generation_contract_digest"))) is None
|
|
1935
|
+
or any(type(training.get(k)) is not int or training[k] <= 0 for k in ("max_output_tokens", "max_episode_steps"))):
|
|
1936
|
+
raise ValueError("model_capture training identity is invalid")
|
|
1937
|
+
if managed is not None and not _valid_managed_model_identity(managed):
|
|
1938
|
+
raise ValueError("model_capture managed inference identity is invalid")
|
|
1625
1939
|
return dict(value)
|
|
1626
1940
|
|
|
1627
1941
|
|
|
1942
|
+
def _valid_managed_model_identity(value: Any) -> bool:
|
|
1943
|
+
if (not isinstance(value, dict) or not {"inference_id", "model_id"}.issubset(value)
|
|
1944
|
+
or set(value) - {"inference_id", "model_id", "model_node"}
|
|
1945
|
+
or any(not isinstance(value[k], str) or re.fullmatch(r"sha256:[a-f0-9]{64}", value[k]) is None for k in ("inference_id", "model_id"))):
|
|
1946
|
+
return False
|
|
1947
|
+
if "model_node" not in value:
|
|
1948
|
+
return True
|
|
1949
|
+
node = value["model_node"]
|
|
1950
|
+
return (isinstance(node, dict) and set(node) == {"schema_version", "node_id", "generation", "runtime_digest", "launcher"}
|
|
1951
|
+
and node["schema_version"] == "2" and node["launcher"] == "process"
|
|
1952
|
+
and all(isinstance(node[k], str) and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}", node[k]) is not None for k in ("node_id", "generation"))
|
|
1953
|
+
and isinstance(node["runtime_digest"], str) and re.fullmatch(r"sha256:[a-f0-9]{64}", node["runtime_digest"]) is not None)
|
|
1954
|
+
|
|
1955
|
+
|
|
1628
1956
|
def canonical_manifest_json(manifest: dict[str, Any]) -> str:
|
|
1629
1957
|
"""Canonically encode the runtime identity `{ schema_version, node_range,
|
|
1630
1958
|
entrypoints, files }` with sorted object keys and no insignificant
|
|
@@ -31,6 +31,7 @@ _REQUIRED_LABELS = {
|
|
|
31
31
|
_ALLOWED_LABELS = _REQUIRED_LABELS | {_LABEL_TASK}
|
|
32
32
|
_COMPOSE_RESET_MARKER = "__HITCH_COMPOSE_RESET_NULL__"
|
|
33
33
|
_GPU_OVERRIDE_PREFIX = "__HITCH_GPU_OVERRIDE_"
|
|
34
|
+
_HOST_CREDENTIAL_HELPER_ENV = "HITCH_HOST_CREDENTIAL_HELPER_JSON"
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class HitchHarborDockerEnvironment(DockerEnvironment):
|
|
@@ -128,6 +129,25 @@ class HitchHarborDockerEnvironment(DockerEnvironment):
|
|
|
128
129
|
return result.model_copy(update={"disable_internet": True, "dynamic_network_policy": False})
|
|
129
130
|
return result
|
|
130
131
|
|
|
132
|
+
def _compose_env_vars(self, include_os_env=True):
|
|
133
|
+
result = super()._compose_env_vars(include_os_env=include_os_env)
|
|
134
|
+
# The host-only helper command is not a task Compose interpolation
|
|
135
|
+
# input. The agent reads it directly from its trusted host process.
|
|
136
|
+
result.pop(_HOST_CREDENTIAL_HELPER_ENV, None)
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
async def hitch_exec_with_private_env(self, command, *, env, cwd=None, timeout_sec=None, user=None):
|
|
140
|
+
"""Execute one Target without placing per-exec credential values in argv."""
|
|
141
|
+
from hitch_private_exec import exec_with_private_environment
|
|
142
|
+
return await exec_with_private_environment(
|
|
143
|
+
self,
|
|
144
|
+
command,
|
|
145
|
+
env=env,
|
|
146
|
+
cwd=cwd,
|
|
147
|
+
timeout_sec=timeout_sec,
|
|
148
|
+
user=user,
|
|
149
|
+
)
|
|
150
|
+
|
|
131
151
|
async def _apply_network_policy(self, network_policy):
|
|
132
152
|
if self._hitch_static_no_network:
|
|
133
153
|
if network_policy != self.network_policy:
|
|
@@ -375,7 +395,8 @@ def _validate_labels(value: Mapping[str, str] | None) -> dict[str, str]:
|
|
|
375
395
|
labels = dict(value)
|
|
376
396
|
if (
|
|
377
397
|
not re.fullmatch(r"[a-f0-9]{24}", labels.get(_LABEL_ROOT, ""))
|
|
378
|
-
or labels.get(_LABEL_PROVIDER)
|
|
398
|
+
or not isinstance(labels.get(_LABEL_PROVIDER), str)
|
|
399
|
+
or not re.fullmatch(r"[a-z0-9][a-z0-9._-]{0,127}", labels[_LABEL_PROVIDER])
|
|
379
400
|
or not re.fullmatch(r"eval_[a-f0-9]{32}", labels.get(_LABEL_EVAL, ""))
|
|
380
401
|
or not re.fullmatch(r"work_[a-f0-9]{32}", labels.get(_LABEL_WORK, ""))
|
|
381
402
|
or not re.fullmatch(r"lease_[a-f0-9]{32}", labels.get(_LABEL_LEASE, ""))
|