verifiers 0.3.2.dev72__tar.gz → 0.3.2.dev73__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 (216) hide show
  1. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/PKG-INFO +2 -1
  2. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/pyproject.toml +2 -0
  3. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/conftest.py +1 -0
  4. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/dashboard/eval.py +15 -7
  5. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/eval/main.py +0 -4
  6. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/eval/runner.py +69 -34
  7. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/eval.py +12 -2
  8. verifiers-0.3.2.dev73/verifiers/v1/utils/platform.py +164 -0
  9. verifiers-0.3.2.dev72/verifiers/v1/utils/platform.py +0 -317
  10. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/.gitignore +0 -0
  11. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/LICENSE +0 -0
  12. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/README.md +0 -0
  13. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/counter_tool_v1.py +0 -0
  14. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/duet_v1.py +0 -0
  15. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/echo_acp_resume_v1.py +0 -0
  16. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/echo_agentic_v1.py +0 -0
  17. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/echo_tool_v1.py +0 -0
  18. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/echo_user_sim_v1.py +0 -0
  19. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/echo_v1.py +0 -0
  20. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/pyproject.toml +0 -0
  21. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/fixtures/tool_response_image_v1.py +0 -0
  22. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_color_codeword_tasks.py +0 -0
  23. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_configs.py +0 -0
  24. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_e2e.py +0 -0
  25. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_envs.py +0 -0
  26. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_graph.py +0 -0
  27. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_judges.py +0 -0
  28. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_scoring.py +0 -0
  29. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_taskset.py +0 -0
  30. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/tests/v1/test_trace.py +0 -0
  31. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/__init__.py +0 -0
  32. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/cli/commands/init.py +0 -0
  33. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/__init__.py +0 -0
  34. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/acp/__init__.py +0 -0
  35. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/acp/runner.py +0 -0
  36. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/agent.py +0 -0
  37. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/__init__.py +0 -0
  38. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/dashboard/__init__.py +0 -0
  39. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/dashboard/base.py +0 -0
  40. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/dashboard/replay.py +0 -0
  41. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/dashboard/validate.py +0 -0
  42. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/debug.py +0 -0
  43. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/eval/__init__.py +0 -0
  44. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/eval/resume.py +0 -0
  45. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/gepa.py +0 -0
  46. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/init.py +0 -0
  47. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/output.py +0 -0
  48. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/replay.py +0 -0
  49. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/resolve.py +0 -0
  50. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/resume.py +0 -0
  51. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/cli/validate.py +0 -0
  52. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/clients/__init__.py +0 -0
  53. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/clients/base.py +0 -0
  54. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/clients/client.py +0 -0
  55. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/clients/eval.py +0 -0
  56. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/clients/train.py +0 -0
  57. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/__init__.py +0 -0
  58. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/agent.py +0 -0
  59. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/__init__.py +0 -0
  60. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/debug.py +0 -0
  61. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/env.py +0 -0
  62. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/init.py +0 -0
  63. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/replay.py +0 -0
  64. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/cli/validate.py +0 -0
  65. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/client.py +0 -0
  66. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/env.py +0 -0
  67. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/harness.py +0 -0
  68. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/judge.py +0 -0
  69. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/retries.py +0 -0
  70. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/runtime.py +0 -0
  71. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/serve.py +0 -0
  72. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/task.py +0 -0
  73. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/configs/taskset.py +0 -0
  74. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/dialects/__init__.py +0 -0
  75. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/dialects/anthropic.py +0 -0
  76. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/dialects/base.py +0 -0
  77. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/dialects/chat.py +0 -0
  78. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/dialects/responses.py +0 -0
  79. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/env.py +0 -0
  80. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/__init__.py +0 -0
  81. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  82. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  83. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  84. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/best_of_n/env.py +0 -0
  85. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/isolated_verifier/__init__.py +0 -0
  86. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/isolated_verifier/env.py +0 -0
  87. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/shared_agentic_judge/__init__.py +0 -0
  88. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  89. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/single_agent/env.py +0 -0
  90. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  91. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/envs/user_sim/env.py +0 -0
  92. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/episode.py +0 -0
  93. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/errors.py +0 -0
  94. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/__init__.py +0 -0
  95. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/adapter.py +0 -0
  96. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/config.py +0 -0
  97. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/dataset.py +0 -0
  98. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/reflection.py +0 -0
  99. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/gepa/runner.py +0 -0
  100. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/graph.py +0 -0
  101. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harness.py +0 -0
  102. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/__init__.py +0 -0
  103. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  104. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/bash/harness.py +0 -0
  105. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  106. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  107. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  108. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  109. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  110. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  111. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/codex/harness.py +0 -0
  112. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  113. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  114. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  115. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  116. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  117. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  118. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  119. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  120. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/node.py +0 -0
  121. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/null/__init__.py +0 -0
  122. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/null/harness.py +0 -0
  123. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  124. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  125. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  126. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/pi/harness.py +0 -0
  127. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/pool/__init__.py +0 -0
  128. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/pool/harness.py +0 -0
  129. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  130. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  131. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  132. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  133. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  134. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  135. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  136. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  137. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  138. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/core.py +0 -0
  139. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/install.py +0 -0
  140. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/launch.py +0 -0
  141. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  142. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/__init__.py +0 -0
  143. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/base.py +0 -0
  144. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/pool.py +0 -0
  145. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/server.py +0 -0
  146. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/tool.py +0 -0
  147. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  148. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/tunnel/base.py +0 -0
  149. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/tunnel/custom.py +0 -0
  150. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/interception/tunnel/prime.py +0 -0
  151. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judge.py +0 -0
  152. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judges/__init__.py +0 -0
  153. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judges/reference.py +0 -0
  154. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judges/reference.txt +0 -0
  155. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judges/rubric.py +0 -0
  156. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/judges/rubric.txt +0 -0
  157. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/mcp/__init__.py +0 -0
  158. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/mcp/launch.py +0 -0
  159. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/mcp/server.py +0 -0
  160. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/mcp/toolset.py +0 -0
  161. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/rollout.py +0 -0
  162. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/__init__.py +0 -0
  163. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/base.py +0 -0
  164. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/docker/__init__.py +0 -0
  165. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/docker/egress.py +0 -0
  166. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/limiters.py +0 -0
  167. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/modal.py +0 -0
  168. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/prime.py +0 -0
  169. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/runtimes/subprocess.py +0 -0
  170. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/semantic.py +0 -0
  171. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/__init__.py +0 -0
  172. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/client.py +0 -0
  173. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/encoding.py +0 -0
  174. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/pool.py +0 -0
  175. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/server.py +0 -0
  176. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/serve/types.py +0 -0
  177. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/session.py +0 -0
  178. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/state.py +0 -0
  179. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/task.py +0 -0
  180. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/taskset.py +0 -0
  181. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/__init__.py +0 -0
  182. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  183. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/harbor/env.py +0 -0
  184. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/harbor/taskset.py +0 -0
  185. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/lean/__init__.py +0 -0
  186. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/lean/scoring.py +0 -0
  187. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/lean/taskset.py +0 -0
  188. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  189. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  190. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  191. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  192. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  193. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  194. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  195. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  196. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  197. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/trace.py +0 -0
  198. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/types.py +0 -0
  199. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/__init__.py +0 -0
  200. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/aio.py +0 -0
  201. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/artifacts.py +0 -0
  202. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/compile.py +0 -0
  203. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/decorators.py +0 -0
  204. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/format.py +0 -0
  205. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/generic.py +0 -0
  206. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/git.py +0 -0
  207. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/image.py +0 -0
  208. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/interrupt.py +0 -0
  209. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/loaders.py +0 -0
  210. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/logging.py +0 -0
  211. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/memory.py +0 -0
  212. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/paths.py +0 -0
  213. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/prime.py +0 -0
  214. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/retries.py +0 -0
  215. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/verifiers/v1/utils/score.py +0 -0
  216. {verifiers-0.3.2.dev72 → verifiers-0.3.2.dev73}/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.dev72
3
+ Version: 0.3.2.dev73
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
@@ -35,6 +35,7 @@ Requires-Dist: msgpack>=1.1.2
35
35
  Requires-Dist: numpy>=2.1.0
36
36
  Requires-Dist: openai<3.0.0,>=2.54.0
37
37
  Requires-Dist: prime-pydantic-config[toml]>=0.4.3
38
+ Requires-Dist: prime-runs>=0.1.1
38
39
  Requires-Dist: prime-sandboxes>=0.2.39
39
40
  Requires-Dist: prime-tunnel>=0.1.8
40
41
  Requires-Dist: pydantic>=2.12.3
@@ -34,6 +34,7 @@ dependencies = [
34
34
  "openai>=2.54.0,<3.0.0",
35
35
  "prime-tunnel>=0.1.8",
36
36
  "prime-sandboxes>=0.2.39",
37
+ "prime-runs>=0.1.1",
37
38
  "pydantic>=2.12.3",
38
39
  "rich>=11.0.0",
39
40
  "tenacity>=8.5.0",
@@ -146,6 +147,7 @@ ty = "2026-07-28T00:00:00Z"
146
147
  # PrimeIntellect-published on PyPI (trusted publisher)
147
148
  prime-tunnel = false
148
149
  prime-sandboxes = false
150
+ prime-runs = false
149
151
  prime-pydantic-config = false
150
152
  renderers = false
151
153
 
@@ -188,6 +188,7 @@ def _eval_config(
188
188
  output_dir=output_dir.parent,
189
189
  run={"dir": output_dir.name},
190
190
  model=CI_MODEL,
191
+ push=False,
191
192
  )
192
193
 
193
194
 
@@ -281,17 +281,25 @@ def Overview(config: EvalConfig) -> Table:
281
281
 
282
282
 
283
283
  def _push_footer(push: "PushState | None") -> Group | None:
284
- """The `--push` status line under the rollouts, shown once the run finishes and the upload
285
- begins: dim `Pushing traces...` while it runs, then white `Traces pushed (<url>)` or red
286
- `Trace push failed (<err>)`. `None` (no line) until the upload starts and when `--push` is off."""
284
+ """The `--push` line under the rollouts: dim with the run's URL while it streams,
285
+ white once pushed, red when it failed. `None` when `--push` is off or the run stayed
286
+ local."""
287
287
  if push is None or not push.started:
288
288
  return None
289
- if not push.done:
290
- line = Text("Pushing traces...", style="dim")
291
- elif push.url:
289
+ if push.error and push.url:
290
+ # The run exists and holds what streamed up; only closing it out failed.
292
291
  line = Text(f"Traces pushed ({push.url})", style="white", overflow="fold")
293
- else:
292
+ line.append(f" not closed out: {push.error}", style="red")
293
+ elif push.error:
294
294
  line = Text(f"Trace push failed ({push.error})", style="red", overflow="fold")
295
+ elif push.incomplete and push.url:
296
+ # Closed out, but the uploader lost records: what landed is there, say what didn't.
297
+ line = Text(f"Traces pushed ({push.url})", style="white", overflow="fold")
298
+ line.append(f" incomplete: {push.incomplete}", style="red")
299
+ elif not push.finished:
300
+ line = Text(f"Pushing traces ({push.url})", style="dim", overflow="fold")
301
+ else:
302
+ line = Text(f"Traces pushed ({push.url})", style="white", overflow="fold")
295
303
  return Group(Rule(style="dim"), line)
296
304
 
297
305
 
@@ -134,10 +134,6 @@ def main(argv: list[str] | None = None) -> None:
134
134
  # Graceful cleanup has already run (each rollout's `finally`); partial results are on
135
135
  # disk. Exit on the conventional Ctrl-C code without a traceback.
136
136
  raise SystemExit(130)
137
- if config.push and config.rich is None:
138
- from verifiers.v1.utils.platform import push_traces
139
-
140
- push_traces(episodes, config)
141
137
  if (
142
138
  config.rich is None
143
139
  ): # --rich is the whole output; otherwise dump each trace as JSON
@@ -13,8 +13,8 @@ import asyncio
13
13
  import contextlib
14
14
  import logging
15
15
  import time
16
- from collections.abc import AsyncIterator, Awaitable, Callable
17
- from typing import cast
16
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
17
+ from typing import TypeVar, cast
18
18
 
19
19
  from verifiers.v1.cli.dashboard import dashboard
20
20
  from verifiers.v1.cli.eval import resume
@@ -30,9 +30,36 @@ from verifiers.v1.configs.cli.eval import EvalConfig
30
30
  from verifiers.v1.configs.serve import ServeConfig
31
31
  from verifiers.v1.env import Env, RunSlot
32
32
  from verifiers.v1.episode import Episode, EvalRunInfo
33
+ from verifiers.v1.utils.aio import run_shielded
34
+ from verifiers.v1.utils.platform import (
35
+ PushState,
36
+ abort_run,
37
+ finish_run,
38
+ log_episodes,
39
+ open_run,
40
+ )
33
41
 
34
42
  logger = logging.getLogger(__name__)
35
43
 
44
+ T = TypeVar("T")
45
+
46
+
47
+ async def gather_rollouts(rollouts: Iterable[Awaitable[T]]) -> list[T]:
48
+ """`asyncio.gather`, but one rollout failing cancels the rest and waits for them
49
+ to unwind, so nothing keeps uploading into a run the caller is already closing.
50
+ Not a `TaskGroup`: that wraps errors in an `ExceptionGroup`, and `main` would no
51
+ longer see a `KeyboardInterrupt` as Ctrl-C."""
52
+ tasks = [asyncio.ensure_future(rollout) for rollout in rollouts]
53
+ try:
54
+ return await asyncio.gather(*tasks)
55
+ except BaseException:
56
+ for task in tasks:
57
+ task.cancel()
58
+ # return_exceptions: wait for every task, not just the first to cancel.
59
+ await asyncio.gather(*tasks, return_exceptions=True)
60
+ raise
61
+
62
+
36
63
  RunSlotFn = Callable[[RunSlot], Awaitable[Episode]]
37
64
  OnComplete = Callable[[Episode], Awaitable[None]]
38
65
 
@@ -203,47 +230,55 @@ async def run_eval(config: EvalConfig) -> list[Episode]:
203
230
  asyncio.Semaphore(config.max_concurrent) if config.max_concurrent else None
204
231
  )
205
232
  write_lock = asyncio.Lock()
233
+ push_state = PushState()
234
+
235
+ # Opened before the first rollout so every episode streams as it lands.
236
+ run = open_run(config, push_state, num_examples=len(tasks))
237
+ # Resumed rollouts are part of this run too.
238
+ log_episodes(run, finished)
206
239
 
207
240
  async def on_complete(episode: Episode) -> None:
208
241
  episode.record_run(EvalRunInfo(id=config.run.id, name=config.run.name))
209
242
  await append_episode(out, episode, write_lock)
243
+ await asyncio.to_thread(log_episodes, run, [episode])
210
244
 
211
245
  backend = (
212
246
  _in_process(env, config, semaphore, on_complete)
213
247
  if env is not None
214
248
  else _server(config, config.serve, semaphore, on_complete)
215
249
  )
216
- async with backend as run_slot:
217
- # The display slots: in-process ones are the env's own (it fills their live
218
- # traces); a served rollout's is a client-side stand-in its worker never sees.
219
- planned = [
220
- slot
221
- for task, n in plan
222
- for slot in (
223
- env.slots(task, n)
224
- if env is not None
225
- else [RunSlot(task) for _ in range(n)]
250
+ # The run is closed out whatever breaks, backend setup and teardown included.
251
+ try:
252
+ async with backend as run_slot:
253
+ # The display slots: in-process ones are the env's own (it fills their live
254
+ # traces); a served rollout's is a client-side stand-in its worker never sees.
255
+ planned = [
256
+ slot
257
+ for task, n in plan
258
+ for slot in (
259
+ env.slots(task, n)
260
+ if env is not None
261
+ else [RunSlot(task) for _ in range(n)]
262
+ )
263
+ ]
264
+ slots = [RunSlot.finished(episode) for episode in finished] + planned
265
+ display = (
266
+ dashboard(slots, config, start, push=push_state)
267
+ if config.rich is not None
268
+ else contextlib.nullcontext()
226
269
  )
227
- ]
228
- slots = [RunSlot.finished(episode) for episode in finished] + planned
229
- push_state = None
230
- if config.push and config.rich is not None:
231
- from verifiers.v1.utils.platform import PushState
232
-
233
- push_state = PushState()
234
- display = (
235
- dashboard(slots, config, start, push=push_state)
236
- if config.rich is not None
237
- else contextlib.nullcontext()
238
- )
239
- async with display:
240
- results = await asyncio.gather(*(run_slot(slot) for slot in planned))
241
- episodes = finished + list(results)
242
- if (
243
- push_state is not None
244
- ): # upload off the event loop so the view keeps refreshing
245
- from verifiers.v1.utils.platform import push_traces
246
-
247
- push_state.started = True
248
- await asyncio.to_thread(push_traces, episodes, config, push_state)
270
+ async with display:
271
+ results = await gather_rollouts(run_slot(slot) for slot in planned)
272
+ episodes = finished + list(results)
273
+ # Drain and close out off the event loop so the view keeps refreshing.
274
+ # Shielded: a Ctrl-C here must not cancel the close-out before the
275
+ # worker picks it up (a cancelled executor item never runs), so it
276
+ # runs to completion first and the interrupt is re-raised after —
277
+ # by which point the run is finished and `abort_run` has nothing to do.
278
+ await run_shielded(
279
+ asyncio.to_thread(finish_run, run, episodes, push_state)
280
+ )
281
+ except BaseException as e:
282
+ await asyncio.to_thread(abort_run, run, e, push_state)
283
+ raise
249
284
  return episodes
@@ -48,13 +48,23 @@ class RunConfig(BaseConfig):
48
48
  """Run directory name — the run writes to `output_dir / dir`. Defaults to `run.name`;
49
49
  set it only when the directory should differ from the display name."""
50
50
 
51
- # TODO: fetch the id from the Prime SDK once runs are registered there.
52
- _id: str = PrivateAttr(default_factory=lambda: str(uuid4()))
51
+ _id: str | None = PrivateAttr(default=None)
53
52
 
54
53
  @property
55
54
  def id(self) -> str:
55
+ """The run's one id, assigned by `open_run` from the prime-runs handle: the
56
+ platform's evaluation id online, the SDK's local id otherwise. The run mints
57
+ none of its own, so the run dir, every trace and the dashboard agree."""
58
+ if self._id is None:
59
+ raise RuntimeError("the run has no id until `open_run` has opened it")
56
60
  return self._id
57
61
 
62
+ def assign_id(self, run_id: str) -> None:
63
+ """Called once by `open_run`, before the first rollout."""
64
+ if self._id is not None and self._id != run_id:
65
+ raise RuntimeError(f"the run already has id {self._id!r}")
66
+ self._id = run_id
67
+
58
68
 
59
69
  class EvalConfig(BaseConfig):
60
70
  env: SerializeAsAny[EnvConfig] = SingleAgentEnvConfig()
@@ -0,0 +1,164 @@
1
+ """The eval's run on the Prime Intellect platform (`--no-push` to keep it local)."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ import prime_runs as pr
11
+ from prime_runs.projection import (
12
+ build_samples, # noqa: F401 - prime-rl imports it from here
13
+ )
14
+
15
+ from verifiers.v1.configs.cli.eval import EvalConfig
16
+ from verifiers.v1.episode import Episode
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class PushState:
23
+ """The dashboard's view of the run: reads through to it, owns no I/O."""
24
+
25
+ run: pr.Run | None = None
26
+ error: str | None = None
27
+ incomplete: str | None = None
28
+ """Set when the run closed out but the uploader lost records on the way: the run
29
+ exists and holds what did land, so the footer says so rather than "failed"."""
30
+
31
+ @property
32
+ def url(self) -> str | None:
33
+ return self.run.url if self.run is not None else None
34
+
35
+ @property
36
+ def finished(self) -> bool:
37
+ return self.run is not None and self.run.finished
38
+
39
+ @property
40
+ def started(self) -> bool:
41
+ """A live run, or a reason there isn't one."""
42
+ return self.error is not None or self.url is not None
43
+
44
+
45
+ def open_run(config: EvalConfig, state: PushState, *, num_examples: int) -> pr.Run:
46
+ """Open the run this eval streams into, before the first rollout, and give the
47
+ config the run's id. A run that cannot be opened is logged and replaced by a
48
+ disabled one; the eval goes on."""
49
+ identity: dict[str, Any] = {
50
+ "name": config.run.name,
51
+ # Resolved by name via the hub's get-or-create; no taskset, nothing to attach to.
52
+ "environments": [config.env.taskset.id] if config.env.taskset.id else [],
53
+ "model": config.model,
54
+ "framework": "verifiers",
55
+ # The v0 keys the dashboard's lists read. The config itself is a follow-up:
56
+ # a dump or the launched file can carry credentials and needs masking first.
57
+ "config": {
58
+ "model": config.model,
59
+ "num_examples": num_examples,
60
+ "rollouts_per_example": config.num_rollouts,
61
+ },
62
+ }
63
+ if config.push and os.getenv(pr.MODE_ENV, "").strip().lower() == "disabled":
64
+ # The SDK's own kill switch; the explicit `mode="online"` below would override it.
65
+ logger.info("--push: %s=disabled; running without a platform run", pr.MODE_ENV)
66
+ elif config.push:
67
+ try:
68
+ state.run = pr.init(mode="online", **identity)
69
+ except Exception as e: # noqa: BLE001 - a failed upload must not fail the eval
70
+ logger.warning(
71
+ "--push: could not open the run (%s: %s); running without it",
72
+ type(e).__name__,
73
+ e,
74
+ )
75
+ state.error = f"{type(e).__name__}: {e}"
76
+ if state.run is None:
77
+ state.run = pr.init(mode="disabled", **identity)
78
+ # The run's one id: the platform's when online, the SDK's local one otherwise.
79
+ # The SDK keys every upload to it regardless; this is for the local records.
80
+ config.run.assign_id(state.run.id)
81
+ return state.run
82
+
83
+
84
+ def log_episodes(run: pr.Run, episodes: list[Episode]) -> None:
85
+ """Hand finished episodes to the run, best effort: the SDK already keeps upload
86
+ failures on its own thread, so this only guards the hand-off itself. A problem
87
+ here is the platform's, never the eval's."""
88
+ if not episodes:
89
+ return
90
+ try:
91
+ run.log_episodes(episodes)
92
+ except Exception as e: # noqa: BLE001 - the rollouts are on disk; report, don't raise
93
+ logger.warning(
94
+ "--push: could not queue %d episode(s) (%s: %s)",
95
+ len(episodes),
96
+ type(e).__name__,
97
+ e,
98
+ )
99
+
100
+
101
+ def finish_run(run: pr.Run, episodes: list[Episode], state: PushState) -> None:
102
+ """Drain, write the run's aggregates, close it out. Blocking: call it off the loop."""
103
+ try:
104
+ summary = pr.metrics.from_episodes(episodes)
105
+ except Exception as e: # noqa: BLE001 - close the run even without its headline
106
+ logger.warning(
107
+ "--push: could not aggregate the run's metrics (%s: %s)",
108
+ type(e).__name__,
109
+ e,
110
+ )
111
+ summary = None
112
+ _close(run, state, summary=summary)
113
+
114
+
115
+ def abort_run(run: pr.Run, error: BaseException, state: PushState) -> None:
116
+ """Close the run out after the eval broke, so it doesn't sit at running. Not
117
+ for a break during `finish_run`: that close-out completes on its own thread
118
+ and the SDK lets the first `finish()` decide the status — it sets `run.status`
119
+ as it starts, so an in-flight one is visible here before it is `finished`."""
120
+ if run.finished or run.status is not pr.RunStatus.RUNNING:
121
+ return
122
+ if isinstance(error, (KeyboardInterrupt, asyncio.CancelledError)):
123
+ status, message = pr.RunStatus.CANCELLED, "interrupted"
124
+ else:
125
+ status, message = pr.RunStatus.FAILED, f"{type(error).__name__}: {error}"
126
+ _close(run, state, status=status, error=message)
127
+
128
+
129
+ def _close(
130
+ run: pr.Run,
131
+ state: PushState,
132
+ summary: Mapping[str, Any] | None = None,
133
+ status: pr.RunStatus = pr.RunStatus.COMPLETED,
134
+ error: str | None = None,
135
+ ) -> None:
136
+ """`run.finish()`, best effort: the results are on disk, so nothing here may raise."""
137
+ try:
138
+ run.finish(summary, status=status, error=error)
139
+ except Exception as e: # noqa: BLE001 - the run is over; report, don't raise
140
+ logger.warning(
141
+ "--push: could not close out the run (%s: %s)", type(e).__name__, e
142
+ )
143
+ if state.error is None:
144
+ state.error = f"{type(e).__name__}: {e}"
145
+ else:
146
+ state.incomplete = _losses(run)
147
+ if state.incomplete:
148
+ logger.warning("--push: %s, but %s", status.value, state.incomplete)
149
+ if run.url:
150
+ # The run's own status: `finish()` is a no-op once another caller closed it.
151
+ logger.info("--push: %s -> %s", run.status.value, run.url)
152
+
153
+
154
+ def _losses(run: pr.Run) -> str | None:
155
+ """What the uploader could not store, or `None`. A sink that switched itself off
156
+ quietly (Prime Traces outside the beta) is not a loss and is not counted here."""
157
+ parts = [
158
+ f"{count} record(s) not stored by the {sink} sink"
159
+ for sink, count in sorted(run.failed_records.items())
160
+ if count
161
+ ]
162
+ if run.dropped_records:
163
+ parts.append(f"{run.dropped_records} record(s) never queued (uploader overrun)")
164
+ return "; ".join(parts) or None