verifiers 0.3.2.dev138__tar.gz → 0.3.2.dev139__tar.gz
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.
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/PKG-INFO +1 -1
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/base.py +5 -1
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/container.py +135 -64
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/docker/__init__.py +80 -7
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/modal.py +12 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/prime.py +3 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/subprocess.py +3 -0
- verifiers-0.3.2.dev139/verifiers/v1/tasksets/harbor/compose.py +328 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/harbor/env.py +59 -7
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/harbor/taskset.py +78 -29
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/artifacts.py +3 -2
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/.gitignore +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/LICENSE +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/README.md +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/pyproject.toml +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/conftest.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/counter_tool_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/duet_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/echo_tool_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/echo_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/pyproject.toml +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_color_codeword_tasks.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_configs.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_e2e.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_envs.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_graph.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_judges.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_platform_run.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_scoring.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_serve_delta.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/tests/v1/test_trace.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/acp/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/acp/runner.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/agent.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/dashboard/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/dashboard/base.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/dashboard/eval.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/dashboard/replay.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/dashboard/validate.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/debug.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/eval/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/eval/hint.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/eval/main.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/eval/resume.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/eval/runner.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/gepa.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/init.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/output.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/replay.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/resolve.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/resume.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/cli/validate.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/clients/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/clients/base.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/clients/client.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/clients/eval.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/clients/train.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/agent.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/debug.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/eval.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/init.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/replay.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/cli/validate.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/client.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/judge.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/retries.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/runtime.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/serve.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/task.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/configs/taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/dialects/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/dialects/anthropic.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/dialects/base.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/dialects/chat.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/dialects/responses.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/agentic_judge/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/best_of_n/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/single_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/single_agent/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/user_sim/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/envs/user_sim/env.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/episode.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/errors.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/adapter.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/config.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/dataset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/reflection.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/gepa/runner.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/graph.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/bash/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/bash/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/browser_use/program.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/codex/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/codex/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/node.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/null/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/null/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/pi/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/pi/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/rlm/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/compaction.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/core.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/install.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/launch.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/harnesses/utils/mcp.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/base.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/pool.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/server.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/tunnel/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/tunnel/base.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/tunnel/custom.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/interception/tunnel/prime.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judge.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judges/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judges/reference.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judges/reference.txt +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judges/rubric.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/judges/rubric.txt +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/mcp/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/mcp/launch.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/mcp/server.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/mcp/toolset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/rollout.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/apptainer.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/docker/egress.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/runtimes/limiters.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/semantic.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/client.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/delta.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/encoding.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/pool.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/server.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/serve/types.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/session.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/state.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/task.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/trace.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/types.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/aio.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/compile.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/decorators.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/format.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/generic.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/git.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/image.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/interrupt.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/loaders.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/logging.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/memory.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/paths.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/platform.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/prime.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/retries.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/scope.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/score.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/trace_store.py +0 -0
- {verifiers-0.3.2.dev138 → verifiers-0.3.2.dev139}/verifiers/v1/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev139
|
|
4
4
|
Summary: Verifiers: Environments for LLM Reinforcement Learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
|
|
6
6
|
Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
|
|
@@ -72,6 +72,10 @@ class RuntimeProcess(ABC):
|
|
|
72
72
|
async def wait(self) -> int:
|
|
73
73
|
pass
|
|
74
74
|
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def poll(self) -> int | None:
|
|
77
|
+
"""Return the underlying process's exit code without waiting for it to exit."""
|
|
78
|
+
|
|
75
79
|
@abstractmethod
|
|
76
80
|
async def terminate(self) -> None:
|
|
77
81
|
pass
|
|
@@ -134,7 +138,7 @@ class BaseRuntimeInfo(BaseConfig):
|
|
|
134
138
|
class Runtime(ABC):
|
|
135
139
|
__slots__ = ("env",)
|
|
136
140
|
|
|
137
|
-
is_local:
|
|
141
|
+
is_local: bool = True
|
|
138
142
|
"""Whether this runtime exchanges host-local URLs without a public tunnel. True for
|
|
139
143
|
subprocess and the local container runtimes; remote runtimes override to False and
|
|
140
144
|
use a host `Tunnel` inward plus `expose` outward."""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Container operations through a CLI on the local machine or an owned runtime."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
@@ -7,14 +7,19 @@ import shlex
|
|
|
7
7
|
import signal
|
|
8
8
|
import uuid
|
|
9
9
|
from pathlib import PurePosixPath
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
from pydantic_config import BaseConfig
|
|
12
13
|
|
|
13
14
|
from verifiers.v1.errors import SandboxError
|
|
14
15
|
from verifiers.v1.runtimes.base import ProgramResult, Runtime, RuntimeProcess
|
|
15
|
-
from verifiers.v1.runtimes.subprocess import
|
|
16
|
+
from verifiers.v1.runtimes.subprocess import SubprocessProcess
|
|
16
17
|
from verifiers.v1.utils.aio import run_shielded
|
|
17
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from verifiers.v1.runtimes.modal import ModalConfig
|
|
21
|
+
from verifiers.v1.runtimes.prime import PrimeConfig
|
|
22
|
+
|
|
18
23
|
|
|
19
24
|
class ContainerConfig(BaseConfig):
|
|
20
25
|
image: str = "python:3.11-slim"
|
|
@@ -37,11 +42,12 @@ class ContainerConfig(BaseConfig):
|
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
async def _communicate(
|
|
40
|
-
*argv: str, input: bytes | None = None
|
|
45
|
+
*argv: str, input: bytes | None = None, env: dict[str, str] | None = None
|
|
41
46
|
) -> tuple[int, bytes, bytes]:
|
|
42
|
-
"""Run a host command
|
|
47
|
+
"""Run a host command; explicit env replaces inheritance, cancellation kills it."""
|
|
43
48
|
proc = await asyncio.create_subprocess_exec(
|
|
44
49
|
*argv,
|
|
50
|
+
env=env,
|
|
45
51
|
stdin=asyncio.subprocess.PIPE
|
|
46
52
|
if input is not None
|
|
47
53
|
else asyncio.subprocess.DEVNULL,
|
|
@@ -59,8 +65,10 @@ async def _communicate(
|
|
|
59
65
|
return proc.returncode or 0, stdout, stderr
|
|
60
66
|
|
|
61
67
|
|
|
62
|
-
async def cli(
|
|
63
|
-
|
|
68
|
+
async def cli(
|
|
69
|
+
*argv: str, input: bytes | None = None, env: dict[str, str] | None = None
|
|
70
|
+
) -> ProgramResult:
|
|
71
|
+
code, stdout, stderr = await _communicate(*argv, input=input, env=env)
|
|
64
72
|
return ProgramResult(
|
|
65
73
|
code, stdout.decode(errors="replace"), stderr.decode(errors="replace")
|
|
66
74
|
)
|
|
@@ -68,35 +76,38 @@ async def cli(*argv: str, input: bytes | None = None) -> ProgramResult:
|
|
|
68
76
|
|
|
69
77
|
class ContainerProcess(RuntimeProcess):
|
|
70
78
|
"""A process attached through the CLI client. The client does not forward signals,
|
|
71
|
-
so
|
|
79
|
+
so the container runtime delivers them directly to the inner process group."""
|
|
72
80
|
|
|
73
81
|
def __init__(
|
|
74
82
|
self,
|
|
75
|
-
process:
|
|
76
|
-
|
|
83
|
+
process: RuntimeProcess,
|
|
84
|
+
runtime: "ContainerRuntime",
|
|
77
85
|
pid: int,
|
|
78
86
|
pidfile: str,
|
|
79
87
|
) -> None:
|
|
80
88
|
self._process = process
|
|
81
|
-
self.
|
|
89
|
+
self._runtime = runtime
|
|
82
90
|
self._pid = pid
|
|
83
91
|
self._pidfile = pidfile
|
|
84
|
-
|
|
85
|
-
assert process.stdout is not None
|
|
86
|
-
assert process.stderr is not None
|
|
87
|
-
self._stdin = process.stdin
|
|
88
|
-
self.stdout = read_stream(process.stdout)
|
|
89
|
-
self.stderr = read_stream(process.stderr)
|
|
92
|
+
self.stdout, self.stderr = process.stdout, process.stderr
|
|
90
93
|
|
|
91
94
|
async def write(self, data: bytes) -> None:
|
|
92
|
-
self.
|
|
93
|
-
await self._stdin.drain()
|
|
95
|
+
await self._process.write(data)
|
|
94
96
|
|
|
95
97
|
async def wait(self) -> int:
|
|
96
98
|
try:
|
|
97
99
|
return await self._process.wait()
|
|
98
100
|
finally:
|
|
99
|
-
|
|
101
|
+
# Removing bookkeeping files must not replace the process result.
|
|
102
|
+
with contextlib.suppress(Exception):
|
|
103
|
+
await run_shielded(
|
|
104
|
+
self._runtime._run_host(
|
|
105
|
+
*self._runtime._exec({}), "rm", "-f", self._pidfile
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def poll(self) -> int | None:
|
|
110
|
+
return await self._process.poll()
|
|
100
111
|
|
|
101
112
|
async def terminate(self) -> None:
|
|
102
113
|
await self._signal("TERM")
|
|
@@ -105,27 +116,27 @@ class ContainerProcess(RuntimeProcess):
|
|
|
105
116
|
await self._signal("KILL")
|
|
106
117
|
|
|
107
118
|
async def _signal(self, signal: str) -> None:
|
|
108
|
-
if self.
|
|
119
|
+
if await self.poll() is not None:
|
|
109
120
|
return
|
|
110
|
-
result = await
|
|
111
|
-
*self._exec,
|
|
121
|
+
result = await self._runtime._run_host(
|
|
122
|
+
*self._runtime._exec({}),
|
|
112
123
|
"sh",
|
|
113
124
|
"-c",
|
|
114
|
-
'kill -"$1" "-$2" 2>/dev/null || kill -"$1" "$2"',
|
|
125
|
+
'kill -"$1" "-$2" 2>/dev/null || kill -"$1" "$2" 2>/dev/null',
|
|
115
126
|
"vf-signal",
|
|
116
127
|
signal,
|
|
117
128
|
str(self._pid),
|
|
118
129
|
)
|
|
119
|
-
if result.exit_code != 0 and self.
|
|
130
|
+
if result.exit_code != 0 and await self.poll() is None:
|
|
120
131
|
raise SandboxError(
|
|
121
132
|
f"container process signal failed: {result.stderr.strip()}"
|
|
122
133
|
)
|
|
123
134
|
|
|
124
135
|
|
|
125
136
|
async def _abort_process_startup(
|
|
126
|
-
proc:
|
|
137
|
+
proc: RuntimeProcess, runtime: "ContainerRuntime", pidfile: str
|
|
127
138
|
) -> str:
|
|
128
|
-
"""Kill a partially opened container process and reap its
|
|
139
|
+
"""Kill a partially opened container process and reap its CLI client."""
|
|
129
140
|
# The target normally writes its PID immediately, but cancellation can win
|
|
130
141
|
# that race. Wait briefly for the file before signalling the process group.
|
|
131
142
|
cleanup = (
|
|
@@ -137,23 +148,64 @@ async def _abort_process_startup(
|
|
|
137
148
|
)
|
|
138
149
|
try:
|
|
139
150
|
with contextlib.suppress(Exception):
|
|
151
|
+
# An exited CLI client may have left its inner process running.
|
|
140
152
|
await asyncio.wait_for(
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
runtime._run_host(
|
|
154
|
+
*runtime._exec({}),
|
|
155
|
+
"sh",
|
|
156
|
+
"-c",
|
|
157
|
+
cleanup,
|
|
158
|
+
"vf-process-cleanup",
|
|
159
|
+
pidfile,
|
|
160
|
+
),
|
|
161
|
+
timeout=5,
|
|
143
162
|
)
|
|
144
163
|
finally:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
with contextlib.suppress(Exception):
|
|
165
|
+
await asyncio.wait_for(proc.kill(), 5)
|
|
166
|
+
stderr = b""
|
|
167
|
+
with contextlib.suppress(Exception):
|
|
168
|
+
async with asyncio.timeout(5):
|
|
169
|
+
async for _ in proc.stdout:
|
|
170
|
+
pass
|
|
171
|
+
stderr = b"".join([chunk async for chunk in proc.stderr])
|
|
172
|
+
await proc.wait()
|
|
149
173
|
return stderr.decode(errors="replace").strip()
|
|
150
174
|
|
|
151
175
|
|
|
152
176
|
class ContainerRuntime(Runtime):
|
|
153
|
-
"""A
|
|
177
|
+
"""A container reached through its CLI: every operation is an `exec` into it.
|
|
154
178
|
Subclasses provision the container (`start` / `cleanup`) and describe the exec."""
|
|
155
179
|
|
|
156
|
-
config: ContainerConfig
|
|
180
|
+
config: "ContainerConfig | PrimeConfig | ModalConfig"
|
|
181
|
+
_host: Runtime | None = None
|
|
182
|
+
|
|
183
|
+
async def _run_host(self, *argv: str) -> ProgramResult:
|
|
184
|
+
if self._host is None:
|
|
185
|
+
return await cli(*argv)
|
|
186
|
+
return await self._host.run(list(argv), {})
|
|
187
|
+
|
|
188
|
+
async def _communicate_host(
|
|
189
|
+
self, *argv: str, input: bytes | None = None
|
|
190
|
+
) -> tuple[int, bytes, bytes]:
|
|
191
|
+
if self._host is None:
|
|
192
|
+
return await _communicate(*argv, input=input)
|
|
193
|
+
# Stage bytes through the provider filesystem, never its text command logs.
|
|
194
|
+
temporary = f"/tmp/vf-io-{uuid.uuid4().hex}"
|
|
195
|
+
command = f"{shlex.join(argv)} > {temporary}.out"
|
|
196
|
+
try:
|
|
197
|
+
if input is not None:
|
|
198
|
+
await self._host.write(f"{temporary}.in", input)
|
|
199
|
+
command += f" < {temporary}.in"
|
|
200
|
+
result = await self._run_host("sh", "-c", command)
|
|
201
|
+
data = await self._host.read(f"{temporary}.out")
|
|
202
|
+
return result.exit_code, data, result.stderr.encode()
|
|
203
|
+
finally:
|
|
204
|
+
# Temporary-file cleanup is best-effort after command completion/failure.
|
|
205
|
+
with contextlib.suppress(Exception):
|
|
206
|
+
await run_shielded(
|
|
207
|
+
self._run_host("rm", "-f", f"{temporary}.in", f"{temporary}.out")
|
|
208
|
+
)
|
|
157
209
|
|
|
158
210
|
def _exec(self, env: dict[str, str], *, stdin: bool = False) -> list[str]:
|
|
159
211
|
"""Host argv that runs a command inside the container, in the workdir, with
|
|
@@ -161,7 +213,7 @@ class ContainerRuntime(Runtime):
|
|
|
161
213
|
raise NotImplementedError
|
|
162
214
|
|
|
163
215
|
async def run(self, argv: list[str], env: dict[str, str]) -> ProgramResult:
|
|
164
|
-
return await
|
|
216
|
+
return await self._run_host(*self._exec(self.process_env(env)), *argv)
|
|
165
217
|
|
|
166
218
|
async def open_process(
|
|
167
219
|
self, argv: list[str], env: dict[str, str]
|
|
@@ -177,8 +229,7 @@ class ContainerRuntime(Runtime):
|
|
|
177
229
|
'vf-process "$@"; '
|
|
178
230
|
'fi; echo $$ > "$1"; shift; exec "$@"'
|
|
179
231
|
)
|
|
180
|
-
|
|
181
|
-
proc = await asyncio.create_subprocess_exec(
|
|
232
|
+
command = [
|
|
182
233
|
*self._exec(self.process_env(env), stdin=True),
|
|
183
234
|
"sh",
|
|
184
235
|
"-c",
|
|
@@ -186,33 +237,47 @@ class ContainerRuntime(Runtime):
|
|
|
186
237
|
"vf-process",
|
|
187
238
|
pidfile,
|
|
188
239
|
*argv,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
240
|
+
]
|
|
241
|
+
if self._host is None:
|
|
242
|
+
proc = SubprocessProcess(
|
|
243
|
+
await asyncio.create_subprocess_exec(
|
|
244
|
+
*command,
|
|
245
|
+
stdin=asyncio.subprocess.PIPE,
|
|
246
|
+
stdout=asyncio.subprocess.PIPE,
|
|
247
|
+
stderr=asyncio.subprocess.PIPE,
|
|
248
|
+
start_new_session=True,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
proc = await self._host.open_process(command, {})
|
|
193
253
|
loop = asyncio.get_running_loop()
|
|
194
|
-
deadline = loop.time() + 5
|
|
195
|
-
exited = False
|
|
254
|
+
deadline = loop.time() + (30 if self._host is not None else 5)
|
|
196
255
|
try:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
256
|
+
async with asyncio.timeout_at(deadline):
|
|
257
|
+
while True:
|
|
258
|
+
# An exited client gets one final PID read: short commands may
|
|
259
|
+
# have written their PID and finished before the first poll.
|
|
260
|
+
returncode = await proc.poll()
|
|
261
|
+
ready = await self._run_host(*self._exec({}), "cat", pidfile)
|
|
262
|
+
if ready.exit_code == 0 and ready.stdout.strip().isdigit():
|
|
263
|
+
return ContainerProcess(
|
|
264
|
+
proc, self, int(ready.stdout.strip()), pidfile
|
|
265
|
+
)
|
|
266
|
+
if returncode is not None:
|
|
267
|
+
break
|
|
268
|
+
await asyncio.sleep(0.05)
|
|
269
|
+
except TimeoutError as error:
|
|
270
|
+
stderr = await run_shielded(_abort_process_startup(proc, self, pidfile))
|
|
271
|
+
raise SandboxError(
|
|
272
|
+
f"container live process failed to start: {stderr or 'PID unavailable'}"
|
|
273
|
+
) from error
|
|
208
274
|
except BaseException:
|
|
209
|
-
await run_shielded(_abort_process_startup(proc,
|
|
275
|
+
await run_shielded(_abort_process_startup(proc, self, pidfile))
|
|
210
276
|
raise
|
|
211
|
-
|
|
212
|
-
stderr = await run_shielded(_abort_process_startup(proc, control, pidfile))
|
|
213
|
-
detail = stderr or ready.stderr.strip()
|
|
277
|
+
stderr = await run_shielded(_abort_process_startup(proc, self, pidfile))
|
|
214
278
|
raise SandboxError(
|
|
215
|
-
f"container
|
|
279
|
+
f"container client exited with code {returncode} before reporting its PID: "
|
|
280
|
+
f"{stderr or ready.stderr.strip()}"
|
|
216
281
|
)
|
|
217
282
|
|
|
218
283
|
async def run_background(
|
|
@@ -221,7 +286,9 @@ class ContainerRuntime(Runtime):
|
|
|
221
286
|
# Backgrounded inside the container, so it outlives this exec and lives until
|
|
222
287
|
# the container is removed in stop().
|
|
223
288
|
script = f"{shlex.join(argv)} > {shlex.quote(log)} 2>&1 < /dev/null &"
|
|
224
|
-
result = await
|
|
289
|
+
result = await self._run_host(
|
|
290
|
+
*self._exec(self.process_env(env)), "sh", "-c", script
|
|
291
|
+
)
|
|
225
292
|
if result.exit_code != 0:
|
|
226
293
|
raise SandboxError(
|
|
227
294
|
f"container background process failed: {result.stderr.strip()}"
|
|
@@ -229,7 +296,9 @@ class ContainerRuntime(Runtime):
|
|
|
229
296
|
|
|
230
297
|
async def _read(self, path: str, max_bytes: int | None = None) -> bytes:
|
|
231
298
|
argv = ["cat"] if max_bytes is None else ["head", "-c", str(max_bytes)]
|
|
232
|
-
code, data, stderr = await
|
|
299
|
+
code, data, stderr = await self._communicate_host(
|
|
300
|
+
*self._exec({}), *argv, "--", path
|
|
301
|
+
)
|
|
233
302
|
if code != 0:
|
|
234
303
|
raise SandboxError(
|
|
235
304
|
f"read {path!r}: {stderr.decode(errors='replace').strip()}"
|
|
@@ -238,12 +307,14 @@ class ContainerRuntime(Runtime):
|
|
|
238
307
|
|
|
239
308
|
async def write(self, path: str, data: bytes) -> None:
|
|
240
309
|
parent = shlex.quote(str(PurePosixPath(path).parent))
|
|
241
|
-
|
|
310
|
+
code, _, stderr = await self._communicate_host(
|
|
242
311
|
*self._exec({}, stdin=True),
|
|
243
312
|
"sh",
|
|
244
313
|
"-c",
|
|
245
314
|
f"mkdir -p {parent} && cat > {shlex.quote(path)}",
|
|
246
315
|
input=data,
|
|
247
316
|
)
|
|
248
|
-
if
|
|
249
|
-
raise SandboxError(
|
|
317
|
+
if code != 0:
|
|
318
|
+
raise SandboxError(
|
|
319
|
+
f"write {path!r}: {stderr.decode(errors='replace').strip()}"
|
|
320
|
+
)
|
|
@@ -12,7 +12,8 @@ import subprocess
|
|
|
12
12
|
import sys
|
|
13
13
|
import tempfile
|
|
14
14
|
import uuid
|
|
15
|
-
from
|
|
15
|
+
from collections.abc import AsyncIterator
|
|
16
|
+
from typing import TYPE_CHECKING, ClassVar, Literal
|
|
16
17
|
from urllib.parse import urlsplit
|
|
17
18
|
|
|
18
19
|
from verifiers.v1.configs.runtime import NetworkPolicyConfig
|
|
@@ -20,6 +21,7 @@ from verifiers.v1.errors import SandboxError
|
|
|
20
21
|
from verifiers.v1.runtimes.base import (
|
|
21
22
|
SERVICE_PORT,
|
|
22
23
|
BaseRuntimeInfo,
|
|
24
|
+
Runtime,
|
|
23
25
|
parse_gpu,
|
|
24
26
|
)
|
|
25
27
|
from verifiers.v1.runtimes.container import ContainerConfig, ContainerRuntime, cli
|
|
@@ -32,6 +34,10 @@ from verifiers.v1.utils.scope import run_scope
|
|
|
32
34
|
|
|
33
35
|
logger = logging.getLogger(__name__)
|
|
34
36
|
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from verifiers.v1.runtimes.modal import ModalConfig
|
|
39
|
+
from verifiers.v1.runtimes.prime import PrimeConfig
|
|
40
|
+
|
|
35
41
|
|
|
36
42
|
class DockerConfig(ContainerConfig, NetworkPolicyConfig):
|
|
37
43
|
type: Literal["docker"] = "docker"
|
|
@@ -69,17 +75,77 @@ class DockerRuntime(ContainerRuntime):
|
|
|
69
75
|
info_cls: ClassVar[type[BaseRuntimeInfo]] = DockerRuntimeInfo
|
|
70
76
|
|
|
71
77
|
def __init__(
|
|
72
|
-
self,
|
|
78
|
+
self,
|
|
79
|
+
config: "DockerConfig | PodmanConfig | PrimeConfig | ModalConfig",
|
|
80
|
+
name: str | None = None,
|
|
81
|
+
*,
|
|
82
|
+
host: Runtime | None = None,
|
|
73
83
|
) -> None:
|
|
74
84
|
super().__init__(name)
|
|
75
85
|
self.config = config.model_copy(update={"workdir": config.workdir or "/app"})
|
|
76
|
-
self.
|
|
86
|
+
self._host = host
|
|
87
|
+
self.is_local = host is None or host.is_local
|
|
88
|
+
self.info = (
|
|
89
|
+
host.info.model_copy(
|
|
90
|
+
update={"image": self.config.image, "workdir": self.config.workdir}
|
|
91
|
+
)
|
|
92
|
+
if host
|
|
93
|
+
else self.info_cls(**self.config.model_dump())
|
|
94
|
+
)
|
|
77
95
|
self._container: str | None = None # our `--name` (used for exec/rm)
|
|
78
96
|
self._service_url: str | None = None
|
|
79
97
|
self._proxy: EgressProxy | None = None
|
|
80
98
|
self._image_env: dict[str, str] = {}
|
|
81
99
|
self._stopped = False
|
|
82
100
|
self._cut = False
|
|
101
|
+
# Helper containers carry the run label, including for borrowed containers.
|
|
102
|
+
self._label_args = ["--label", f"verifiers.run={run_scope()}"]
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
@contextlib.asynccontextmanager
|
|
106
|
+
async def attach(
|
|
107
|
+
cls,
|
|
108
|
+
config: "DockerConfig | PrimeConfig | ModalConfig",
|
|
109
|
+
container: str,
|
|
110
|
+
*,
|
|
111
|
+
service_url: str | None = None,
|
|
112
|
+
host: Runtime | None = None,
|
|
113
|
+
) -> AsyncIterator["DockerRuntime"]:
|
|
114
|
+
"""Borrow an existing container; its caller owns creation and removal."""
|
|
115
|
+
runtime = cls(config, host=host)
|
|
116
|
+
runtime._container = container
|
|
117
|
+
runtime.info.borrowed = True # cleanup never removes a borrowed container
|
|
118
|
+
try:
|
|
119
|
+
code, data, stderr = await runtime._communicate_host(
|
|
120
|
+
runtime.engine, "inspect", "--format", "{{json .Config}}", container
|
|
121
|
+
)
|
|
122
|
+
if code:
|
|
123
|
+
raise SandboxError(
|
|
124
|
+
f"Container inspection failed: {stderr.decode(errors='replace')}"
|
|
125
|
+
)
|
|
126
|
+
info = json.loads(data)
|
|
127
|
+
runtime._image_env = dict(
|
|
128
|
+
entry.split("=", 1) for entry in info["Env"] or []
|
|
129
|
+
)
|
|
130
|
+
runtime.config = config.model_copy(
|
|
131
|
+
update={"image": info["Image"], "workdir": info["WorkingDir"] or "/"}
|
|
132
|
+
)
|
|
133
|
+
if host is None:
|
|
134
|
+
runtime.info.id = container
|
|
135
|
+
runtime.info.image = runtime.config.image
|
|
136
|
+
runtime.info.workdir = runtime.config.workdir
|
|
137
|
+
runtime._service_url = service_url
|
|
138
|
+
if host is None and service_url is not None:
|
|
139
|
+
runtime._proxy = EgressProxy(NetworkPolicy(NetworkPolicyConfig(), []))
|
|
140
|
+
if sys.platform == "linux":
|
|
141
|
+
await runtime._proxy.start(
|
|
142
|
+
listener=await runtime._container_listener()
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
await runtime._proxy.start("127.0.0.1")
|
|
146
|
+
yield runtime
|
|
147
|
+
finally:
|
|
148
|
+
await runtime.stop()
|
|
83
149
|
|
|
84
150
|
@property
|
|
85
151
|
def published_port(self) -> int:
|
|
@@ -162,7 +228,6 @@ class DockerRuntime(ContainerRuntime):
|
|
|
162
228
|
for key, value in self.env.items()
|
|
163
229
|
for arg in ("--env", f"{key}={value}")
|
|
164
230
|
]
|
|
165
|
-
self._label_args = ["--label", f"verifiers.run={run_scope()}"]
|
|
166
231
|
run = await cli(
|
|
167
232
|
self.engine,
|
|
168
233
|
"run",
|
|
@@ -262,6 +327,8 @@ class DockerRuntime(ContainerRuntime):
|
|
|
262
327
|
)
|
|
263
328
|
|
|
264
329
|
def host_url(self, url: str) -> str:
|
|
330
|
+
if self._host is not None:
|
|
331
|
+
return self._host.host_url(url)
|
|
265
332
|
parts = urlsplit(url)
|
|
266
333
|
if not is_loopback_host(parts.hostname or ""):
|
|
267
334
|
return url
|
|
@@ -270,6 +337,8 @@ class DockerRuntime(ContainerRuntime):
|
|
|
270
337
|
return self._proxy.callback_url(url, host)
|
|
271
338
|
|
|
272
339
|
async def expose(self, port: int) -> str:
|
|
340
|
+
if self._host is not None:
|
|
341
|
+
return await self._host.expose(port)
|
|
273
342
|
if port != SERVICE_PORT or self._service_url is None:
|
|
274
343
|
raise SandboxError(
|
|
275
344
|
f"{self.engine} publishes only port {SERVICE_PORT}, not {port}"
|
|
@@ -340,6 +409,9 @@ class DockerRuntime(ContainerRuntime):
|
|
|
340
409
|
|
|
341
410
|
async def prepare_execution(self, routes: list[str] | None) -> None:
|
|
342
411
|
"""Allow the declared framework routes, then leave the proxy as the only way out."""
|
|
412
|
+
if self._host is not None:
|
|
413
|
+
await self._host.prepare_execution(routes)
|
|
414
|
+
return
|
|
343
415
|
if not self.network_restricted:
|
|
344
416
|
return
|
|
345
417
|
assert self._proxy is not None
|
|
@@ -352,6 +424,7 @@ class DockerRuntime(ContainerRuntime):
|
|
|
352
424
|
urlsplit(url)._replace(path="", query="", fragment="").geturl()
|
|
353
425
|
for url in routes
|
|
354
426
|
]
|
|
427
|
+
assert isinstance(self.config, NetworkPolicyConfig)
|
|
355
428
|
self._proxy.policy = NetworkPolicy(self.config, framework)
|
|
356
429
|
if self._cut:
|
|
357
430
|
return
|
|
@@ -465,20 +538,20 @@ class DockerRuntime(ContainerRuntime):
|
|
|
465
538
|
self, argv: list[str], env: dict[str, str], log: str
|
|
466
539
|
) -> None:
|
|
467
540
|
# A setup server outlives the network cut and needs the initially open proxy.
|
|
468
|
-
if self.network_restricted:
|
|
541
|
+
if self.network_restricted and self._proxy is not None:
|
|
469
542
|
env = {**env, **self._proxy_env()}
|
|
470
543
|
# The engine owns the background server as a detached exec process.
|
|
471
544
|
command = self._exec(self.process_env(env))
|
|
472
545
|
command.insert(2, "--detach")
|
|
473
546
|
script = f"exec {shlex.join(argv)} > {shlex.quote(log)} 2>&1 < /dev/null"
|
|
474
|
-
result = await
|
|
547
|
+
result = await self._run_host(*command, "sh", "-c", script)
|
|
475
548
|
if result.exit_code != 0:
|
|
476
549
|
raise SandboxError(
|
|
477
550
|
f"{self.engine} background process failed: {result.stderr.strip()}"
|
|
478
551
|
)
|
|
479
552
|
|
|
480
553
|
def cleanup(self) -> None:
|
|
481
|
-
if self._container is None or self._stopped:
|
|
554
|
+
if self.info.borrowed or self._container is None or self._stopped:
|
|
482
555
|
return
|
|
483
556
|
self._stopped = (
|
|
484
557
|
True # idempotency guard; keep `_container` so the name still shows
|
|
@@ -75,6 +75,8 @@ def _egress_domain(rule: str, *, framework: bool = False) -> str | None:
|
|
|
75
75
|
|
|
76
76
|
class ModalConfig(NetworkPolicyConfig):
|
|
77
77
|
type: Literal["modal"] = "modal"
|
|
78
|
+
vm: bool = False
|
|
79
|
+
"""Use a VM sandbox for workloads requiring a Docker daemon."""
|
|
78
80
|
image: str = "python:3.11-slim"
|
|
79
81
|
workdir: str | None = None
|
|
80
82
|
"""Working directory override; None uses the task's workdir, or /app."""
|
|
@@ -113,6 +115,12 @@ class ModalConfig(NetworkPolicyConfig):
|
|
|
113
115
|
_egress_domain(rule)
|
|
114
116
|
return self
|
|
115
117
|
|
|
118
|
+
@model_validator(mode="after")
|
|
119
|
+
def _validate_vm(self) -> "ModalConfig":
|
|
120
|
+
if self.vm and self.gpu:
|
|
121
|
+
raise ValueError("Modal VM sandboxes support CPU workloads only")
|
|
122
|
+
return self
|
|
123
|
+
|
|
116
124
|
|
|
117
125
|
class ModalRuntimeInfo(ModalConfig, BaseRuntimeInfo):
|
|
118
126
|
pass
|
|
@@ -134,6 +142,9 @@ class ModalProcess(RuntimeProcess):
|
|
|
134
142
|
async def wait(self) -> int:
|
|
135
143
|
return await self._process.wait.aio()
|
|
136
144
|
|
|
145
|
+
async def poll(self) -> int | None:
|
|
146
|
+
return await self._process.poll.aio()
|
|
147
|
+
|
|
137
148
|
async def terminate(self) -> None:
|
|
138
149
|
await self._signal("TERM")
|
|
139
150
|
|
|
@@ -233,6 +244,7 @@ class ModalRuntime(Runtime):
|
|
|
233
244
|
),
|
|
234
245
|
timeout=24 * 60 * 60, # Maximum lifetime of any sandbox.
|
|
235
246
|
encrypted_ports=[SERVICE_PORT],
|
|
247
|
+
experimental_options={"vm_runtime": True} if self.config.vm else {},
|
|
236
248
|
)
|
|
237
249
|
|
|
238
250
|
async def prepare_execution(self, routes: list[str] | None) -> None:
|
|
@@ -133,6 +133,9 @@ class PrimeProcess(RuntimeProcess):
|
|
|
133
133
|
async def wait(self) -> int:
|
|
134
134
|
return await self._process.wait()
|
|
135
135
|
|
|
136
|
+
async def poll(self) -> int | None:
|
|
137
|
+
return self._process.returncode
|
|
138
|
+
|
|
136
139
|
async def terminate(self) -> None:
|
|
137
140
|
await self._process.terminate()
|
|
138
141
|
|
|
@@ -70,6 +70,9 @@ class SubprocessProcess(RuntimeProcess):
|
|
|
70
70
|
async def wait(self) -> int:
|
|
71
71
|
return await self._process.wait()
|
|
72
72
|
|
|
73
|
+
async def poll(self) -> int | None:
|
|
74
|
+
return self._process.returncode
|
|
75
|
+
|
|
73
76
|
async def terminate(self) -> None:
|
|
74
77
|
signal_process(self._process, signal.SIGTERM)
|
|
75
78
|
|