verifiers 0.4.1.dev26__tar.gz → 0.4.1.dev28__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 (266) hide show
  1. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/PKG-INFO +1 -1
  2. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/conftest.py +3 -0
  3. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/grid.py +1 -0
  4. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_agent_user.py +6 -0
  5. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_agentic_judge.py +2 -0
  6. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_examples.py +14 -9
  7. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_limits.py +2 -0
  8. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_multi_agent.py +3 -0
  9. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_multi_turn.py +1 -0
  10. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_multi_turn_tool.py +2 -0
  11. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_multimodal_tool.py +1 -0
  12. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_network_policy.py +6 -0
  13. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_resume_tool.py +1 -0
  14. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_rubric_judge.py +1 -0
  15. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_shared_tool.py +1 -0
  16. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_single_turn.py +1 -0
  17. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_skills.py +1 -0
  18. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_stateful_tool.py +1 -0
  19. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_user_echo.py +2 -0
  20. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_user_sim.py +2 -0
  21. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_graph.py +40 -0
  22. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/graph.py +23 -12
  23. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harness.py +8 -1
  24. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/__init__.py +3 -0
  25. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/bash/harness.py +14 -3
  26. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/claude_code/harness.py +7 -0
  27. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/hermes_agent/harness.py +9 -0
  28. verifiers-0.4.1.dev28/verifiers/v1/harnesses/opencode/__init__.py +6 -0
  29. verifiers-0.4.1.dev28/verifiers/v1/harnesses/opencode/harness.py +141 -0
  30. verifiers-0.4.1.dev28/verifiers/v1/harnesses/opencode/mcp-ready.js +44 -0
  31. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/pi/harness.py +7 -0
  32. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/prime_agent/harness.py +7 -0
  33. verifiers-0.4.1.dev28/verifiers/v1/harnesses/utils/replay.py +30 -0
  34. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/rollout.py +1 -0
  35. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/trace.py +3 -0
  36. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/.gitignore +0 -0
  37. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/LICENSE +0 -0
  38. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/README.md +0 -0
  39. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/pyproject.toml +0 -0
  40. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/agent_user_harness_v1.py +0 -0
  41. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/agent_user_v1.py +0 -0
  42. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/counter_tool_v1.py +0 -0
  43. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/duet_v1.py +0 -0
  44. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/echo_acp_resume_v1.py +0 -0
  45. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/echo_agentic_v1.py +0 -0
  46. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/echo_tool_v1.py +0 -0
  47. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/echo_user_sim_v1.py +0 -0
  48. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/echo_v1.py +0 -0
  49. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/network_policy_v1.py +0 -0
  50. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/pyproject.toml +0 -0
  51. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/skill_codeword_v1.py +0 -0
  52. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/skills/project-codeword/SKILL.md +0 -0
  53. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/fixtures/tool_response_image_v1.py +0 -0
  54. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_compose_scoring.py +0 -0
  55. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/integration/test_sampling.py +0 -0
  56. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/cli/test_replay.py +0 -0
  57. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/conftest.py +0 -0
  58. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/harnesses/test_core.py +0 -0
  59. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/harnesses/test_rlm.py +0 -0
  60. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/interception/test_tunnel.py +0 -0
  61. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/judges/conftest.py +0 -0
  62. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/judges/test_judge.py +0 -0
  63. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/judges/test_reference.py +0 -0
  64. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/judges/test_rubric.py +0 -0
  65. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/runtimes/test_e2b.py +0 -0
  66. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/serve/test_client.py +0 -0
  67. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/serve/test_delta.py +0 -0
  68. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/serve/test_pool.py +0 -0
  69. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_agent.py +0 -0
  70. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_dialects.py +0 -0
  71. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_semantic.py +0 -0
  72. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_task.py +0 -0
  73. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_taskset.py +0 -0
  74. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/test_trace.py +0 -0
  75. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/utils/test_compile.py +0 -0
  76. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/utils/test_loaders.py +0 -0
  77. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/utils/test_platform.py +0 -0
  78. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/tests/unit/utils/test_score.py +0 -0
  79. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/__init__.py +0 -0
  80. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/__init__.py +0 -0
  81. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/acp/__init__.py +0 -0
  82. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/acp/runner.py +0 -0
  83. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/agent.py +0 -0
  84. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/__init__.py +0 -0
  85. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  86. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/dashboard/base.py +0 -0
  87. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/dashboard/eval.py +0 -0
  88. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/dashboard/replay.py +0 -0
  89. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/dashboard/validate.py +0 -0
  90. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/debug.py +0 -0
  91. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/eval/__init__.py +0 -0
  92. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/eval/hint.py +0 -0
  93. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/eval/main.py +0 -0
  94. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/eval/resume.py +0 -0
  95. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/eval/runner.py +0 -0
  96. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/gepa.py +0 -0
  97. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/output.py +0 -0
  98. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/replay.py +0 -0
  99. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/resolve.py +0 -0
  100. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/resume.py +0 -0
  101. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/cli/validate.py +0 -0
  102. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/clients/__init__.py +0 -0
  103. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/clients/base.py +0 -0
  104. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/clients/client.py +0 -0
  105. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/clients/eval.py +0 -0
  106. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/clients/train.py +0 -0
  107. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/__init__.py +0 -0
  108. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/agent.py +0 -0
  109. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/__init__.py +0 -0
  110. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/debug.py +0 -0
  111. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/env.py +0 -0
  112. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/eval.py +0 -0
  113. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/replay.py +0 -0
  114. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/cli/validate.py +0 -0
  115. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/client.py +0 -0
  116. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/env.py +0 -0
  117. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/harness.py +0 -0
  118. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/judge.py +0 -0
  119. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/retries.py +0 -0
  120. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/runtime.py +0 -0
  121. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/scoring.py +0 -0
  122. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/select.py +0 -0
  123. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/serve.py +0 -0
  124. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/task.py +0 -0
  125. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/configs/taskset.py +0 -0
  126. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/dialects/__init__.py +0 -0
  127. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/dialects/anthropic.py +0 -0
  128. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/dialects/base.py +0 -0
  129. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/dialects/chat.py +0 -0
  130. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/dialects/responses.py +0 -0
  131. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/env.py +0 -0
  132. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/__init__.py +0 -0
  133. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  134. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  135. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  136. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/best_of_n/env.py +0 -0
  137. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  138. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/single_agent/env.py +0 -0
  139. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  140. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/envs/user_sim/env.py +0 -0
  141. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/episode.py +0 -0
  142. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/errors.py +0 -0
  143. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/__init__.py +0 -0
  144. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/adapter.py +0 -0
  145. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/config.py +0 -0
  146. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/dataset.py +0 -0
  147. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/reflection.py +0 -0
  148. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/gepa/runner.py +0 -0
  149. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/grading.py +0 -0
  150. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  151. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  152. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  153. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  154. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  155. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  156. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
  157. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/codex/harness.py +0 -0
  158. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  159. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  160. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  161. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  162. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  163. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  164. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  165. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/node.py +0 -0
  166. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/null/__init__.py +0 -0
  167. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/null/harness.py +0 -0
  168. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  169. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  170. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  171. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
  172. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  173. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  174. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  175. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  176. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  177. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  178. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  179. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  180. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/core.py +0 -0
  181. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/install.py +0 -0
  182. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/launch.py +0 -0
  183. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  184. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/__init__.py +0 -0
  185. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/base.py +0 -0
  186. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/pool.py +0 -0
  187. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/server.py +0 -0
  188. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  189. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/tunnel/base.py +0 -0
  190. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/tunnel/custom.py +0 -0
  191. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/interception/tunnel/prime.py +0 -0
  192. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judge.py +0 -0
  193. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/__init__.py +0 -0
  194. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/decision.py +0 -0
  195. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/reference.py +0 -0
  196. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/reference.txt +0 -0
  197. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/rubric.py +0 -0
  198. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/judges/rubric.txt +0 -0
  199. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/mcp/__init__.py +0 -0
  200. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/mcp/launch.py +0 -0
  201. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/mcp/server.py +0 -0
  202. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/mcp/toolset.py +0 -0
  203. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/__init__.py +0 -0
  204. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/apptainer.py +0 -0
  205. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/base.py +0 -0
  206. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/compose.py +0 -0
  207. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/container.py +0 -0
  208. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/deployment.py +0 -0
  209. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  210. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/docker/egress.py +0 -0
  211. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/e2b.py +0 -0
  212. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/limiters.py +0 -0
  213. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/modal.py +0 -0
  214. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/prime.py +0 -0
  215. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/runtime.py +0 -0
  216. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/strategies.py +0 -0
  217. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/runtimes/subprocess.py +0 -0
  218. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/semantic.py +0 -0
  219. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/__init__.py +0 -0
  220. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/client.py +0 -0
  221. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/delta.py +0 -0
  222. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/encoding.py +0 -0
  223. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/pool.py +0 -0
  224. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/server.py +0 -0
  225. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/serve/types.py +0 -0
  226. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/session.py +0 -0
  227. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/state.py +0 -0
  228. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/task.py +0 -0
  229. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/taskset.py +0 -0
  230. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/__init__.py +0 -0
  231. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  232. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  233. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  234. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  235. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  236. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  237. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  238. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  239. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  240. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  241. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  242. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  243. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/types.py +0 -0
  244. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/__init__.py +0 -0
  245. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/aio.py +0 -0
  246. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/artifacts.py +0 -0
  247. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/compile.py +0 -0
  248. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/decorators.py +0 -0
  249. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/env.py +0 -0
  250. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/eval.py +0 -0
  251. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/format.py +0 -0
  252. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/generic.py +0 -0
  253. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/git.py +0 -0
  254. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/image.py +0 -0
  255. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/interrupt.py +0 -0
  256. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/loaders.py +0 -0
  257. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/logging.py +0 -0
  258. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/memory.py +0 -0
  259. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/paths.py +0 -0
  260. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/platform.py +0 -0
  261. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/prime.py +0 -0
  262. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/retries.py +0 -0
  263. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/scope.py +0 -0
  264. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/score.py +0 -0
  265. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/verifiers/v1/utils/trace_store.py +0 -0
  266. {verifiers-0.4.1.dev26 → verifiers-0.4.1.dev28}/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.dev26
3
+ Version: 0.4.1.dev28
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
@@ -67,6 +67,9 @@ def pytest_configure(config) -> None:
67
67
  inherits `PYTHONPATH` but not pytest's in-process `pythonpath`. Put the fixture dir on
68
68
  `PYTHONPATH` so a fixture server module (e.g. `tool_response_image_v1`)
69
69
  resolves there too — an installed example package (e.g. `glossary`) already would."""
70
+ config.addinivalue_line(
71
+ "markers", "opencode: integration cells on the OpenCode harness"
72
+ )
70
73
  fixtures = str(Path(__file__).parent / "fixtures")
71
74
  existing = os.environ.get("PYTHONPATH", "")
72
75
  if fixtures not in existing.split(os.pathsep):
@@ -52,6 +52,7 @@ HARNESSES: list[str | dict] = [
52
52
  "kimi-code",
53
53
  "pi",
54
54
  "openclaw",
55
+ "opencode",
55
56
  "mini-swe-agent",
56
57
  "prime-agent",
57
58
  ]
@@ -52,5 +52,11 @@ async def test_agent_user_harness(run, harness, harness_runtime, scripted_model)
52
52
  client=scripted_model,
53
53
  )
54
54
  (trace,) = episode.traces
55
+ # mini-swe-agent only acts through tool calls: it drops a tool-less reply (the
56
+ # scripted "OK") from its history and resends only its format error, so the
57
+ # trace forks by design. Recheck on version bumps:
58
+ # https://github.com/SWE-agent/mini-swe-agent/blob/v2.4.6/src/minisweagent/agents/default.py#L114
59
+ if harness.id != "mini-swe-agent":
60
+ assert trace.num_branches == 1
55
61
  assert trace.ok, [error.message for error in trace.errors]
56
62
  assert trace.num_turns >= 1
@@ -30,6 +30,7 @@ async def test_agentic_judge_shared_verifier(run, tmp_path):
30
30
  rollout_timeout=600,
31
31
  )
32
32
  traces = episode.traces
33
+ assert all(trace.num_branches == 1 for trace in traces)
33
34
  assert sorted(t.agent.name for t in traces) == ["judge", "solver"]
34
35
  (solver,) = [t for t in traces if t.agent.name == "solver"]
35
36
  (judge,) = [t for t in traces if t.agent.name == "judge"]
@@ -75,6 +76,7 @@ async def test_env_id_agentic_judge(run, tmp_path):
75
76
  rollout_timeout=600,
76
77
  )
77
78
  traces = episode.traces
79
+ assert all(trace.num_branches == 1 for trace in traces)
78
80
  assert sorted(t.agent.name for t in traces) == ["judge", "solver"]
79
81
  (solver,) = [t for t in traces if t.agent.name == "solver"]
80
82
  (judge,) = [t for t in traces if t.agent.name == "judge"]
@@ -14,6 +14,8 @@ import pytest
14
14
  from conftest import CI_DECISION_JUDGE, CI_MODEL, CI_REASONING_EFFORT
15
15
  from grid import agent_seats
16
16
 
17
+ import verifiers.v1 as vf
18
+
17
19
  EVAL_TIMEOUT = 600 # 10 minutes for a capped eval (-n 1 -r 1)
18
20
 
19
21
  ENVIRONMENTS = Path(__file__).parent.parent.parent / "environments"
@@ -107,26 +109,29 @@ def test_eval(taskset: str, tmp_path: Path):
107
109
  f"eval {taskset} failed: {(proc.stderr or proc.stdout)[-2000:]}"
108
110
  )
109
111
  episodes = [
110
- json.loads(line)
112
+ vf.WireEpisode.model_validate_json(line)
111
113
  for line in (tmp_path / "run" / "traces.jsonl").read_text().splitlines()
112
114
  ]
113
- assert episodes and all(episode["ok"] for episode in episodes), [
114
- error["message"][:500]
115
+ assert episodes and all(episode.ok for episode in episodes), [
116
+ error.message[:500]
115
117
  for episode in episodes
116
- for holder in (episode, *episode["traces"])
117
- for error in holder.get("errors") or []
118
+ for holder in (episode, *episode.traces)
119
+ for error in holder.errors or []
118
120
  ]
121
+ assert all(
122
+ trace.num_branches == 1 for episode in episodes for trace in episode.traces
123
+ )
119
124
  if taskset == "interception" and os.getenv("PRIME_API_KEY"):
120
125
  assert len(episodes) == 4
121
126
  judged = [
122
127
  trace
123
128
  for episode in episodes
124
- for trace in episode["traces"]
125
- if trace["info"].get("judge_calls")
129
+ for trace in episode.traces
130
+ if trace.info.get("judge_calls")
126
131
  ]
127
132
  assert len(judged) == 1
128
133
  assert (
129
- judged[0]["info"]["judge_calls"][0]["request"]["model"]
134
+ judged[0].info["judge_calls"][0]["request"]["model"]
130
135
  == CI_DECISION_JUDGE["model"]
131
136
  )
132
- assert judged[0]["response_rewrites"]
137
+ assert judged[0].response_rewrites
@@ -23,6 +23,7 @@ async def test_cap(run, cap, stop_condition):
23
23
  env={"agent": cap},
24
24
  )
25
25
  (trace,) = episode.traces
26
+ assert trace.num_branches == 1
26
27
  assert trace.stop_condition == stop_condition
27
28
  assert trace.is_truncated
28
29
  assert trace.num_turns == 1
@@ -50,5 +51,6 @@ async def test_max_turns(run, harness, harness_runtime):
50
51
  max_tokens=8192,
51
52
  )
52
53
  (trace,) = episode.traces
54
+ assert trace.num_branches == 1
53
55
  assert trace.stop_condition == "max_turns", [e.message for e in trace.errors]
54
56
  assert trace.num_turns == 1
@@ -14,6 +14,7 @@ async def test_multi_agent_env(run):
14
14
  max_turns=2,
15
15
  )
16
16
  traces = episode.traces
17
+ assert all(trace.num_branches == 1 for trace in traces)
17
18
  assert len(traces) == 2 # one episode, one trace per role
18
19
  assert sorted(t.agent.name for t in traces) == ["a", "b"]
19
20
  (b,) = [t for t in traces if t.agent.name == "b"]
@@ -42,6 +43,7 @@ async def test_env_id_best_of_n(run):
42
43
  max_turns=2,
43
44
  )
44
45
  traces = episode.traces
46
+ assert all(trace.num_branches == 1 for trace in traces)
45
47
  assert len(traces) == 2 # one episode, two attempts
46
48
  assert all(t.agent.name == "agent" and t.ok for t in traces)
47
49
  assert any(t.metrics["best"] == 1.0 for t in traces)
@@ -62,6 +64,7 @@ async def test_kuhn_poker_self_play(run):
62
64
  rollout_timeout=300,
63
65
  )
64
66
  traces = episode.traces
67
+ assert all(trace.num_branches == 1 for trace in traces)
65
68
  assert sorted(t.agent.name for t in traces) == ["player0", "player1"]
66
69
  payoffs = {t.agent.name: t.rewards["payoff"].score for t in traces}
67
70
  assert payoffs["player0"] + payoffs["player1"] == 0 # zero-sum
@@ -25,6 +25,7 @@ async def test_multi_turn(run, harness, harness_runtime):
25
25
  max_tokens=8192,
26
26
  )
27
27
  (trace,) = episode.traces
28
+ assert trace.num_branches == 1
28
29
  assert trace.ok, [error.message for error in trace.errors]
29
30
  assert trace.num_turns >= 1 # ran a command, then finished
30
31
  assert trace.reward == 1.0
@@ -20,6 +20,7 @@ async def test_tool(run, harness, harness_runtime):
20
20
  taskset_overrides={"task": {"tools": {"colocated": True}}},
21
21
  )
22
22
  (trace,) = episode.traces
23
+ assert trace.num_branches == 1
23
24
  assert trace.ok, [error.message for error in trace.errors]
24
25
  assert trace.num_turns >= 1
25
26
  # Reward 1.0 is the proof the tool ran. How a program surfaces an MCP tool to the
@@ -41,6 +42,7 @@ async def test_tool_placement(run, harness_runtime, tool_runtime):
41
42
  taskset_overrides={"task": {"tools": tool_runtime}},
42
43
  )
43
44
  (trace,) = episode.traces
45
+ assert trace.num_branches == 1
44
46
  assert trace.ok, [error.message for error in trace.errors]
45
47
  assert trace.num_turns >= 2 # tool call + answer
46
48
  assert trace.reward == 1.0
@@ -12,6 +12,7 @@ async def test_multimodal_tool(run):
12
12
  max_turns=4,
13
13
  )
14
14
  (trace,) = episode.traces
15
+ assert trace.num_branches == 1
15
16
  assert trace.ok
16
17
  assert trace.num_turns >= 2 # tool call + answer
17
18
  assert trace.reward == 1.0
@@ -54,6 +54,12 @@ async def test_network_policy(run, harness, harness_runtime, notice):
54
54
  assert set(by_name) == set(NOTICES)
55
55
  for name, episode in by_name.items():
56
56
  [trace] = episode.traces
57
+ # mini-swe-agent only acts through tool calls: it drops a tool-less reply
58
+ # (the probe output as a final answer) from its history, so the trace forks
59
+ # by design. Recheck on version bumps:
60
+ # https://github.com/SWE-agent/mini-swe-agent/blob/v2.4.6/src/minisweagent/agents/default.py#L114
61
+ if harness.id != "mini-swe-agent":
62
+ assert trace.num_branches == 1
57
63
  assert trace.ok, [error.message for error in trace.errors]
58
64
  prompt = prompt_text(trace)
59
65
  if notice and NOTICES[name]:
@@ -19,6 +19,7 @@ async def test_resume_tool(run, harness, harness_runtime):
19
19
  rollout_timeout=600,
20
20
  )
21
21
  (trace,) = episode.traces
22
+ assert trace.num_branches == 1
22
23
  assert trace.ok, [error.message for error in trace.errors]
23
24
  assert trace.stop_condition == "user_closed"
24
25
  assert trace.rewards["resumed"].score == 1.0
@@ -31,6 +31,7 @@ async def test_rubric_judge(run, tmp_path, judge_id):
31
31
  max_turns=2,
32
32
  )
33
33
  (trace,) = episode.traces
34
+ assert trace.num_branches == 1
34
35
  assert trace.ok
35
36
  assert trace.rewards[judge_id].score > 0 # the judge's verdict landed in the reward
36
37
  if judge_id == "rubric":
@@ -22,6 +22,7 @@ async def test_shared_tool(run, harness_runtime, tool_runtime):
22
22
  taskset_overrides={"tools": tool_runtime},
23
23
  )
24
24
  traces = [trace for episode in episodes for trace in episode.traces]
25
+ assert all(trace.num_branches == 1 for trace in traces)
25
26
  assert len(traces) == 2
26
27
  for trace in traces:
27
28
  assert trace.ok, [error.message for error in trace.errors]
@@ -23,6 +23,7 @@ async def test_single_turn(run, harness, harness_runtime):
23
23
  max_turns=2,
24
24
  )
25
25
  (trace,) = episode.traces
26
+ assert trace.num_branches == 1
26
27
  assert trace.ok, [error.message for error in trace.errors]
27
28
  assert trace.num_turns == 1
28
29
  assert trace.stop_condition == "agent_completed"
@@ -17,5 +17,6 @@ async def test_skills(run, harness, harness_runtime):
17
17
  max_tokens=8192,
18
18
  )
19
19
  (trace,) = episode.traces
20
+ assert trace.num_branches == 1
20
21
  assert trace.ok, [error.message for error in trace.errors]
21
22
  assert trace.reward == 1.0, trace.last_reply
@@ -20,6 +20,7 @@ async def test_tool_state(run, harness_runtime, tool_runtime):
20
20
  taskset_overrides={"task": {"tools": tool_runtime}},
21
21
  )
22
22
  (trace,) = episode.traces
23
+ assert trace.num_branches == 1
23
24
  assert trace.ok, [error.message for error in trace.errors]
24
25
  assert trace.num_turns >= 2 # at least two tool calls accumulated
25
26
  assert trace.reward == 1.0
@@ -25,6 +25,7 @@ async def test_user(run, harness, harness_runtime):
25
25
  max_tokens=8192,
26
26
  )
27
27
  (trace,) = episode.traces
28
+ assert trace.num_branches == 1
28
29
  assert trace.ok, [error.message for error in trace.errors]
29
30
  assert trace.num_turns >= 2 # genuinely multi-turn
30
31
  assert trace.stop_condition == "user_closed" # leaving the interaction ended it
@@ -67,3 +68,4 @@ async def test_interaction(live_ctx):
67
68
  assert trace is not None and trace.errors == []
68
69
  assert trace.stop_condition == "user_closed" # closing the interaction ended it
69
70
  assert trace.num_turns == 2
71
+ assert trace.num_branches == 1
@@ -17,6 +17,7 @@ async def test_env_id_user_sim(run):
17
17
  rollout_timeout=300,
18
18
  )
19
19
  traces = episode.traces
20
+ assert all(trace.num_branches == 1 for trace in traces)
20
21
  assert sorted(t.agent.name for t in traces) == ["assistant", "user"]
21
22
  (assistant,) = [t for t in traces if t.agent.name == "assistant"]
22
23
  (user,) = [t for t in traces if t.agent.name == "user"]
@@ -53,6 +54,7 @@ async def test_env_id_user_sim_with_tools(run):
53
54
  rollout_timeout=300,
54
55
  )
55
56
  traces = episode.traces
57
+ assert all(trace.num_branches == 1 for trace in traces)
56
58
  (assistant,) = [t for t in traces if t.agent.name == "assistant"]
57
59
  (user,) = [t for t in traces if t.agent.name == "user"]
58
60
  assert assistant.ok and user.ok
@@ -5,6 +5,7 @@ import pytest
5
5
 
6
6
  import verifiers.v1 as vf
7
7
  from verifiers.v1 import graph
8
+ from verifiers.v1.harnesses.utils.replay import drop_blank_text, strip_text
8
9
  from verifiers.v1.types import TurnTokens
9
10
 
10
11
 
@@ -269,6 +270,45 @@ def test_reasoning_content_participates_in_graph_prefix_matching():
269
270
  assert len(tool_call_nodes) == 2
270
271
 
271
272
 
273
+ @pytest.mark.parametrize(
274
+ "view,sampled,replayed",
275
+ [
276
+ (drop_blank_text, {"content": "\n\n"}, {"content": None}),
277
+ (
278
+ drop_blank_text,
279
+ {"content": "\n\n", "reasoning_content": "plan"},
280
+ {"reasoning_content": "plan"},
281
+ ),
282
+ (strip_text, {"content": "Calling it.\n\n"}, {"content": "Calling it."}),
283
+ (strip_text, {"content": None}, {"reasoning_content": " "}),
284
+ ],
285
+ )
286
+ def test_replay_view_matches_cosmetically_rewritten_turns(view, sampled, replayed):
287
+ def run(replay_view):
288
+ trace = vf.Trace(
289
+ agent=vf.AgentInfo(config=vf.AgentConfig()),
290
+ task=vf.TraceTask(type="Task", data=vf.TaskData(idx=0, prompt="go")),
291
+ )
292
+ trace._replay_view = replay_view
293
+ user = vf.UserMessage(content="go")
294
+ call = vf.ToolCall(id="call_0", name="lookup", arguments="{}")
295
+ graph.prepare_turn(trace, [user]).commit(
296
+ _response(vf.AssistantMessage(tool_calls=[call], **sampled))
297
+ )
298
+ graph.prepare_turn(
299
+ trace,
300
+ [
301
+ user,
302
+ vf.AssistantMessage(tool_calls=[call], **replayed),
303
+ vf.ToolMessage(content="result", tool_call_id="call_0"),
304
+ ],
305
+ ).commit(_response(vf.AssistantMessage(content="done")))
306
+ return trace
307
+
308
+ assert run(None).num_branches == 2
309
+ assert run(view).num_branches == 1
310
+
311
+
272
312
  @pytest.mark.parametrize("same_tools", [False, True])
273
313
  def test_parallel_commits_reconcile_shared_prompt_prefix(same_tools):
274
314
  """Two requests prepared from the same graph snapshot share any common prompt prefix that
@@ -321,13 +321,23 @@ def _tools_hash(tools: list[Tool] | None) -> str:
321
321
  return hashlib.blake2b(data, digest_size=16).hexdigest()
322
322
 
323
323
 
324
+ def _message_key(trace: Trace, message: Message) -> str:
325
+ """`message_hash` of the message as the trace's harness replays it, so a turn the
326
+ harness re-sends with cosmetic rewrites still matches the node it came from."""
327
+ view = trace._replay_view
328
+ return message_hash(view(message) if view is not None else message)
329
+
330
+
324
331
  def _node_key(
325
- parent: int | None, message: Message, tools: list[Tool] | None = None
332
+ trace: Trace,
333
+ parent: int | None,
334
+ message: Message,
335
+ tools: list[Tool] | None = None,
326
336
  ) -> tuple[int | None, str | None, str]:
327
337
  return (
328
338
  parent,
329
339
  _tools_hash(tools) if parent is None else None,
330
- message_hash(message),
340
+ _message_key(trace, message),
331
341
  )
332
342
 
333
343
 
@@ -335,7 +345,7 @@ def _head_index(trace: Trace) -> dict[tuple[int | None, str | None, str], int]:
335
345
  """Physical node key -> id, rebuilt lazily after deserialization."""
336
346
  if not trace._head_index and trace.nodes:
337
347
  trace._head_index = {
338
- _node_key(node.parent, node.message, node.tools): nid
348
+ _node_key(trace, node.parent, node.message, node.tools): nid
339
349
  for nid, node in enumerate(trace.nodes)
340
350
  }
341
351
  return trace._head_index
@@ -350,12 +360,12 @@ def message_prefix_len(trace: Trace, prompt: list[Message]) -> int:
350
360
  parents: list[int | None] = [None]
351
361
  matched = 0
352
362
  for message in prompt:
353
- key = message_hash(message)
363
+ key = _message_key(trace, message)
354
364
  parents = [
355
365
  node_id
356
366
  for parent in parents
357
367
  for node_id in children.get(parent, [])
358
- if message_hash(trace.nodes[node_id].message) == key
368
+ if _message_key(trace, trace.nodes[node_id].message) == key
359
369
  ]
360
370
  if not parents:
361
371
  break
@@ -377,7 +387,7 @@ def _matching_node(
377
387
  prefix breaks can leave older physical variants under the same key, so a token mismatch falls
378
388
  back to a reverse scan rather than materializing a duplicate of an already-existing variant.
379
389
  """
380
- key = _node_key(parent, message, tools)
390
+ key = _node_key(trace, parent, message, tools)
381
391
  indexed = _head_index(trace).get(key)
382
392
  if (
383
393
  indexed is not None
@@ -397,7 +407,7 @@ def _matching_node(
397
407
  if (
398
408
  (token_ids is None or node.token_ids == token_ids)
399
409
  and (renderer_token_ids is None or node.logical_ids == renderer_token_ids)
400
- and _node_key(node.parent, node.message, node.tools) == key
410
+ and _node_key(trace, node.parent, node.message, node.tools) == key
401
411
  ):
402
412
  return node_id
403
413
  return None
@@ -422,7 +432,7 @@ def _matching_prefix_node(
422
432
  otherwise-missing message boundary. The next attributed message's start bounds the match:
423
433
  a sampled variant must never consume tokens that the renderer assigned to that message.
424
434
  """
425
- key = _node_key(parent, message, tools)
435
+ key = _node_key(trace, parent, message, tools)
426
436
  indexed = _head_index(trace).get(key)
427
437
  candidates: list[int] = []
428
438
  if indexed is not None:
@@ -432,6 +442,7 @@ def _matching_prefix_node(
432
442
  for node_id in range(len(trace.nodes) - 1, -1, -1)
433
443
  if node_id != indexed
434
444
  and _node_key(
445
+ trace,
435
446
  trace.nodes[node_id].parent,
436
447
  trace.nodes[node_id].message,
437
448
  trace.nodes[node_id].tools,
@@ -562,7 +573,7 @@ class PendingTurn:
562
573
  )
563
574
  )
564
575
  parent = len(self.trace.nodes) - 1
565
- index[_node_key(previous, message, self.tools)] = parent
576
+ index[_node_key(self.trace, previous, message, self.tools)] = parent
566
577
  if self.tools:
567
578
  self.trace.tools = list(
568
579
  {
@@ -603,7 +614,7 @@ def prepare_turn(
603
614
  if len(children) == 1 and trace.nodes[children[0]].message == msg:
604
615
  existing = children[0]
605
616
  if existing is None:
606
- message_key = message_hash(msg)
617
+ message_key = _message_key(trace, msg)
607
618
  existing = idx.get(
608
619
  (parent, tools_hash if parent is None else None, message_key)
609
620
  )
@@ -903,7 +914,7 @@ def _commit_turn(turn: PendingTurn, response: Response) -> int:
903
914
  # Track new nodes separately so routed-expert attribution needs only node ids, not this path.
904
915
  new_node_ids: list[int] = []
905
916
  for i, msg in enumerate(prompt[num_reused:], start=num_reused):
906
- key = _node_key(parent, msg, turn.tools)
917
+ key = _node_key(trace, parent, msg, turn.tools)
907
918
  start = path_len if cursor is None else cursor
908
919
  span = spans[i] if spans and i < len(spans) else None
909
920
  end = span[1] if span else start
@@ -956,7 +967,7 @@ def _commit_turn(turn: PendingTurn, response: Response) -> int:
956
967
  )
957
968
  )
958
969
  assistant_id = len(trace.nodes) - 1
959
- idx[_node_key(parent, response.message, turn.tools)] = assistant_id
970
+ idx[_node_key(trace, parent, response.message, turn.tools)] = assistant_id
960
971
  new_node_ids.append(assistant_id)
961
972
 
962
973
  # Attribute this turn's expert-routing array onto the nodes created this turn (new input
@@ -18,7 +18,7 @@ from verifiers.v1.errors import (
18
18
  )
19
19
  from verifiers.v1.runtimes import ProgramResult, Runtime
20
20
  from verifiers.v1.task import TaskData
21
- from verifiers.v1.types import Messages
21
+ from verifiers.v1.types import Message, Messages
22
22
  from verifiers.v1.utils.decorators import (
23
23
  discover_decorated,
24
24
  invoke_all,
@@ -67,6 +67,13 @@ class Harness(ABC, Generic[ConfigT]):
67
67
  def __init__(self, config: ConfigT) -> None:
68
68
  self.config = config
69
69
 
70
+ def replay_view(self, message: Message) -> Message:
71
+ """`message` as this harness re-sends it on later requests. Override only for
72
+ deterministic cosmetic rewrites (e.g. dropped whitespace-only text), never for
73
+ content changes, so a replayed turn matches the turn it came from in the trace
74
+ graph instead of forking it."""
75
+ return message
76
+
70
77
  def resolve_prompt(
71
78
  self, task: TaskData
72
79
  ) -> tuple[str | None, str | Messages | None]:
@@ -19,6 +19,7 @@ from verifiers.v1.harnesses.mini_swe_agent import (
19
19
  )
20
20
  from verifiers.v1.harnesses.null import NullHarness, NullHarnessConfig
21
21
  from verifiers.v1.harnesses.openclaw import OpenClawHarness, OpenClawHarnessConfig
22
+ from verifiers.v1.harnesses.opencode import OpenCodeHarness, OpenCodeHarnessConfig
22
23
  from verifiers.v1.harnesses.pi import PiHarness, PiHarnessConfig
23
24
  from verifiers.v1.harnesses.prime_agent import (
24
25
  PrimeAgentHarness,
@@ -46,6 +47,8 @@ __all__ = [
46
47
  "NullHarnessConfig",
47
48
  "OpenClawHarness",
48
49
  "OpenClawHarnessConfig",
50
+ "OpenCodeHarness",
51
+ "OpenCodeHarnessConfig",
49
52
  "PiHarness",
50
53
  "PiHarnessConfig",
51
54
  "PrimeAgentHarness",
@@ -79,9 +79,20 @@ class BashHarness(Harness[BashHarnessConfig]):
79
79
  fragments.append(EDIT_SYSTEM_PROMPT)
80
80
  if self.config.search:
81
81
  fragments.append(SEARCH_PROMPT)
82
- system_prompt = "\n\n".join(
83
- p for p in (" ".join(fragments), system_prompt) if p
84
- )
82
+ harness_prompt = " ".join(fragments)
83
+ system_prompt = "\n\n".join(p for p in (harness_prompt, system_prompt) if p)
84
+ # Default resume replays the transcript. If there was no task system
85
+ # prompt, that transcript already contains this harness prompt.
86
+ if (
87
+ data.system_prompt is None
88
+ and prompt is not None
89
+ and not isinstance(prompt, str)
90
+ and any(
91
+ message.role == "system" and message.content == harness_prompt
92
+ for message in prompt
93
+ )
94
+ ):
95
+ system_prompt = None
85
96
  env = {**self.config.resolved_env}
86
97
  args = ["--bash"]
87
98
  if tool_interception_url:
@@ -7,9 +7,11 @@ from verifiers.v1.clients import ModelContext
7
7
  from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
8
8
  from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
9
9
  from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
10
+ from verifiers.v1.harnesses.utils.replay import drop_blank_text
10
11
  from verifiers.v1.runtimes import Runtime
11
12
  from verifiers.v1.task import TaskData
12
13
  from verifiers.v1.trace import Trace
14
+ from verifiers.v1.types import Message
13
15
 
14
16
  CLAUDE_ACP_DIR = "/var/tmp/vf-claude-agent-acp-{version}-{acp_version}"
15
17
  PACKAGES_DIR = f"{CLAUDE_ACP_DIR}/packages"
@@ -40,6 +42,11 @@ class ClaudeCodeHarness(ACPHarness[ClaudeCodeHarnessConfig]):
40
42
  SUPPORTS_SKILLS = True
41
43
  SUPPORTS_TOOL_INTERCEPTION = True
42
44
 
45
+ # Claude Code drops whitespace-only text blocks on replay. Closed source: observed
46
+ # with 2.1.278, recheck on version bumps.
47
+ def replay_view(self, message: Message) -> Message:
48
+ return drop_blank_text(message)
49
+
43
50
  async def setup(self, runtime: Runtime) -> None:
44
51
  await ensure_node(runtime)
45
52
  versions = {"version": self.config.version, "acp_version": ACP_VERSION}
@@ -7,10 +7,12 @@ from verifiers.v1.acp import ACPConfig, ACPHarness
7
7
  from verifiers.v1.clients import ModelContext
8
8
  from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
9
9
  from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
10
+ from verifiers.v1.harnesses.utils.replay import strip_text
10
11
  from verifiers.v1.runtimes import Runtime
11
12
  from verifiers.v1.runtimes.base import _ENSURE_UV
12
13
  from verifiers.v1.task import TaskData
13
14
  from verifiers.v1.trace import Trace
15
+ from verifiers.v1.types import Message
14
16
 
15
17
  PROGRAM_SOURCE = (Path(__file__).resolve().parent / "program.py").read_text()
16
18
  HERMES_DIR = "/var/tmp/vf-hermes-agent-{version}"
@@ -37,6 +39,13 @@ class HermesAgentHarness(ACPHarness[HermesAgentHarnessConfig]):
37
39
  SUPPORTS_MCP = True
38
40
  SUPPORTS_SKILLS = True
39
41
 
42
+ # Hermes stores assistant text stripped and pads missing reasoning with " " on
43
+ # replay. Recheck on version bumps:
44
+ # https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/agent/chat_completion_helpers.py#L1444
45
+ # https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/agent/message_sanitization.py#L545
46
+ def replay_view(self, message: Message) -> Message:
47
+ return strip_text(message)
48
+
40
49
  async def setup(self, runtime: Runtime) -> None:
41
50
  # Hermes needs its source-tree assets and supports editable installs only.
42
51
  directory = HERMES_DIR.format(version=self.config.version)
@@ -0,0 +1,6 @@
1
+ from verifiers.v1.harnesses.opencode.harness import (
2
+ OpenCodeHarness,
3
+ OpenCodeHarnessConfig,
4
+ )
5
+
6
+ __all__ = ["OpenCodeHarness", "OpenCodeHarnessConfig"]