verifiers 0.3.2.dev208__tar.gz → 0.3.2.dev209__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.dev208 → verifiers-0.3.2.dev209}/PKG-INFO +1 -1
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/conftest.py +101 -0
- verifiers-0.3.2.dev209/tests/v1/fixtures/agent_user_harness_v1.py +37 -0
- verifiers-0.3.2.dev209/tests/v1/fixtures/agent_user_v1.py +161 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/pyproject.toml +1 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_e2e.py +67 -8
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/install.py +10 -1
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/launch.py +48 -24
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/rollout.py +13 -7
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/base.py +26 -14
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/prime.py +28 -8
- verifiers-0.3.2.dev208/tests/v1/fixtures/agent_user_v1.py +0 -82
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/.gitignore +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/LICENSE +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/README.md +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/pyproject.toml +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/counter_tool_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/delivery_behaviors.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/duet_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_tool_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_color_codeword_tasks.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_configs.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_delivery_behaviors.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_envs.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_graph.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_judges.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_platform_run.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_scoring.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_serve_delta.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_trace.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/tests/v1/test_tunnel_watchdog.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/acp/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/acp/runner.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/agent.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/base.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/eval.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/replay.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/validate.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/debug.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/hint.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/main.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/resume.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/runner.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/gepa.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/init.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/output.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/replay.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/resolve.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/resume.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/cli/validate.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/clients/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/clients/base.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/clients/client.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/clients/eval.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/clients/train.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/agent.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/debug.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/eval.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/init.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/replay.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/validate.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/client.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/judge.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/retries.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/runtime.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/select.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/serve.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/task.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/configs/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/anthropic.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/base.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/chat.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/responses.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/agentic_judge/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/best_of_n/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/single_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/single_agent/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/user_sim/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/envs/user_sim/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/episode.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/errors.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/adapter.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/config.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/dataset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/reflection.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/runner.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/graph.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/bash/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/bash/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/program.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/node.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/null/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/null/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/rlm/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/compaction.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/core.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/launch.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/mcp.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/base.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/pool.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/server.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/base.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/custom.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/prime.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judge.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/decision.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/reference.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/reference.txt +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/rubric.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/judges/rubric.txt +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/server.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/toolset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/apptainer.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/container.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/docker/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/docker/egress.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/e2b.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/limiters.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/modal.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/subprocess.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/semantic.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/client.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/delta.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/encoding.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/pool.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/server.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/serve/types.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/session.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/state.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/task.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/compose.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/env.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/trace.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/types.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/__init__.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/aio.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/artifacts.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/compile.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/decorators.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/eval.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/format.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/generic.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/git.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/image.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/interrupt.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/loaders.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/logging.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/memory.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/paths.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/platform.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/prime.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/retries.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/scope.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/score.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/verifiers/v1/utils/trace_store.py +0 -0
- {verifiers-0.3.2.dev208 → verifiers-0.3.2.dev209}/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.dev209
|
|
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
|
|
@@ -134,6 +134,7 @@ def _eval_config(
|
|
|
134
134
|
runtime: dict | None = None,
|
|
135
135
|
env: dict | None = None,
|
|
136
136
|
reasoning_effort: str | None = None,
|
|
137
|
+
client: dict | None = None,
|
|
137
138
|
) -> EvalConfig:
|
|
138
139
|
"""Build the smallest `EvalConfig` that still exercises the path, shared by the in-process
|
|
139
140
|
(`run_v1`) and env-server (`run_v1_server`) fixtures. `taskset_overrides` merges onto the
|
|
@@ -195,6 +196,7 @@ def _eval_config(
|
|
|
195
196
|
run={"dir": output_dir.name},
|
|
196
197
|
model=CI_MODEL,
|
|
197
198
|
push=False,
|
|
199
|
+
**({"client": client} if client else {}),
|
|
198
200
|
)
|
|
199
201
|
|
|
200
202
|
|
|
@@ -280,3 +282,102 @@ async def live_ctx():
|
|
|
280
282
|
client=EvalClientConfig(),
|
|
281
283
|
sampling=SamplingConfig(max_tokens=2048),
|
|
282
284
|
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@pytest.fixture
|
|
288
|
+
async def scripted_model():
|
|
289
|
+
"""A deterministic upstream model: every call gets a plain "OK" in the caller's own
|
|
290
|
+
dialect (chat completions, Responses, or Anthropic messages), streamed through that
|
|
291
|
+
dialect's own encoder when asked. Yields a client config pointing at it."""
|
|
292
|
+
from aiohttp import web
|
|
293
|
+
|
|
294
|
+
from verifiers.v1.dialects.anthropic import AnthropicDialect
|
|
295
|
+
from verifiers.v1.dialects.chat import ChatDialect
|
|
296
|
+
from verifiers.v1.dialects.responses import ResponsesDialect
|
|
297
|
+
|
|
298
|
+
def chat(model: str) -> dict:
|
|
299
|
+
message = {"role": "assistant", "content": "OK"}
|
|
300
|
+
return {
|
|
301
|
+
"id": "chatcmpl-scripted",
|
|
302
|
+
"object": "chat.completion",
|
|
303
|
+
"created": 0,
|
|
304
|
+
"model": model,
|
|
305
|
+
"choices": [{"index": 0, "message": message, "finish_reason": "stop"}],
|
|
306
|
+
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def responses(model: str) -> dict:
|
|
310
|
+
text = {"type": "output_text", "text": "OK", "annotations": []}
|
|
311
|
+
return {
|
|
312
|
+
"id": "resp_scripted",
|
|
313
|
+
"object": "response",
|
|
314
|
+
"created_at": 0,
|
|
315
|
+
"model": model,
|
|
316
|
+
"status": "completed",
|
|
317
|
+
"output": [
|
|
318
|
+
{
|
|
319
|
+
"type": "message",
|
|
320
|
+
"id": "msg_scripted",
|
|
321
|
+
"status": "completed",
|
|
322
|
+
"role": "assistant",
|
|
323
|
+
"content": [text],
|
|
324
|
+
}
|
|
325
|
+
],
|
|
326
|
+
"parallel_tool_calls": True,
|
|
327
|
+
"tool_choice": "auto",
|
|
328
|
+
"tools": [],
|
|
329
|
+
"usage": {
|
|
330
|
+
"input_tokens": 1,
|
|
331
|
+
"output_tokens": 1,
|
|
332
|
+
"total_tokens": 2,
|
|
333
|
+
"input_tokens_details": {"cached_tokens": 0},
|
|
334
|
+
"output_tokens_details": {"reasoning_tokens": 0},
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
def anthropic(model: str) -> dict:
|
|
339
|
+
return {
|
|
340
|
+
"id": "msg_scripted",
|
|
341
|
+
"type": "message",
|
|
342
|
+
"role": "assistant",
|
|
343
|
+
"model": model,
|
|
344
|
+
"content": [{"type": "text", "text": "OK"}],
|
|
345
|
+
"stop_reason": "end_turn",
|
|
346
|
+
"stop_sequence": None,
|
|
347
|
+
"usage": {"input_tokens": 1, "output_tokens": 1},
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
routes = [
|
|
351
|
+
("/chat/completions", ChatDialect(), chat),
|
|
352
|
+
("/responses", ResponsesDialect(), responses),
|
|
353
|
+
("/messages", AnthropicDialect(), anthropic),
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
async def answer(request: web.Request) -> web.StreamResponse:
|
|
357
|
+
body = await request.json()
|
|
358
|
+
dialect, build = next(
|
|
359
|
+
(dialect, build)
|
|
360
|
+
for suffix, dialect, build in routes
|
|
361
|
+
if request.path.endswith(suffix)
|
|
362
|
+
)
|
|
363
|
+
raw = build(str(body.get("model", "scripted")))
|
|
364
|
+
if not dialect.streaming(body):
|
|
365
|
+
return web.json_response(raw)
|
|
366
|
+
response = web.StreamResponse(headers={"Content-Type": "text/event-stream"})
|
|
367
|
+
await response.prepare(request)
|
|
368
|
+
for event in dialect.stream_events(raw):
|
|
369
|
+
await response.write(event)
|
|
370
|
+
await response.write_eof()
|
|
371
|
+
return response
|
|
372
|
+
|
|
373
|
+
app = web.Application()
|
|
374
|
+
app.router.add_post("/{tail:.*}", answer)
|
|
375
|
+
runner = web.AppRunner(app)
|
|
376
|
+
await runner.setup()
|
|
377
|
+
site = web.TCPSite(runner, "127.0.0.1", 0)
|
|
378
|
+
await site.start()
|
|
379
|
+
port = site._server.sockets[0].getsockname()[1]
|
|
380
|
+
try:
|
|
381
|
+
yield {"base_url": f"http://127.0.0.1:{port}/v1"}
|
|
382
|
+
finally:
|
|
383
|
+
await runner.cleanup()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""agent-user-harness-v1: one turn of any harness running as a non-root agent user.
|
|
2
|
+
|
|
3
|
+
Root setup creates the task's `agent_user` (images normally ship one) and hands it the
|
|
4
|
+
workdir; the harness then installs, launches as that user, and answers a one-line prompt.
|
|
5
|
+
With the e2e suite's scripted upstream answering, the run is deterministic: it fails only if
|
|
6
|
+
the harness can't set up or run without root.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import verifiers.v1 as vf
|
|
10
|
+
from verifiers.v1.runtimes import Runtime
|
|
11
|
+
|
|
12
|
+
__all__ = ["AgentUserHarnessTaskset"]
|
|
13
|
+
|
|
14
|
+
AGENT_USER = "vf-agent"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentUserHarnessTask(vf.Task):
|
|
18
|
+
async def setup(self, trace: vf.Trace, runtime: Runtime) -> None:
|
|
19
|
+
# Stands in for the image, which normally ships the user and gives it the
|
|
20
|
+
# workdir: kimi-code, pi, and openclaw keep per-rollout state there.
|
|
21
|
+
setup = f"useradd -m {AGENT_USER} && chown -R {AGENT_USER} ."
|
|
22
|
+
result = await runtime.run(["sh", "-c", setup], {})
|
|
23
|
+
if result.exit_code != 0:
|
|
24
|
+
raise RuntimeError(f"root setup failed: {result.stderr}")
|
|
25
|
+
|
|
26
|
+
@vf.reward(weight=1.0)
|
|
27
|
+
async def answered(self, trace: vf.Trace) -> float:
|
|
28
|
+
return float(trace.num_turns >= 1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentUserHarnessTaskset(vf.Taskset[AgentUserHarnessTask, vf.TasksetConfig]):
|
|
32
|
+
def load(self) -> list[AgentUserHarnessTask]:
|
|
33
|
+
return [
|
|
34
|
+
AgentUserHarnessTask(
|
|
35
|
+
vf.TaskData(idx=0, prompt="Reply with OK.", agent_user=AGENT_USER)
|
|
36
|
+
)
|
|
37
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""agent-user-v1: a scripted, model-free agent that runs as a non-root user and tries root actions.
|
|
2
|
+
|
|
3
|
+
Root setup creates the task's `agent_user`, plants a root-only secret, and prepares a uv
|
|
4
|
+
script; the workdir stays root-owned, like a typical image WORKDIR. The scripted harness runs
|
|
5
|
+
as that user and records whether root-only actions are denied, whether its own uv script, its
|
|
6
|
+
runtime file I/O, and its colocated tool server all act as the user, and whether that server
|
|
7
|
+
can reach uv. The reward is 1 only if every check holds. Needs docker or prime.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import pwd
|
|
13
|
+
import shutil
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import verifiers.v1 as vf
|
|
17
|
+
from verifiers.v1.clients import ModelContext
|
|
18
|
+
from verifiers.v1.configs.harness import HarnessConfig
|
|
19
|
+
from verifiers.v1.errors import SandboxError
|
|
20
|
+
from verifiers.v1.harness import Harness
|
|
21
|
+
from verifiers.v1.mcp.launch import serve
|
|
22
|
+
from verifiers.v1.runtimes import ProgramResult, Runtime
|
|
23
|
+
|
|
24
|
+
__all__ = ["AgentUserTaskset", "ScriptedAgentUserHarness"]
|
|
25
|
+
|
|
26
|
+
AGENT_USER = "vf-agent"
|
|
27
|
+
SECRET = "/opt/vf-agent-user-secret"
|
|
28
|
+
REPORT = "/tmp/agent-user-report.json"
|
|
29
|
+
WRITTEN = "/home/vf-agent/agent-user-written.txt"
|
|
30
|
+
TOOL_USER = "/tmp/vf-agent-user-tool-user"
|
|
31
|
+
TOOL_UV = "/tmp/vf-agent-user-tool-uv"
|
|
32
|
+
ROOT_ACTIONS = ("read_secret", "write_etc", "chmod_secret", "signal_init")
|
|
33
|
+
|
|
34
|
+
SETUP = f"useradd -m {AGENT_USER} && echo secret > {SECRET} && chmod 600 {SECRET}"
|
|
35
|
+
|
|
36
|
+
UV_SCRIPT = """# /// script
|
|
37
|
+
# dependencies = []
|
|
38
|
+
# ///
|
|
39
|
+
import os, pwd, sys
|
|
40
|
+
print(pwd.getpwuid(os.getuid()).pw_name, sys.executable)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
PROBE = f"""
|
|
44
|
+
r() {{ "$@" >/dev/null 2>&1 && echo ok || echo denied; }}
|
|
45
|
+
printf '{{"user":"%s","secret_exists":"%s","read_secret":"%s","write_etc":"%s","chmod_secret":"%s","signal_init":"%s","write_home":"%s","written_owner":"%s","tool_user":"%s","tool_uv":"%s"}}' \\
|
|
46
|
+
"$(id -un)" "$(r test -e {SECRET})" "$(r cat {SECRET})" "$(r touch /etc/vf-agent-user-probe)" \\
|
|
47
|
+
"$(r chmod 644 {SECRET})" "$(r kill -0 1)" "$(r touch "$HOME/probe")" "$(stat -c %U {WRITTEN})" \\
|
|
48
|
+
"$(cat {TOOL_USER})" "$(cat {TOOL_UV})" > {REPORT}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def current_user() -> str:
|
|
53
|
+
return pwd.getpwuid(os.getuid()).pw_name
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AgentUserToolset(vf.Toolset[vf.ToolsetConfig]):
|
|
57
|
+
TOOL_PREFIX = "agentuser"
|
|
58
|
+
|
|
59
|
+
@vf.tool
|
|
60
|
+
def whoami(self) -> str:
|
|
61
|
+
"""Return the user this tool server runs as."""
|
|
62
|
+
return current_user()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ScriptedAgentUserHarness(Harness[HarnessConfig]):
|
|
66
|
+
SUPPORTS_MCP = True
|
|
67
|
+
|
|
68
|
+
async def launch(
|
|
69
|
+
self,
|
|
70
|
+
ctx: ModelContext,
|
|
71
|
+
trace: vf.Trace,
|
|
72
|
+
runtime: Runtime,
|
|
73
|
+
endpoint: str,
|
|
74
|
+
secret: str,
|
|
75
|
+
mcp_urls: dict[str, str],
|
|
76
|
+
data: vf.TaskData,
|
|
77
|
+
tool_interception_url: str | None = None,
|
|
78
|
+
) -> ProgramResult:
|
|
79
|
+
script = await runtime.prepare_uv_script(UV_SCRIPT)
|
|
80
|
+
uv_user, uv_python = (await runtime.run(script, {})).stdout.split()
|
|
81
|
+
await runtime.write(WRITTEN, b"written by the agent user")
|
|
82
|
+
try:
|
|
83
|
+
await runtime.read(SECRET)
|
|
84
|
+
read_api = "ok"
|
|
85
|
+
except SandboxError:
|
|
86
|
+
read_api = "denied"
|
|
87
|
+
trace.info["agent_user_harness"] = {
|
|
88
|
+
"uv_user": uv_user,
|
|
89
|
+
"uv_python": uv_python,
|
|
90
|
+
"uv_script": script[-1],
|
|
91
|
+
"read_secret_api": read_api,
|
|
92
|
+
}
|
|
93
|
+
return await runtime.run_program(["sh", "-c", PROBE], {})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AgentUserData(vf.TaskData):
|
|
97
|
+
preinstalled_tools: bool = False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AgentUserTask(vf.Task[AgentUserData]):
|
|
101
|
+
@classmethod
|
|
102
|
+
def toolsets(cls, config: vf.TaskConfig) -> list[vf.Toolset]:
|
|
103
|
+
return [AgentUserToolset(vf.ToolsetConfig(colocated=True))]
|
|
104
|
+
|
|
105
|
+
async def setup(self, trace: vf.Trace, runtime: Runtime) -> None:
|
|
106
|
+
result = await runtime.run(["sh", "-c", SETUP], {})
|
|
107
|
+
if result.exit_code != 0:
|
|
108
|
+
raise RuntimeError(f"root setup failed: {result.stderr}")
|
|
109
|
+
trace.info["root_uv_script"] = (await runtime.prepare_uv_script(UV_SCRIPT))[-1]
|
|
110
|
+
if self.data.preinstalled_tools:
|
|
111
|
+
# As an earlier rollout on a reused runtime would: the agent's server then
|
|
112
|
+
# finds its package already installed by the default user.
|
|
113
|
+
async with serve(
|
|
114
|
+
AgentUserToolset(vf.ToolsetConfig(colocated=True)), runtime
|
|
115
|
+
):
|
|
116
|
+
pass
|
|
117
|
+
await runtime.run(["rm", "-f", TOOL_USER, TOOL_UV], {})
|
|
118
|
+
|
|
119
|
+
@vf.reward(weight=1.0)
|
|
120
|
+
async def agent_user_contained(self, trace: vf.Trace, runtime: Runtime) -> float:
|
|
121
|
+
report = {
|
|
122
|
+
**json.loads(await runtime.read(REPORT)),
|
|
123
|
+
**trace.info["agent_user_harness"],
|
|
124
|
+
"root_uv_script": trace.info["root_uv_script"],
|
|
125
|
+
}
|
|
126
|
+
trace.info["agent_user_report"] = report
|
|
127
|
+
users = ("user", "uv_user", "written_owner", "tool_user")
|
|
128
|
+
return float(
|
|
129
|
+
all(report[key] == AGENT_USER for key in users)
|
|
130
|
+
and report["secret_exists"] == report["write_home"] == "ok"
|
|
131
|
+
and all(report[action] == "denied" for action in ROOT_ACTIONS)
|
|
132
|
+
and report["read_secret_api"] == "denied"
|
|
133
|
+
and report["tool_uv"] == "ok"
|
|
134
|
+
and not report["uv_python"].startswith("/root/")
|
|
135
|
+
and report["uv_script"] != report["root_uv_script"]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AgentUserConfig(vf.TasksetConfig):
|
|
140
|
+
preinstalled_tools: bool = False
|
|
141
|
+
"""Bring the colocated tool up once as the default user before the agent's run."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AgentUserTaskset(vf.Taskset[AgentUserTask, AgentUserConfig]):
|
|
145
|
+
def load(self) -> list[AgentUserTask]:
|
|
146
|
+
return [
|
|
147
|
+
AgentUserTask(
|
|
148
|
+
AgentUserData(
|
|
149
|
+
idx=0,
|
|
150
|
+
prompt="Scripted: try root-only actions as a non-root user.",
|
|
151
|
+
agent_user=AGENT_USER,
|
|
152
|
+
preinstalled_tools=self.config.preinstalled_tools,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
Path(TOOL_USER).write_text(current_user())
|
|
160
|
+
Path(TOOL_UV).write_text("ok" if shutil.which("uv") else "missing")
|
|
161
|
+
AgentUserToolset.run()
|
|
@@ -190,12 +190,40 @@ USER_RUNTIMES = [
|
|
|
190
190
|
pytest.param("e2b", marks=[mark.e2b], id="harness-in-e2b"),
|
|
191
191
|
]
|
|
192
192
|
|
|
193
|
-
# Runtimes that can switch users (`Runtime.with_user`).
|
|
194
193
|
AGENT_USER_RUNTIMES = [
|
|
195
194
|
pytest.param("docker", marks=[mark.docker], id="harness-in-docker"),
|
|
196
195
|
pytest.param("prime", marks=[mark.prime], id="harness-in-prime"),
|
|
197
196
|
]
|
|
198
197
|
|
|
198
|
+
|
|
199
|
+
# Boxed harnesses (terminus-2 drives the host tmux; browser-use needs its own image).
|
|
200
|
+
def _agent_user_placements():
|
|
201
|
+
harnesses = [
|
|
202
|
+
("null", "null"),
|
|
203
|
+
("bash", "bash"),
|
|
204
|
+
("rlm", "rlm"),
|
|
205
|
+
("codex", "codex"),
|
|
206
|
+
("claude-code", "claude_code"),
|
|
207
|
+
("hermes-agent", "hermes_agent"),
|
|
208
|
+
({"id": "kimi-code", "transport": "responses"}, "kimi_code"),
|
|
209
|
+
({"id": "pi", "transport": "responses"}, "pi"),
|
|
210
|
+
("openclaw", "openclaw"),
|
|
211
|
+
("mini-swe-agent", None),
|
|
212
|
+
("prime-agent", None),
|
|
213
|
+
]
|
|
214
|
+
for harness, harness_mark in harnesses:
|
|
215
|
+
name = harness if isinstance(harness, str) else harness["id"]
|
|
216
|
+
for runtime in ("docker", "prime"):
|
|
217
|
+
if name == "prime-agent" and runtime != "prime":
|
|
218
|
+
continue
|
|
219
|
+
marks = [getattr(mark, runtime)]
|
|
220
|
+
if harness_mark:
|
|
221
|
+
marks.append(getattr(mark, harness_mark))
|
|
222
|
+
yield pytest.param(harness, runtime, marks=marks, id=f"{name}-in-{runtime}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
AGENT_USER_HARNESS_PLACEMENTS = list(_agent_user_placements())
|
|
226
|
+
|
|
199
227
|
# ACP-backed harnesses: each must preserve an exchange across interaction segments and
|
|
200
228
|
# retain MCP access after resuming. Cover every harness in the local container runtime,
|
|
201
229
|
# plus remote placements for the sandbox/tunnel and native-process boundaries.
|
|
@@ -339,28 +367,59 @@ async def test_browser_use(run_v1, tmp_path):
|
|
|
339
367
|
|
|
340
368
|
|
|
341
369
|
@pytest.mark.e2e
|
|
370
|
+
@pytest.mark.parametrize(
|
|
371
|
+
"preinstalled_tools", [False, True], ids=["fresh", "preinstalled"]
|
|
372
|
+
)
|
|
342
373
|
@pytest.mark.parametrize("harness_runtime", AGENT_USER_RUNTIMES, indirect=True)
|
|
343
|
-
async def test_agent_user_cannot_act_as_root(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
denied
|
|
374
|
+
async def test_agent_user_cannot_act_as_root(
|
|
375
|
+
run_v1, harness_runtime, preinstalled_tools, tmp_path
|
|
376
|
+
):
|
|
377
|
+
"""A scripted, model-free agent runs as the task's non-root `agent_user`. Root-only
|
|
378
|
+
actions are denied, while its uv script, runtime file I/O, and colocated tool server
|
|
379
|
+
all act as the user. With `preinstalled_tools`, the tool's package was first installed
|
|
380
|
+
by the default user, as on a reused runtime."""
|
|
348
381
|
(trace,) = await run_v1(
|
|
349
382
|
"agent-user-v1",
|
|
350
383
|
harness="agent-user-v1",
|
|
351
384
|
runtime={"type": harness_runtime},
|
|
352
385
|
output_dir=tmp_path,
|
|
386
|
+
taskset_overrides={"preinstalled_tools": preinstalled_tools},
|
|
353
387
|
)
|
|
354
388
|
report = trace.info["agent_user_report"]
|
|
355
|
-
|
|
356
|
-
|
|
389
|
+
for key in ("user", "uv_user", "written_owner", "tool_user"):
|
|
390
|
+
assert report[key] == "vf-agent", key
|
|
391
|
+
assert report["secret_exists"] == "ok" and report["write_home"] == "ok"
|
|
357
392
|
root_actions = ("read_secret", "write_etc", "chmod_secret", "signal_init")
|
|
358
393
|
assert {action: report[action] for action in root_actions} == dict.fromkeys(
|
|
359
394
|
root_actions, "denied"
|
|
360
395
|
)
|
|
396
|
+
assert report["read_secret_api"] == "denied"
|
|
397
|
+
assert report["tool_uv"] == "ok"
|
|
398
|
+
assert not report["uv_python"].startswith("/root/")
|
|
399
|
+
assert report["uv_script"] != report["root_uv_script"]
|
|
361
400
|
assert trace.reward == 1.0
|
|
362
401
|
|
|
363
402
|
|
|
403
|
+
@pytest.mark.e2e
|
|
404
|
+
@pytest.mark.parametrize(
|
|
405
|
+
"harness,harness_runtime", AGENT_USER_HARNESS_PLACEMENTS, indirect=True
|
|
406
|
+
)
|
|
407
|
+
async def test_agent_user_harness(
|
|
408
|
+
run_v1, harness, harness_runtime, scripted_model, tmp_path
|
|
409
|
+
):
|
|
410
|
+
"""Each harness installs and runs a turn as a non-root agent user against a scripted
|
|
411
|
+
upstream, so the run fails only if the harness can't set up or run without root."""
|
|
412
|
+
(trace,) = await run_v1(
|
|
413
|
+
"agent-user-harness-v1",
|
|
414
|
+
harness=harness,
|
|
415
|
+
runtime={"type": harness_runtime},
|
|
416
|
+
output_dir=tmp_path,
|
|
417
|
+
client=scripted_model,
|
|
418
|
+
)
|
|
419
|
+
assert trace.ok, [error.message for error in trace.errors]
|
|
420
|
+
assert trace.num_turns >= 1
|
|
421
|
+
|
|
422
|
+
|
|
364
423
|
@pytest.mark.e2e
|
|
365
424
|
@pytest.mark.parametrize("harness_runtime", USER_RUNTIMES, indirect=True)
|
|
366
425
|
async def test_user(run_v1, harness_runtime, tmp_path):
|
|
@@ -50,7 +50,16 @@ async def ensure_installed(
|
|
|
50
50
|
f"mkdir -p {shlex.quote(directory)} && "
|
|
51
51
|
f'if l=$(command -v flock || command -v lockf); then "$l" {lock} {run}; else {spinlock}; fi'
|
|
52
52
|
)
|
|
53
|
-
|
|
53
|
+
if runtime.user is None:
|
|
54
|
+
result = await runtime.run(["sh", "-c", guarded], env)
|
|
55
|
+
else:
|
|
56
|
+
# Install as root (system packages), keeping uv's Python in `directory`, then
|
|
57
|
+
# open it to the agent user: some installers leave it private.
|
|
58
|
+
python_dir = {"UV_PYTHON_INSTALL_DIR": f"{directory}/python"}
|
|
59
|
+
shareable = f"{guarded} && chmod -R a+rX {shlex.quote(directory)}"
|
|
60
|
+
result = await runtime.with_user(None).run(
|
|
61
|
+
["sh", "-c", shareable], {**python_dir, **env}
|
|
62
|
+
)
|
|
54
63
|
if result.exit_code != 0:
|
|
55
64
|
detail = (result.stderr.strip() or result.stdout.strip())[-500:]
|
|
56
65
|
raise RuntimeError(f"{label} install failed: {detail}")
|
|
@@ -234,7 +234,9 @@ async def _cached_package(src: Path) -> tuple[str, bytes]:
|
|
|
234
234
|
del _PACKAGE_BUILD_STATES[key]
|
|
235
235
|
|
|
236
236
|
|
|
237
|
-
async def _install_in_sandbox(server: ServerBase,
|
|
237
|
+
async def _install_in_sandbox(server: ServerBase, agent: Runtime) -> str:
|
|
238
|
+
# Install as root; the agent user only runs the server.
|
|
239
|
+
runtime = agent if agent.user is None else agent.with_user(None)
|
|
238
240
|
# Prime VMs mount /tmp as a small tmpfs, while the runtime workdir lives on
|
|
239
241
|
# the VM's root disk. Keep source, build scratch space, and uv's cache on the
|
|
240
242
|
# durable runtime filesystem so ordinary dependency installs cannot exhaust
|
|
@@ -244,35 +246,51 @@ async def _install_in_sandbox(server: ServerBase, runtime: Runtime) -> str:
|
|
|
244
246
|
temp = str(PurePosixPath(workdir) / ".vf-tmp")
|
|
245
247
|
cache = str(PurePosixPath(workdir) / ".vf-uv-cache")
|
|
246
248
|
venv = str(PurePosixPath(workdir) / ".vf-venv")
|
|
249
|
+
python_dir = str(PurePosixPath(workdir) / ".vf-python")
|
|
247
250
|
root_q, temp_q, cache_q, venv_q = map(shlex.quote, (root, temp, cache, venv))
|
|
248
251
|
# Colocated servers and borrowed views install into one physical environment.
|
|
249
252
|
# Serialize its mutations and only remember sources after a successful install.
|
|
250
253
|
async with runtime._mcp_install_lock:
|
|
251
254
|
sources = dict.fromkeys((_package_dir(ServerBase), _package_dir(type(server))))
|
|
252
255
|
pending = [source for source in sources if source not in runtime._mcp_sources]
|
|
253
|
-
if
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
'export PATH="$HOME/.local/bin:$PATH"; '
|
|
259
|
-
)
|
|
260
|
-
if not runtime._mcp_sources:
|
|
261
|
-
# Failed installs can leave the venv behind; retain it when retrying.
|
|
262
|
-
setup += f"{_ENSURE_UV}; uv venv --allow-existing {venv_q}; "
|
|
263
|
-
# Drain remote writes and installs before cancellation releases the lock.
|
|
264
|
-
for source in pending:
|
|
265
|
-
name, data = await _cached_package(Path(source))
|
|
266
|
-
remote = f"{root}/{name}"
|
|
267
|
-
await run_shielded(runtime.write(remote, data))
|
|
268
|
-
setup += f"uv pip install --python {venv_q} {shlex.quote(remote)}; "
|
|
269
|
-
result = await run_shielded(runtime.run(["sh", "-c", setup], {}))
|
|
270
|
-
if result.exit_code != 0:
|
|
271
|
-
raise ToolsetError(
|
|
272
|
-
f"server {server.server_name!r} install failed in runtime: "
|
|
273
|
-
f"{(result.stderr or result.stdout).strip()[-2000:]}"
|
|
256
|
+
if pending:
|
|
257
|
+
setup = (
|
|
258
|
+
f"set -e; mkdir -p {root_q} {temp_q} {cache_q}; "
|
|
259
|
+
f"export TMPDIR={temp_q} UV_CACHE_DIR={cache_q}; "
|
|
260
|
+
'export PATH="$HOME/.local/bin:$PATH"; '
|
|
274
261
|
)
|
|
275
|
-
|
|
262
|
+
if agent.user is not None:
|
|
263
|
+
setup += f"export UV_PYTHON_INSTALL_DIR={shlex.quote(python_dir)}; "
|
|
264
|
+
if not runtime._mcp_sources:
|
|
265
|
+
# Failed installs can leave the venv behind; retain it when retrying.
|
|
266
|
+
setup += f"{_ENSURE_UV}; uv venv --allow-existing {venv_q}; "
|
|
267
|
+
# Drain remote writes and installs before cancellation releases the lock.
|
|
268
|
+
for source in pending:
|
|
269
|
+
name, data = await _cached_package(Path(source))
|
|
270
|
+
remote = f"{root}/{name}"
|
|
271
|
+
await run_shielded(runtime.write(remote, data))
|
|
272
|
+
setup += f"uv pip install --python {venv_q} {shlex.quote(remote)}; "
|
|
273
|
+
result = await run_shielded(runtime.run(["sh", "-c", setup], {}))
|
|
274
|
+
if result.exit_code != 0:
|
|
275
|
+
raise ToolsetError(
|
|
276
|
+
f"server {server.server_name!r} install failed in runtime: "
|
|
277
|
+
f"{(result.stderr or result.stdout).strip()[-2000:]}"
|
|
278
|
+
)
|
|
279
|
+
runtime._mcp_sources.update(pending)
|
|
280
|
+
if agent.user is not None:
|
|
281
|
+
# Root's uv lives in its home; copy it into the venv, open to the agent.
|
|
282
|
+
python_q = shlex.quote(python_dir)
|
|
283
|
+
share = (
|
|
284
|
+
'set -e; export PATH="$HOME/.local/bin:$PATH"; '
|
|
285
|
+
f'cp "$(command -v uv)" {venv_q}/bin/uv; chmod -R a+rX {venv_q}; '
|
|
286
|
+
f"[ ! -d {python_q} ] || chmod -R a+rX {python_q}"
|
|
287
|
+
)
|
|
288
|
+
result = await run_shielded(runtime.run(["sh", "-c", share], {}))
|
|
289
|
+
if result.exit_code != 0:
|
|
290
|
+
raise ToolsetError(
|
|
291
|
+
f"server {server.server_name!r} could not be shared with "
|
|
292
|
+
f"{agent.user!r}: {(result.stderr or result.stdout).strip()[-2000:]}"
|
|
293
|
+
)
|
|
276
294
|
return f"{venv}/bin/python"
|
|
277
295
|
|
|
278
296
|
|
|
@@ -338,12 +356,18 @@ async def serve_in_runtime(
|
|
|
338
356
|
command = [python, "-m", type(server).__module__]
|
|
339
357
|
if runtime.type != "subprocess":
|
|
340
358
|
# Providers may invoke uv after the install shell exits, so preserve its PATH.
|
|
359
|
+
path = "$HOME/.local/bin:$PATH"
|
|
360
|
+
if runtime.user is not None:
|
|
361
|
+
path = f"{PurePosixPath(python).parent}:{path}"
|
|
341
362
|
command = [
|
|
342
363
|
"sh",
|
|
343
364
|
"-c",
|
|
344
|
-
f'export PATH="
|
|
365
|
+
f'export PATH="{path}"; exec {shlex.join(command)}',
|
|
345
366
|
]
|
|
346
367
|
log = f"vf_tool_{server.server_name}.log"
|
|
368
|
+
if runtime.user is not None:
|
|
369
|
+
# The agent user may not be able to write the workdir.
|
|
370
|
+
log = f"/tmp/vf_tool_{server.server_name}-{uuid.uuid4().hex[:8]}.log"
|
|
347
371
|
await runtime.run_background(command, env, log)
|
|
348
372
|
if fixed is not None:
|
|
349
373
|
port = fixed
|
|
@@ -59,7 +59,7 @@ _HOME_PROBE = (
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
async def
|
|
62
|
+
async def resolve_agent_runtime(runtime: Runtime, user: str | None) -> Runtime:
|
|
63
63
|
"""The runtime the harness runs on: `runtime` itself, or a view running as `user`
|
|
64
64
|
with `HOME` set to its home. `user` must exist with a home directory it can write
|
|
65
65
|
(task authors ship it in the image), and the view must really run as it, so a
|
|
@@ -72,8 +72,7 @@ async def agent_runtime(runtime: Runtime, user: str | None) -> Runtime:
|
|
|
72
72
|
raise TaskError(f"agent_user {user!r} does not exist in the image")
|
|
73
73
|
if lookup.exit_code != 0:
|
|
74
74
|
raise TaskError(f"agent_user {user!r} has no home directory ({home})")
|
|
75
|
-
view = runtime.with_user(user)
|
|
76
|
-
view = view.with_env({**view.env, "HOME": home})
|
|
75
|
+
view = runtime.with_user(user, home=home)
|
|
77
76
|
probe = await view.run(["sh", "-c", 'id -un; test -w "$HOME" && echo writable'], {})
|
|
78
77
|
effective, *rest = probe.stdout.split() or [""]
|
|
79
78
|
if effective != user:
|
|
@@ -168,6 +167,7 @@ class Rollout:
|
|
|
168
167
|
self._endpoint: str | None = None
|
|
169
168
|
self._urls: dict[str, str] = {}
|
|
170
169
|
self._harness_session: HarnessSession | None = None
|
|
170
|
+
self._agent_runtime: Runtime | None = None
|
|
171
171
|
self.deadline_at: float | None = None
|
|
172
172
|
"""The active harness segment's absolute deadline (event-loop clock), or
|
|
173
173
|
None between segments / when unbounded. An interaction spends one cumulative
|
|
@@ -279,7 +279,13 @@ class Rollout:
|
|
|
279
279
|
asyncio.timeout_at(setup_deadline) as setup_timeout,
|
|
280
280
|
boundary(HarnessError, "harness setup"),
|
|
281
281
|
):
|
|
282
|
-
|
|
282
|
+
# The harness, its colocated tools, and its session run as the task's
|
|
283
|
+
# `agent_user` (just `runtime` when unset). Task setup and scoring,
|
|
284
|
+
# interception, and the execution policy keep the default `runtime`.
|
|
285
|
+
self._agent_runtime = await resolve_agent_runtime(
|
|
286
|
+
runtime, self.task.data.agent_user
|
|
287
|
+
)
|
|
288
|
+
await self.harness.setup(self._agent_runtime)
|
|
283
289
|
async with boundary(ToolsetError, "building tool servers"):
|
|
284
290
|
toolsets = self.task.toolsets(self.task.config)
|
|
285
291
|
# `base_url` is the interception server's reachable URL for this rollout.
|
|
@@ -304,7 +310,7 @@ class Rollout:
|
|
|
304
310
|
self._urls = await self._stack.enter_async_context(
|
|
305
311
|
serve_tools(
|
|
306
312
|
toolsets,
|
|
307
|
-
|
|
313
|
+
self._agent_runtime,
|
|
308
314
|
shared=self._shared_tools,
|
|
309
315
|
state_secret=state_secret,
|
|
310
316
|
state_route=self.trace.id,
|
|
@@ -415,7 +421,7 @@ class Rollout:
|
|
|
415
421
|
self._harness_session = await self.harness.session(
|
|
416
422
|
self.ctx,
|
|
417
423
|
self.trace,
|
|
418
|
-
|
|
424
|
+
self._agent_runtime,
|
|
419
425
|
self._endpoint,
|
|
420
426
|
self._secret,
|
|
421
427
|
self._urls,
|
|
@@ -606,7 +612,7 @@ class Rollout:
|
|
|
606
612
|
# Cross-trace judgement runs later, after the runtime is gone.
|
|
607
613
|
await asyncio.gather(
|
|
608
614
|
self.task.score(trace, runtime),
|
|
609
|
-
self.harness.score(trace, runtime),
|
|
615
|
+
self.harness.score(trace, self._agent_runtime or runtime),
|
|
610
616
|
)
|
|
611
617
|
except TimeoutError:
|
|
612
618
|
self.timeout("scoring")
|