verifiers 0.4.1.dev41__tar.gz → 0.4.1.dev42__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.
Files changed (276) hide show
  1. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/PKG-INFO +2 -2
  2. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/pyproject.toml +1 -1
  3. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/conftest.py +4 -1
  4. verifiers-0.4.1.dev42/tests/integration/fixtures/fetch_tool_v1.py +36 -0
  5. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/pyproject.toml +1 -0
  6. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/grid.py +7 -1
  7. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_shared_tool.py +1 -1
  8. verifiers-0.4.1.dev42/tests/integration/test_tool_vms.py +88 -0
  9. verifiers-0.4.1.dev42/tests/unit/runtimes/test_private_network.py +254 -0
  10. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/mcp/launch.py +28 -9
  11. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/mcp/toolset.py +3 -0
  12. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/rollout.py +25 -6
  13. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/deployment.py +19 -5
  14. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/prime.py +43 -16
  15. verifiers-0.4.1.dev42/verifiers/v1/runtimes/private_network.py +148 -0
  16. verifiers-0.4.1.dev42/verifiers/v1/runtimes/strategies.py +119 -0
  17. verifiers-0.4.1.dev41/verifiers/v1/runtimes/strategies.py +0 -64
  18. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/.gitignore +0 -0
  19. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/LICENSE +0 -0
  20. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/README.md +0 -0
  21. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/agent_user_harness_v1.py +0 -0
  22. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/agent_user_v1.py +0 -0
  23. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/counter_tool_v1.py +0 -0
  24. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/duet_v1.py +0 -0
  25. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_acp_resume_v1.py +0 -0
  26. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_agentic_v1.py +0 -0
  27. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_tool_compose/docker-compose.yaml +0 -0
  28. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_tool_v1.py +0 -0
  29. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_user_sim_v1.py +0 -0
  30. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/echo_v1.py +0 -0
  31. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/network_policy_v1.py +0 -0
  32. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/skill_codeword_v1.py +0 -0
  33. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/skills/project-codeword/SKILL.md +0 -0
  34. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/fixtures/tool_response_image_v1.py +0 -0
  35. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_agent_user.py +0 -0
  36. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_agentic_judge.py +0 -0
  37. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_compose_scoring.py +0 -0
  38. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_examples.py +0 -0
  39. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_limits.py +0 -0
  40. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_multi_agent.py +0 -0
  41. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_multi_turn.py +0 -0
  42. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_multi_turn_tool.py +0 -0
  43. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_multimodal_tool.py +0 -0
  44. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_network_policy.py +0 -0
  45. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_resume_tool.py +0 -0
  46. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_rubric_judge.py +0 -0
  47. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_sampling.py +0 -0
  48. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_single_turn.py +0 -0
  49. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_skills.py +0 -0
  50. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_stateful_tool.py +0 -0
  51. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_user_echo.py +0 -0
  52. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/integration/test_user_sim.py +0 -0
  53. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/cli/test_replay.py +0 -0
  54. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/conftest.py +0 -0
  55. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/harnesses/test_core.py +0 -0
  56. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/harnesses/test_rlm.py +0 -0
  57. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/interception/test_tunnel.py +0 -0
  58. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/judges/conftest.py +0 -0
  59. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/judges/test_judge.py +0 -0
  60. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/judges/test_reference.py +0 -0
  61. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/judges/test_rubric.py +0 -0
  62. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/runtimes/test_e2b.py +0 -0
  63. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/serve/test_client.py +0 -0
  64. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/serve/test_delta.py +0 -0
  65. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/serve/test_pool.py +0 -0
  66. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_agent.py +0 -0
  67. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_dialects.py +0 -0
  68. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_graph.py +0 -0
  69. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_semantic.py +0 -0
  70. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_task.py +0 -0
  71. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_taskset.py +0 -0
  72. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/test_trace.py +0 -0
  73. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/utils/test_compile.py +0 -0
  74. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/utils/test_loaders.py +0 -0
  75. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/utils/test_platform.py +0 -0
  76. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/tests/unit/utils/test_score.py +0 -0
  77. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/__init__.py +0 -0
  78. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/__init__.py +0 -0
  79. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/acp/__init__.py +0 -0
  80. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/acp/runner.py +0 -0
  81. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/agent.py +0 -0
  82. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/__init__.py +0 -0
  83. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  84. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/dashboard/base.py +0 -0
  85. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/dashboard/eval.py +0 -0
  86. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/dashboard/replay.py +0 -0
  87. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/dashboard/validate.py +0 -0
  88. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/debug.py +0 -0
  89. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/eval/__init__.py +0 -0
  90. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/eval/hint.py +0 -0
  91. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/eval/main.py +0 -0
  92. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/eval/resume.py +0 -0
  93. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/eval/runner.py +0 -0
  94. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/gepa.py +0 -0
  95. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/output.py +0 -0
  96. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/replay.py +0 -0
  97. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/resolve.py +0 -0
  98. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/resume.py +0 -0
  99. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/cli/validate.py +0 -0
  100. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/clients/__init__.py +0 -0
  101. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/clients/base.py +0 -0
  102. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/clients/client.py +0 -0
  103. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/clients/eval.py +0 -0
  104. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/clients/train.py +0 -0
  105. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/__init__.py +0 -0
  106. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/agent.py +0 -0
  107. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/__init__.py +0 -0
  108. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/debug.py +0 -0
  109. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/env.py +0 -0
  110. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/eval.py +0 -0
  111. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/replay.py +0 -0
  112. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/cli/validate.py +0 -0
  113. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/client.py +0 -0
  114. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/env.py +0 -0
  115. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/harness.py +0 -0
  116. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/judge.py +0 -0
  117. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/retries.py +0 -0
  118. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/runtime.py +0 -0
  119. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/scoring.py +0 -0
  120. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/select.py +0 -0
  121. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/serve.py +0 -0
  122. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/task.py +0 -0
  123. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/configs/taskset.py +0 -0
  124. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/dialects/__init__.py +0 -0
  125. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/dialects/anthropic.py +0 -0
  126. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/dialects/base.py +0 -0
  127. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/dialects/chat.py +0 -0
  128. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/dialects/responses.py +0 -0
  129. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/env.py +0 -0
  130. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/__init__.py +0 -0
  131. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  132. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  133. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  134. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/best_of_n/env.py +0 -0
  135. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  136. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/single_agent/env.py +0 -0
  137. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  138. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/envs/user_sim/env.py +0 -0
  139. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/episode.py +0 -0
  140. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/errors.py +0 -0
  141. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/__init__.py +0 -0
  142. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/adapter.py +0 -0
  143. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/config.py +0 -0
  144. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/dataset.py +0 -0
  145. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/reflection.py +0 -0
  146. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/gepa/runner.py +0 -0
  147. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/grading.py +0 -0
  148. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/graph.py +0 -0
  149. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harness.py +0 -0
  150. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/__init__.py +0 -0
  151. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  152. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/bash/harness.py +0 -0
  153. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  154. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  155. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  156. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  157. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  158. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  159. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
  160. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/codex/harness.py +0 -0
  161. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/deepseek/__init__.py +0 -0
  162. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/deepseek/gate.mjs +0 -0
  163. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/deepseek/harness.py +0 -0
  164. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/deepseek/usage.mjs +0 -0
  165. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  166. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  167. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  168. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  169. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  170. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  171. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  172. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  173. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/node.py +0 -0
  174. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/null/__init__.py +0 -0
  175. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/null/harness.py +0 -0
  176. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  177. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  178. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/opencode/__init__.py +0 -0
  179. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/opencode/harness.py +0 -0
  180. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/opencode/mcp-ready.js +0 -0
  181. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  182. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
  183. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/pi/harness.py +0 -0
  184. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  185. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  186. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  187. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  188. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  189. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  190. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  191. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  192. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  193. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/core.py +0 -0
  194. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/install.py +0 -0
  195. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/launch.py +0 -0
  196. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  197. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/harnesses/utils/replay.py +0 -0
  198. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/__init__.py +0 -0
  199. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/base.py +0 -0
  200. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/pool.py +0 -0
  201. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/server.py +0 -0
  202. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  203. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/tunnel/base.py +0 -0
  204. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/tunnel/custom.py +0 -0
  205. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/interception/tunnel/prime.py +0 -0
  206. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judge.py +0 -0
  207. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/__init__.py +0 -0
  208. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/decision.py +0 -0
  209. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/reference.py +0 -0
  210. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/reference.txt +0 -0
  211. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/rubric.py +0 -0
  212. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/judges/rubric.txt +0 -0
  213. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/mcp/__init__.py +0 -0
  214. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/mcp/server.py +0 -0
  215. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/__init__.py +0 -0
  216. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/apptainer.py +0 -0
  217. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/base.py +0 -0
  218. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/compose.py +0 -0
  219. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/container.py +0 -0
  220. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  221. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/docker/egress.py +0 -0
  222. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/e2b.py +0 -0
  223. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/limiters.py +0 -0
  224. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/modal.py +0 -0
  225. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/runtime.py +0 -0
  226. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/runtimes/subprocess.py +0 -0
  227. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/semantic.py +0 -0
  228. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/__init__.py +0 -0
  229. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/client.py +0 -0
  230. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/delta.py +0 -0
  231. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/encoding.py +0 -0
  232. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/pool.py +0 -0
  233. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/server.py +0 -0
  234. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/serve/types.py +0 -0
  235. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/session.py +0 -0
  236. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/state.py +0 -0
  237. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/task.py +0 -0
  238. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/taskset.py +0 -0
  239. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/__init__.py +0 -0
  240. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  241. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  242. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  243. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  244. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  245. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  246. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  247. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  248. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  249. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  250. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  251. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  252. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/trace.py +0 -0
  253. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/types.py +0 -0
  254. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/__init__.py +0 -0
  255. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/aio.py +0 -0
  256. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/artifacts.py +0 -0
  257. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/compile.py +0 -0
  258. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/decorators.py +0 -0
  259. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/env.py +0 -0
  260. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/eval.py +0 -0
  261. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/format.py +0 -0
  262. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/generic.py +0 -0
  263. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/git.py +0 -0
  264. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/image.py +0 -0
  265. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/interrupt.py +0 -0
  266. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/loaders.py +0 -0
  267. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/logging.py +0 -0
  268. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/memory.py +0 -0
  269. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/paths.py +0 -0
  270. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/platform.py +0 -0
  271. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/prime.py +0 -0
  272. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/retries.py +0 -0
  273. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/scope.py +0 -0
  274. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/score.py +0 -0
  275. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/trace_store.py +0 -0
  276. {verifiers-0.4.1.dev41 → verifiers-0.4.1.dev42}/verifiers/v1/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: verifiers
3
- Version: 0.4.1.dev41
3
+ Version: 0.4.1.dev42
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
@@ -36,7 +36,7 @@ Requires-Dist: numpy>=2.1.0
36
36
  Requires-Dist: openai<3.0.0,>=2.54.0
37
37
  Requires-Dist: prime-pydantic-config[toml]>=0.4.3
38
38
  Requires-Dist: prime-runs>=0.1.4
39
- Requires-Dist: prime-sandboxes>=0.4.2
39
+ Requires-Dist: prime-sandboxes>=0.4.7
40
40
  Requires-Dist: prime-tunnel>=0.1.14
41
41
  Requires-Dist: pydantic>=2.12.3
42
42
  Requires-Dist: pyzmq>=27.1.0
@@ -33,7 +33,7 @@ dependencies = [
33
33
  "math-verify>=0.8.0",
34
34
  "openai>=2.54.0,<3.0.0",
35
35
  "prime-tunnel>=0.1.14",
36
- "prime-sandboxes>=0.4.2",
36
+ "prime-sandboxes>=0.4.7",
37
37
  "prime-runs>=0.1.4",
38
38
  "pydantic>=2.12.3",
39
39
  "rich>=11.0.0",
@@ -46,11 +46,14 @@ def harness_runtime(request) -> dict:
46
46
  @pytest.fixture
47
47
  def tool_runtime(request) -> dict:
48
48
  """A `taskset.task.tools` override placing the tool server: `colocated` (inside the harness's
49
- runtime), `service` (in the task's `tools` Compose service) or its own runtime, by type."""
49
+ runtime), `service` (in the task's `tools` Compose service), `service-vm` (in the task's
50
+ `tools-vm` service VM) or its own runtime, by type."""
50
51
  if request.param == "colocated":
51
52
  return {"colocated": True}
52
53
  if request.param == "service":
53
54
  return {"service": "tools"}
55
+ if request.param == "service-vm":
56
+ return {"service": "tools-vm"}
54
57
  return {"runtime": {"type": request.param}}
55
58
 
56
59
 
@@ -0,0 +1,36 @@
1
+ """fetch (v1, MCP tool): a tool server on a Prime VM of its own, under its own egress.
2
+
3
+ The integration test places a `FetchToolset` on a Prime runtime of its own
4
+ (`tools.runtime`) beside an offline workspace. The server's VM joins the workspace over
5
+ the rollout's private network and keeps the egress policy of its own runtime.
6
+ """
7
+
8
+ import urllib.request
9
+
10
+ import verifiers.v1 as vf
11
+
12
+
13
+ class FetchToolset(vf.Toolset[vf.ToolsetConfig]):
14
+ TOOL_PREFIX = "fetch"
15
+
16
+ @vf.tool
17
+ def fetch(self, url: str) -> str:
18
+ """Fetch a URL; return its HTTP status, or the type of the error."""
19
+ try:
20
+ return str(urllib.request.urlopen(url, timeout=15).status)
21
+ except (OSError, ValueError) as error: # URLError and timeouts are OSErrors
22
+ return type(error).__name__
23
+
24
+
25
+ class FetchToolTaskConfig(vf.TaskConfig):
26
+ tools: vf.ToolsetConfig = vf.ToolsetConfig()
27
+
28
+
29
+ class FetchToolTask(vf.Task[vf.TaskData, vf.State, FetchToolTaskConfig]):
30
+ @classmethod
31
+ def toolsets(cls, config: FetchToolTaskConfig) -> list[vf.Toolset]:
32
+ return [FetchToolset(config.tools)]
33
+
34
+
35
+ if __name__ == "__main__":
36
+ FetchToolset.run()
@@ -18,6 +18,7 @@ include = [
18
18
  "counter_tool_v1.py",
19
19
  "echo_acp_resume_v1.py",
20
20
  "echo_tool_v1.py",
21
+ "fetch_tool_v1.py",
21
22
  "tool_response_image_v1.py",
22
23
  "pyproject.toml",
23
24
  ]
@@ -240,9 +240,13 @@ def skip_harness(cells: list, harness: str, reason: str) -> list:
240
240
  def placements(
241
241
  harness_runtimes: tuple[str, ...] = tuple(RUNTIMES),
242
242
  tool_runtimes: tuple[str, ...] = ("colocated", *RUNTIMES),
243
+ *,
244
+ shared: bool = False,
243
245
  ) -> list:
244
246
  """`(harness_runtime, tool_runtime)` cells for a tool server placed colocated in the
245
- harness's box, in a Compose service beside it (`service`), or in its own runtime."""
247
+ harness's box, in a Compose service beside it (`service`), or in its own runtime
248
+ (on Prime beside a Prime harness, a VM on the rollout's private network). A `shared`
249
+ server runs once per eval, so it joins no rollout's network."""
246
250
  cells = []
247
251
  for harness_runtime in harness_runtimes:
248
252
  for tool_runtime in tool_runtimes:
@@ -264,6 +268,8 @@ def placements(
264
268
  elif (
265
269
  tool_runtime not in ("colocated", "service")
266
270
  and not runtime_cls(tool_runtime).supports_expose
271
+ # Beside a Prime harness, a rollout's Prime server joins its private network.
272
+ and not (not shared and tool_runtime == harness_runtime == "prime")
267
273
  ):
268
274
  marks.append(
269
275
  pytest.mark.skip(
@@ -8,7 +8,7 @@ from grid import HARNESS_RUNTIMES, placements
8
8
 
9
9
  @pytest.mark.parametrize(
10
10
  "harness_runtime,tool_runtime",
11
- placements(tool_runtimes=tuple(HARNESS_RUNTIMES)),
11
+ placements(tool_runtimes=tuple(HARNESS_RUNTIMES), shared=True),
12
12
  indirect=True,
13
13
  )
14
14
  async def test_shared_tool(run, harness_runtime, tool_runtime):
@@ -0,0 +1,88 @@
1
+ """A tool server on a Prime runtime of its own runs on a VM of its own, reachable from
2
+ the restricted workspace over the rollout's private network, under the egress policy of
3
+ its own runtime: unrestricted, or none at all."""
4
+
5
+ import json
6
+ import os
7
+
8
+ import pytest
9
+ from fetch_tool_v1 import FetchToolTask, FetchToolTaskConfig
10
+
11
+ import verifiers.v1 as vf
12
+ from verifiers.v1.configs.agent import TimeoutConfig
13
+
14
+ pytestmark = pytest.mark.skipif(
15
+ not os.environ.get("PRIME_API_KEY"), reason="needs PRIME_API_KEY"
16
+ )
17
+
18
+ FETCH = """
19
+ import sys, urllib.error, urllib.request
20
+ try:
21
+ print(urllib.request.urlopen(sys.argv[1], timeout=15).status)
22
+ except urllib.error.HTTPError as error:
23
+ print(error.code)
24
+ except Exception as error:
25
+ print(type(error).__name__)
26
+ """
27
+
28
+ # The workspace reaches the tool server (any HTTP status) but not the internet.
29
+ PROBE = """
30
+ import json, sys, urllib.error, urllib.request
31
+
32
+ def fetch(url):
33
+ try:
34
+ return urllib.request.urlopen(url, timeout=15).status
35
+ except urllib.error.HTTPError as error:
36
+ return error.code
37
+ except Exception as error:
38
+ return type(error).__name__
39
+
40
+ json.dump(
41
+ {"tool": fetch(sys.argv[1]), "public": fetch("https://example.com")},
42
+ open("/tmp/probe.json", "w"),
43
+ )
44
+ """
45
+
46
+
47
+ class ProbeHarness(vf.Harness):
48
+ SUPPORTS_MCP = True
49
+
50
+ async def launch(self, ctx, trace, runtime, endpoint, secret, mcp_urls, data):
51
+ trace.info["url"] = mcp_urls["fetch"]
52
+ return await runtime.run_program(["python", "-c", PROBE, mcp_urls["fetch"]], {})
53
+
54
+
55
+ class ProbeTask(FetchToolTask):
56
+ async def finalize(self, trace, runtime, deployment):
57
+ trace.info["workspace"] = json.loads(await runtime.read("/tmp/probe.json"))
58
+ tool = await deployment.target("fetch").run(
59
+ ["python", "-c", FETCH, "https://example.com"], {}
60
+ )
61
+ trace.info["tool"] = tool.stdout.strip()
62
+
63
+
64
+ @pytest.mark.parametrize(("allow", "egress"), [(["*"], "200"), ([], "URLError")])
65
+ async def test_a_tool_vm_keeps_its_own_egress_beside_a_restricted_workspace(
66
+ allow, egress
67
+ ):
68
+ tools = vf.ToolsetConfig(
69
+ runtime=vf.PrimeConfig(labels=["vf-integration"], allow=allow)
70
+ )
71
+ task = ProbeTask(
72
+ vf.TaskData(prompt="Probe the network", network_allow=[]),
73
+ FetchToolTaskConfig(tools=tools),
74
+ )
75
+ config = vf.AgentConfig(
76
+ model="unused",
77
+ runtime=vf.PrimeConfig(labels=["vf-integration"]),
78
+ timeout=TimeoutConfig(setup=900, finalize=120, scoring=60),
79
+ )
80
+ async with vf.Agent(config) as agent:
81
+ agent.harness = ProbeHarness(vf.HarnessConfig())
82
+ trace = await agent.run(task)
83
+ assert trace.ok, trace.errors
84
+ assert ".sandbox.internal:" in trace.info["url"]
85
+ workspace = trace.info["workspace"]
86
+ assert isinstance(workspace["tool"], int), workspace
87
+ assert workspace["public"] == "URLError"
88
+ assert trace.info["tool"] == egress
@@ -0,0 +1,254 @@
1
+ import asyncio
2
+ from contextlib import asynccontextmanager
3
+ from types import SimpleNamespace
4
+
5
+ import pytest
6
+
7
+ import verifiers.v1 as vf
8
+ from verifiers.v1.errors import ToolsetError
9
+ from verifiers.v1.mcp import launch
10
+ from verifiers.v1.runtimes import (
11
+ DockerConfig,
12
+ PrimeBackend,
13
+ PrimeConfig,
14
+ prime,
15
+ private_network,
16
+ )
17
+ from verifiers.v1.runtimes.strategies import (
18
+ _ColocatedDeployment,
19
+ _ContainerDeployment,
20
+ _deployment_strategy,
21
+ _PrivateNetworkDeployment,
22
+ )
23
+
24
+ ATLAS = PrimeConfig(image="atlas:1", cpu=4, memory=10) # open egress by default
25
+ FORTH = PrimeConfig(image="forth:1", allow=[])
26
+
27
+
28
+ def toolset(name, **config):
29
+ return SimpleNamespace(server_name=name, config=vf.ToolsetConfig(**config))
30
+
31
+
32
+ def test_tool_servers_on_prime_runtimes_beside_a_prime_workspace_get_vms():
33
+ toolsets = [
34
+ toolset("atlas", runtime=ATLAS),
35
+ toolset("search"), # the evaluator
36
+ toolset("files", colocated=True, runtime=ATLAS), # the workspace
37
+ toolset("remote", url="https://tools.example/mcp"),
38
+ toolset("gym", service="tools"), # a Compose service
39
+ toolset("local", runtime=DockerConfig()), # a published port
40
+ ]
41
+ vms = private_network.tool_vms
42
+ assert vms(toolsets, PrimeConfig(), composed=False, borrowed=False) == {
43
+ "atlas": ATLAS
44
+ }
45
+ # Without a Prime workspace of the rollout's own, servers keep their published port.
46
+ for workspace, composed, borrowed in [
47
+ (DockerConfig(), False, False),
48
+ (PrimeConfig(), True, False),
49
+ (PrimeConfig(), False, True),
50
+ ]:
51
+ assert vms(toolsets, workspace, composed=composed, borrowed=borrowed) == {}
52
+ with pytest.raises(ValueError, match="cannot be named 'main'"):
53
+ vms(
54
+ [toolset("main", runtime=ATLAS)],
55
+ PrimeConfig(),
56
+ composed=False,
57
+ borrowed=False,
58
+ )
59
+
60
+
61
+ def test_the_deployment_joins_tool_vms_over_a_private_network():
62
+ assert isinstance(
63
+ _deployment_strategy(None, PrimeConfig(), None, {"atlas": ATLAS}),
64
+ _PrivateNetworkDeployment,
65
+ )
66
+ assert isinstance(
67
+ _deployment_strategy(None, PrimeConfig(), None, {}), _ColocatedDeployment
68
+ )
69
+ spec = vf.ComposeSpec("task")
70
+ assert isinstance(
71
+ _deployment_strategy(None, PrimeConfig(), spec), _ContainerDeployment
72
+ )
73
+
74
+
75
+ def test_the_private_network_needs_a_prime_workspace():
76
+ workspace = SimpleNamespace(config=DockerConfig())
77
+
78
+ async def enter():
79
+ async with private_network.private_network(workspace, {"atlas": ATLAS}, None):
80
+ pass
81
+
82
+ with pytest.raises(TypeError, match="needs the workspace on Prime, not docker"):
83
+ asyncio.run(enter())
84
+
85
+
86
+ class FakeLanClient:
87
+ def __init__(self, events):
88
+ self.events = events
89
+
90
+ async def create_lan_network(self, name, rules):
91
+ self.events.append(("network", [(rule.from_, rule.to) for rule in rules]))
92
+ return SimpleNamespace(id="net1")
93
+
94
+ async def delete_lan_network(self, network_id):
95
+ self.events.append("network deleted")
96
+
97
+
98
+ def boot(monkeypatch, members):
99
+ """Enter `private_network` with a fake LAN client and Prime VMs that record their
100
+ boot and stop there."""
101
+ events = []
102
+
103
+ @asynccontextmanager
104
+ async def leased_client():
105
+ yield FakeLanClient(events)
106
+
107
+ async def start(self):
108
+ events.append(("boot", self.name, self.config, self.lan))
109
+ raise RuntimeError("booted")
110
+
111
+ monkeypatch.setattr(private_network, "leased_client", leased_client)
112
+ monkeypatch.setattr(prime, "ensure_prime_auth", lambda: None)
113
+ monkeypatch.setattr(PrimeBackend, "start", start)
114
+ workspace = PrimeBackend(PrimeConfig(allow=[]), name="ws")
115
+
116
+ async def enter():
117
+ async with private_network.private_network(workspace, members, workspace.start):
118
+ pass
119
+
120
+ with pytest.raises(RuntimeError, match="booted"):
121
+ asyncio.run(enter())
122
+ return events
123
+
124
+
125
+ def test_each_vm_boots_its_server_runtime_as_given(monkeypatch):
126
+ events = boot(monkeypatch, {"atlas": ATLAS, "forth": FORTH})
127
+ vms = {e[1]: e[2] for e in events if isinstance(e, tuple) and e[0] == "boot"}
128
+
129
+ def shape(config):
130
+ return config.image, config.cpu, config.memory, config.allow
131
+
132
+ assert shape(vms["ws-vf-tool-0"]) == ("atlas:1", 4, 10, ["*"])
133
+ assert shape(vms["ws-vf-tool-1"]) == ("forth:1", FORTH.cpu, FORTH.memory, [])
134
+ assert vms["ws"].allow == [] # the workspace keeps its own policy
135
+
136
+
137
+ def test_the_network_starts_with_its_rules_and_every_vm_joins_as_it_boots(monkeypatch):
138
+ events = boot(monkeypatch, {"atlas": ATLAS, "forth": FORTH})
139
+ assert events[0] == (
140
+ "network",
141
+ [("tag:vf-workspace", "tag:vf-tool-0"), ("tag:vf-workspace", "tag:vf-tool-1")],
142
+ )
143
+ boots = {name: lan for kind, name, _, lan in events[1:-1] if kind == "boot"}
144
+ assert boots == {
145
+ "ws": ("net1", ["vf-workspace"]),
146
+ "ws-vf-tool-0": ("net1", ["vf-tool-0"]),
147
+ "ws-vf-tool-1": ("net1", ["vf-tool-1"]),
148
+ }
149
+ assert events[-1] == "network deleted"
150
+
151
+
152
+ def test_a_tool_vm_keeps_only_its_state_channel_and_the_harness_its_routes():
153
+ applied = []
154
+
155
+ def backend(name):
156
+ async def prepare_execution(routes):
157
+ applied.append((name, routes))
158
+
159
+ def host_url(url):
160
+ return url.replace("http://127.0.0.1:9000", "https://tunnel.example")
161
+
162
+ return SimpleNamespace(prepare_execution=prepare_execution, host_url=host_url)
163
+
164
+ strategy = _PrivateNetworkDeployment()
165
+ strategy._service_backends = [backend("forth")]
166
+ deployment = SimpleNamespace(harness_backend=backend("main"))
167
+ # The harness reaches the model and a remote tool server; the tool VM needs neither.
168
+ routes = ["https://tunnel.example/v1", "https://tools.example/mcp"]
169
+ asyncio.run(
170
+ strategy.prepare_execution(
171
+ deployment, routes, state_route="http://127.0.0.1:9000/state"
172
+ )
173
+ )
174
+ assert sorted(applied) == [
175
+ ("forth", ["https://tunnel.example/state"]),
176
+ ("main", routes),
177
+ ]
178
+
179
+
180
+ def test_the_workspace_stops_before_tool_evidence_and_is_released_regardless():
181
+ events = []
182
+
183
+ async def stop():
184
+ events.append("workspace stopped")
185
+
186
+ async def aclose():
187
+ events.append("tool VMs released")
188
+ raise RuntimeError("network deletion failed")
189
+
190
+ strategy = _PrivateNetworkDeployment()
191
+ strategy._services = SimpleNamespace(aclose=aclose)
192
+ deployment = SimpleNamespace(execution_backend=SimpleNamespace(stop=stop))
193
+
194
+ # As Compose stops its workspace, before finalize_services reads the targets.
195
+ asyncio.run(strategy.stop_execution(deployment))
196
+ assert events == ["workspace stopped"]
197
+
198
+ # A failed tool VM or network release neither skips the workspace's nor raises.
199
+ events.clear()
200
+ asyncio.run(strategy.prepare_verifier(deployment))
201
+ assert events == ["workspace stopped", "tool VMs released"]
202
+
203
+
204
+ def served_url(monkeypatch, server, **kwargs):
205
+ async def serve_in_runtime(server, runtime, **options):
206
+ assert options["listen_all"]
207
+ return 1234
208
+
209
+ monkeypatch.setattr(launch, "serve_in_runtime", serve_in_runtime)
210
+
211
+ async def url():
212
+ async with launch._serve(server, **kwargs) as served:
213
+ return served.url
214
+
215
+ return asyncio.run(url())
216
+
217
+
218
+ def test_a_tool_server_on_its_own_vm_is_reached_by_its_lan_name(monkeypatch):
219
+ runtime = SimpleNamespace(is_local=False, host_url=lambda url: url)
220
+ # Its restricted runtime is enforced on its own VM, so it is not refused.
221
+ server = toolset("forth", runtime=FORTH)
222
+ assert (
223
+ served_url(
224
+ monkeypatch,
225
+ server,
226
+ hosts={"forth": "sb1.sandbox.internal"},
227
+ placements={"forth": runtime},
228
+ )
229
+ == "http://sb1.sandbox.internal:1234/mcp"
230
+ )
231
+ # A Compose service keeps its name.
232
+ compose = toolset("echo", service="tools")
233
+ assert served_url(monkeypatch, compose, services={"tools": runtime}) == (
234
+ "http://tools:1234/mcp"
235
+ )
236
+
237
+
238
+ @pytest.mark.parametrize("service", ["main", "forth"])
239
+ def test_an_explicit_service_names_only_a_compose_service(monkeypatch, service):
240
+ # The workspace and another server's VM are deployment targets, not services.
241
+ runtime = SimpleNamespace(is_local=False, host_url=lambda url: url)
242
+ with pytest.raises(ToolsetError, match="Compose services define it"):
243
+ served_url(
244
+ monkeypatch,
245
+ toolset("echo", service=service),
246
+ services={},
247
+ hosts={"forth": "sb1.sandbox.internal"},
248
+ placements={"forth": runtime},
249
+ )
250
+
251
+
252
+ def test_a_restricted_runtime_without_a_private_network_is_refused(monkeypatch):
253
+ with pytest.raises(ToolsetError, match="joins the rollout's private network"):
254
+ served_url(monkeypatch, toolset("forth", runtime=FORTH))
@@ -434,17 +434,27 @@ async def _serve(
434
434
  state_secret: str = "",
435
435
  state_base: str | None = None,
436
436
  services: Mapping[str, Runtime] | None = None,
437
+ hosts: Mapping[str, str] | None = None,
438
+ placements: Mapping[str, Runtime] | None = None,
437
439
  ):
438
440
  cfg = server.config
439
441
  colocated = getattr(cfg, "colocated", False)
440
- if (service := getattr(cfg, "service", None)) is not None:
441
- # The harness reaches the server over the deployment's Compose network.
442
- if services is None or service not in services:
442
+ service = getattr(cfg, "service", None)
443
+ own_vm = (placements or {}).get(server.server_name)
444
+ if service is not None or own_vm is not None:
445
+ # The harness reaches the server over the deployment's network: a Compose
446
+ # service by its name, a VM of its own by its LAN name.
447
+ if own_vm is not None:
448
+ runtime = own_vm
449
+ host = (hosts or {})[server.server_name]
450
+ elif services is None or service not in services:
443
451
  raise ToolsetError(
444
- f"server {server.server_name!r} runs in Compose service {service!r}, "
452
+ f"server {server.server_name!r} runs in service {service!r}, "
445
453
  "which needs a task whose Compose services define it"
446
454
  )
447
- runtime = services[service]
455
+ else:
456
+ runtime = services[service]
457
+ host = (hosts or {}).get(service, service)
448
458
  if runtime.is_local:
449
459
  # A local service cannot reach the host-loopback state channel.
450
460
  raise ToolsetError(
@@ -463,7 +473,7 @@ async def _serve(
463
473
  ),
464
474
  state_secret=state_secret,
465
475
  )
466
- yield _ServedServer(f"http://{service}:{port}/mcp", runtime)
476
+ yield _ServedServer(f"http://{host}:{port}/mcp", runtime)
467
477
  return
468
478
  async with contextlib.AsyncExitStack() as stack:
469
479
  # Colocated servers inherit the harness cut. A separately provisioned filtered
@@ -475,9 +485,10 @@ async def _serve(
475
485
  and not (colocated and harness_runtime is not None)
476
486
  ):
477
487
  raise ToolsetError(
478
- "Runtime network policies are supported on the harness runtime; "
479
- f"server {server.server_name!r} must be colocated or use an "
480
- "unrestricted runtime"
488
+ f"server {server.server_name!r} restricts its runtime's network, which "
489
+ "needs the server colocated with the harness, or on a Prime runtime "
490
+ "of its own beside a Prime workspace without Compose services (it then "
491
+ "joins the rollout's private network)"
481
492
  )
482
493
  if colocated and harness_runtime is not None:
483
494
  runtime = harness_runtime
@@ -532,6 +543,8 @@ async def serve(
532
543
  state_secret: str = "",
533
544
  state_base: str | None = None,
534
545
  services: Mapping[str, Runtime] | None = None,
546
+ hosts: Mapping[str, str] | None = None,
547
+ placements: Mapping[str, Runtime] | None = None,
535
548
  ):
536
549
  """Serve one MCP server and yield the URL visible to its consumer."""
537
550
  async with _serve(
@@ -541,6 +554,8 @@ async def serve(
541
554
  state_secret=state_secret,
542
555
  state_base=state_base,
543
556
  services=services,
557
+ hosts=hosts,
558
+ placements=placements,
544
559
  ) as served:
545
560
  yield served.url
546
561
 
@@ -649,6 +664,8 @@ async def serve_tools(
649
664
  state_route: str = "",
650
665
  state_base: str | None = None,
651
666
  services: Mapping[str, Runtime] | None = None,
667
+ hosts: Mapping[str, str] | None = None,
668
+ placements: Mapping[str, Runtime] | None = None,
652
669
  ):
653
670
  """Bring up a rollout's tool servers and yield `{name: url}` the harness reaches: the
654
671
  task-scoped `toolsets` are launched by `serve` (placement off each one's `config`; the
@@ -690,6 +707,8 @@ async def serve_tools(
690
707
  state_secret=state_secret,
691
708
  state_base=state_base,
692
709
  services=services,
710
+ hosts=hosts,
711
+ placements=placements,
693
712
  )
694
713
  )
695
714
  logger.info("tool server '%s': %s", name, urls[name])
@@ -17,6 +17,9 @@ if TYPE_CHECKING:
17
17
  class ToolsetConfig(BaseConfig):
18
18
  colocated: bool = False
19
19
  runtime: RuntimeConfig = SubprocessConfig()
20
+ """Where the server runs: the evaluator by default. A Prime runtime beside a Prime
21
+ workspace without Compose services is a VM of the server's own that joins the
22
+ workspace over the rollout's private network, under its own egress policy."""
20
23
  url: str | None = None
21
24
  service: str | None = None
22
25
  """Run the server in this service of the task's Compose services (on a Prime or
@@ -36,6 +36,7 @@ from verifiers.v1.runtimes import (
36
36
  RuntimeConfig,
37
37
  make_backend,
38
38
  )
39
+ from verifiers.v1.runtimes.private_network import tool_vms
39
40
  from verifiers.v1.session import RolloutLimits, RolloutSession, hook_boundary
40
41
  from verifiers.v1.state import state_cls
41
42
  from verifiers.v1.task import Task
@@ -290,8 +291,18 @@ class Rollout:
290
291
  "task.prompt, or drive the run through agent.interaction() and open "
291
292
  "it with the first turn(message)"
292
293
  )
294
+ async with boundary(ToolsetError, "building tool servers"):
295
+ toolsets = self.task.toolsets(self.task.config)
296
+ spec = self.task.deployment_spec()
293
297
  self.deployment = runtime.backend.deploy(
294
- spec=self.task.deployment_spec(),
298
+ spec=spec,
299
+ # Tool servers on Prime VMs of their own boot with the workspace.
300
+ tool_vms=tool_vms(
301
+ toolsets,
302
+ runtime.backend.config,
303
+ composed=spec is not None,
304
+ borrowed=self._borrowed_runtime is not None,
305
+ ),
295
306
  env=runtime.env,
296
307
  config=self._deployment_config,
297
308
  borrowed_runtime=self._borrowed_runtime,
@@ -344,8 +355,6 @@ class Rollout:
344
355
  runtime, self.task.data.agent_user
345
356
  )
346
357
  await self.harness.setup(self._agent_runtime)
347
- async with boundary(ToolsetError, "building tool servers"):
348
- toolsets = self.task.toolsets(self.task.config)
349
358
  # `base_url` is the interception server's reachable URL for this rollout.
350
359
  # The harness reaches the model at `{base_url}/v1`; tool servers reach this
351
360
  # rollout's `/state` + `/task` at `base_url` — it's universally reachable
@@ -382,17 +391,27 @@ class Rollout:
382
391
  state_route=self.trace.id,
383
392
  state_base=base_url,
384
393
  services=services,
394
+ hosts=self.deployment.hosts,
395
+ # Tool servers on VMs of their own, by server name; an explicit
396
+ # `service` names only a Compose service.
397
+ placements={
398
+ name: self.deployment.target(name)
399
+ for name in self.deployment.tool_vms
400
+ },
385
401
  )
386
402
  )
387
403
  # Setup and service provisioning are complete. Apply the runtime's
388
404
  # execution policy while preserving the framework routes the agent uses.
389
- # Compose service names resolve inside the deployment, not as egress.
405
+ # Service hosts resolve inside the deployment (Compose names, LAN
406
+ # names), not as egress.
407
+ internal = {*services, *self.deployment.hosts.values()}
390
408
  await self.deployment.prepare_execution(
391
409
  [
392
410
  url
393
411
  for url in [self._endpoint, *self._urls.values()]
394
- if urlsplit(url).hostname not in services
395
- ]
412
+ if urlsplit(url).hostname not in internal
413
+ ],
414
+ state_route=f"{base_url.rstrip('/')}/state",
396
415
  )
397
416
  async with (
398
417
  boundary(HarnessError, "opening harness session"),