verifiers 0.3.2.dev207__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.
Files changed (230) hide show
  1. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/PKG-INFO +1 -1
  2. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/conftest.py +101 -0
  3. verifiers-0.3.2.dev209/tests/v1/fixtures/agent_user_harness_v1.py +37 -0
  4. verifiers-0.3.2.dev209/tests/v1/fixtures/agent_user_v1.py +161 -0
  5. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/pyproject.toml +1 -0
  6. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_e2e.py +67 -8
  7. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/install.py +10 -1
  8. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/launch.py +48 -24
  9. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/rollout.py +13 -7
  10. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/base.py +34 -19
  11. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/prime.py +28 -8
  12. verifiers-0.3.2.dev207/tests/v1/fixtures/agent_user_v1.py +0 -82
  13. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/.gitignore +0 -0
  14. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/LICENSE +0 -0
  15. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/README.md +0 -0
  16. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/pyproject.toml +0 -0
  17. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/counter_tool_v1.py +0 -0
  18. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/delivery_behaviors.py +0 -0
  19. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/duet_v1.py +0 -0
  20. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
  21. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
  22. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_tool_v1.py +0 -0
  23. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
  24. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/echo_v1.py +0 -0
  25. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
  26. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_color_codeword_tasks.py +0 -0
  27. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_configs.py +0 -0
  28. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_delivery_behaviors.py +0 -0
  29. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_envs.py +0 -0
  30. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_graph.py +0 -0
  31. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_judges.py +0 -0
  32. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_platform_run.py +0 -0
  33. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_scoring.py +0 -0
  34. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_serve_delta.py +0 -0
  35. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_taskset.py +0 -0
  36. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_trace.py +0 -0
  37. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/tests/v1/test_tunnel_watchdog.py +0 -0
  38. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/__init__.py +0 -0
  39. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/__init__.py +0 -0
  40. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/acp/__init__.py +0 -0
  41. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/acp/runner.py +0 -0
  42. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/agent.py +0 -0
  43. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/__init__.py +0 -0
  44. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  45. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/base.py +0 -0
  46. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/eval.py +0 -0
  47. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/replay.py +0 -0
  48. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/dashboard/validate.py +0 -0
  49. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/debug.py +0 -0
  50. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/__init__.py +0 -0
  51. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/hint.py +0 -0
  52. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/main.py +0 -0
  53. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/resume.py +0 -0
  54. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/eval/runner.py +0 -0
  55. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/gepa.py +0 -0
  56. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/init.py +0 -0
  57. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/output.py +0 -0
  58. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/replay.py +0 -0
  59. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/resolve.py +0 -0
  60. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/resume.py +0 -0
  61. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/cli/validate.py +0 -0
  62. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/clients/__init__.py +0 -0
  63. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/clients/base.py +0 -0
  64. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/clients/client.py +0 -0
  65. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/clients/eval.py +0 -0
  66. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/clients/train.py +0 -0
  67. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/__init__.py +0 -0
  68. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/agent.py +0 -0
  69. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/__init__.py +0 -0
  70. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/debug.py +0 -0
  71. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/env.py +0 -0
  72. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/eval.py +0 -0
  73. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/init.py +0 -0
  74. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/replay.py +0 -0
  75. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/cli/validate.py +0 -0
  76. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/client.py +0 -0
  77. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/env.py +0 -0
  78. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/harness.py +0 -0
  79. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/judge.py +0 -0
  80. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/retries.py +0 -0
  81. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/runtime.py +0 -0
  82. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/select.py +0 -0
  83. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/serve.py +0 -0
  84. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/task.py +0 -0
  85. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/configs/taskset.py +0 -0
  86. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/__init__.py +0 -0
  87. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/anthropic.py +0 -0
  88. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/base.py +0 -0
  89. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/chat.py +0 -0
  90. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/dialects/responses.py +0 -0
  91. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/env.py +0 -0
  92. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/__init__.py +0 -0
  93. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  94. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  95. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  96. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/best_of_n/env.py +0 -0
  97. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
  98. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
  99. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
  100. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  101. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/single_agent/env.py +0 -0
  102. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  103. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/envs/user_sim/env.py +0 -0
  104. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/episode.py +0 -0
  105. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/errors.py +0 -0
  106. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/__init__.py +0 -0
  107. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/adapter.py +0 -0
  108. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/config.py +0 -0
  109. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/dataset.py +0 -0
  110. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/reflection.py +0 -0
  111. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/gepa/runner.py +0 -0
  112. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/graph.py +0 -0
  113. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harness.py +0 -0
  114. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/__init__.py +0 -0
  115. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  116. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/bash/harness.py +0 -0
  117. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  118. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  119. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  120. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  121. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  122. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  123. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
  124. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/codex/harness.py +0 -0
  125. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  126. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  127. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  128. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  129. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  130. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  131. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  132. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  133. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/node.py +0 -0
  134. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/null/__init__.py +0 -0
  135. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/null/harness.py +0 -0
  136. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  137. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  138. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  139. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
  140. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/pi/harness.py +0 -0
  141. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  142. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  143. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  144. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  145. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  146. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  147. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  148. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  149. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  150. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/core.py +0 -0
  151. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/launch.py +0 -0
  152. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  153. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/__init__.py +0 -0
  154. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/base.py +0 -0
  155. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/pool.py +0 -0
  156. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/server.py +0 -0
  157. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  158. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/base.py +0 -0
  159. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/custom.py +0 -0
  160. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/interception/tunnel/prime.py +0 -0
  161. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judge.py +0 -0
  162. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/__init__.py +0 -0
  163. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/decision.py +0 -0
  164. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/reference.py +0 -0
  165. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/reference.txt +0 -0
  166. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/rubric.py +0 -0
  167. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/judges/rubric.txt +0 -0
  168. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/__init__.py +0 -0
  169. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/server.py +0 -0
  170. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/mcp/toolset.py +0 -0
  171. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/__init__.py +0 -0
  172. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/apptainer.py +0 -0
  173. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/container.py +0 -0
  174. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  175. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/docker/egress.py +0 -0
  176. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/e2b.py +0 -0
  177. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/limiters.py +0 -0
  178. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/modal.py +0 -0
  179. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/runtimes/subprocess.py +0 -0
  180. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/semantic.py +0 -0
  181. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/__init__.py +0 -0
  182. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/client.py +0 -0
  183. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/delta.py +0 -0
  184. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/encoding.py +0 -0
  185. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/pool.py +0 -0
  186. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/server.py +0 -0
  187. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/serve/types.py +0 -0
  188. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/session.py +0 -0
  189. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/state.py +0 -0
  190. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/task.py +0 -0
  191. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/taskset.py +0 -0
  192. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/__init__.py +0 -0
  193. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  194. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/compose.py +0 -0
  195. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/env.py +0 -0
  196. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  197. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  198. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  199. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  200. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  201. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  202. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  203. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  204. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  205. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  206. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  207. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/trace.py +0 -0
  208. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/types.py +0 -0
  209. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/__init__.py +0 -0
  210. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/aio.py +0 -0
  211. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/artifacts.py +0 -0
  212. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/compile.py +0 -0
  213. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/decorators.py +0 -0
  214. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/eval.py +0 -0
  215. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/format.py +0 -0
  216. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/generic.py +0 -0
  217. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/git.py +0 -0
  218. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/image.py +0 -0
  219. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/interrupt.py +0 -0
  220. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/loaders.py +0 -0
  221. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/logging.py +0 -0
  222. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/memory.py +0 -0
  223. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/paths.py +0 -0
  224. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/platform.py +0 -0
  225. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/prime.py +0 -0
  226. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/retries.py +0 -0
  227. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/scope.py +0 -0
  228. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/score.py +0 -0
  229. {verifiers-0.3.2.dev207 → verifiers-0.3.2.dev209}/verifiers/v1/utils/trace_store.py +0 -0
  230. {verifiers-0.3.2.dev207 → 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.dev207
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()
@@ -14,6 +14,7 @@ build-backend = "hatchling.build"
14
14
  # never get installed.
15
15
  [tool.hatch.build]
16
16
  include = [
17
+ "agent_user_v1.py",
17
18
  "counter_tool_v1.py",
18
19
  "echo_acp_resume_v1.py",
19
20
  "echo_tool_v1.py",
@@ -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(run_v1, harness_runtime, tmp_path):
344
- """A scripted, model-free agent runs as the task's non-root `agent_user`: it can see
345
- the secret the root-run setup planted and write its own workspace, but every
346
- root-only action (read the secret, write /etc, chmod the secret, signal PID 1) is
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
- assert report["user"] == "vf-agent"
356
- assert report["secret_exists"] == "ok" and report["write_workdir"] == "ok"
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
- result = await runtime.run(["sh", "-c", guarded], env)
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, runtime: Runtime) -> str:
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 not pending:
254
- return f"{venv}/bin/python"
255
- setup = (
256
- f"set -e; mkdir -p {root_q} {temp_q} {cache_q}; "
257
- f"export TMPDIR={temp_q} UV_CACHE_DIR={cache_q}; "
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
- runtime._mcp_sources.update(pending)
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="$HOME/.local/bin:$PATH"; exec {shlex.join(command)}',
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 agent_runtime(runtime: Runtime, user: str | None) -> Runtime:
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
- await self.harness.setup(runtime)
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
- runtime,
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
- await agent_runtime(runtime, harness_data.agent_user),
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")