evalrx 0.1.2__py3-none-any.whl

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 (339) hide show
  1. evalrx/__init__.py +139 -0
  2. evalrx/agent_assets/__init__.py +2 -0
  3. evalrx/agent_assets/skills/README.md +28 -0
  4. evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
  5. evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
  6. evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
  7. evalrx/agent_assets/skills/nature-figure/README.md +412 -0
  8. evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
  9. evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
  10. evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
  11. evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
  12. evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
  13. evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
  14. evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
  15. evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
  16. evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
  17. evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
  18. evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
  19. evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
  20. evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
  21. evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
  22. evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
  23. evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
  24. evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
  25. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
  26. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
  27. evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
  28. evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
  29. evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
  30. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
  31. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
  32. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
  33. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
  34. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
  35. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
  36. evalrx/agent_assets/skills.py +27 -0
  37. evalrx/agent_runtime/__init__.py +78 -0
  38. evalrx/agent_runtime/_docker_runner.py +89 -0
  39. evalrx/agent_runtime/cli_runtime.py +103 -0
  40. evalrx/agent_runtime/cli_transcript.py +138 -0
  41. evalrx/agent_runtime/cli_types.py +68 -0
  42. evalrx/agent_runtime/codegen/__init__.py +5 -0
  43. evalrx/agent_runtime/codegen/runner.py +94 -0
  44. evalrx/agent_runtime/experiment_harness.py +117 -0
  45. evalrx/agent_runtime/factory.py +102 -0
  46. evalrx/agent_runtime/json_shape.py +44 -0
  47. evalrx/agent_runtime/judges/__init__.py +28 -0
  48. evalrx/agent_runtime/judges/agy.py +179 -0
  49. evalrx/agent_runtime/judges/autodetect.py +135 -0
  50. evalrx/agent_runtime/judges/claude.py +159 -0
  51. evalrx/agent_runtime/judges/codex.py +120 -0
  52. evalrx/agent_runtime/providers/__init__.py +21 -0
  53. evalrx/agent_runtime/providers/antigravity.py +31 -0
  54. evalrx/agent_runtime/providers/base.py +145 -0
  55. evalrx/agent_runtime/providers/claude_code.py +49 -0
  56. evalrx/agent_runtime/providers/codex.py +37 -0
  57. evalrx/agent_runtime/providers/gemini_cli.py +26 -0
  58. evalrx/agent_runtime/providers/kimi_cli.py +27 -0
  59. evalrx/agent_runtime/providers/opencode.py +27 -0
  60. evalrx/agent_runtime/providers/registry.py +58 -0
  61. evalrx/agent_runtime/sandbox.py +517 -0
  62. evalrx/agent_runtime/skill_audit.py +143 -0
  63. evalrx/agent_runtime/skills/__init__.py +19 -0
  64. evalrx/agent_runtime/skills/installer.py +68 -0
  65. evalrx/agent_runtime/skills/prompt_policy.py +86 -0
  66. evalrx/agent_runtime/skills/resolver.py +19 -0
  67. evalrx/analysis/__init__.py +132 -0
  68. evalrx/analysis/adjudicate.py +154 -0
  69. evalrx/analysis/analysis_module.py +361 -0
  70. evalrx/analysis/api.py +171 -0
  71. evalrx/analysis/case_studio.py +651 -0
  72. evalrx/analysis/cli.py +114 -0
  73. evalrx/analysis/dashboard.py +350 -0
  74. evalrx/analysis/eval_case_matrix.py +118 -0
  75. evalrx/analysis/eval_viz_theme.py +833 -0
  76. evalrx/analysis/explore_run.py +333 -0
  77. evalrx/analysis/explorer.py +1276 -0
  78. evalrx/analysis/failure_modes.py +607 -0
  79. evalrx/analysis/fused_pipeline.py +489 -0
  80. evalrx/analysis/holdout.py +300 -0
  81. evalrx/analysis/hypothesis_agent.py +230 -0
  82. evalrx/analysis/narration.py +177 -0
  83. evalrx/analysis/operationalize.py +442 -0
  84. evalrx/analysis/plain_language.py +42 -0
  85. evalrx/analysis/planner.py +283 -0
  86. evalrx/analysis/probe_search.py +203 -0
  87. evalrx/analysis/profile.py +268 -0
  88. evalrx/analysis/prompts/__init__.py +0 -0
  89. evalrx/analysis/prompts/explorer.py +417 -0
  90. evalrx/analysis/prompts/failure_modes.py +33 -0
  91. evalrx/analysis/prompts/holdout.py +27 -0
  92. evalrx/analysis/prompts/hypothesis_agent.py +78 -0
  93. evalrx/analysis/prompts/run_codebase.py +47 -0
  94. evalrx/analysis/prompts/stats_agent.py +72 -0
  95. evalrx/analysis/prompts/stats_tool_generator.py +43 -0
  96. evalrx/analysis/result_marker.py +47 -0
  97. evalrx/analysis/run_codebase.py +242 -0
  98. evalrx/analysis/run_view.py +205 -0
  99. evalrx/analysis/stage_views.py +93 -0
  100. evalrx/analysis/stats_agent.py +944 -0
  101. evalrx/analysis/stats_tool_agent.py +261 -0
  102. evalrx/analysis/stats_tool_generator.py +415 -0
  103. evalrx/analysis/stats_tools.py +1153 -0
  104. evalrx/analysis/trajectory_records.py +193 -0
  105. evalrx/analysis/workbench.py +431 -0
  106. evalrx/analyzers/__init__.py +42 -0
  107. evalrx/analyzers/agent/__init__.py +25 -0
  108. evalrx/analyzers/agent/counterfactual.py +84 -0
  109. evalrx/analyzers/agent/first_error_judge.py +96 -0
  110. evalrx/analyzers/agent/ignored_obs.py +81 -0
  111. evalrx/analyzers/agent/loop_detect.py +79 -0
  112. evalrx/analyzers/agent/reliability.py +165 -0
  113. evalrx/analyzers/agent/tool_shap.py +225 -0
  114. evalrx/analyzers/agent/trajectory_rubric.py +168 -0
  115. evalrx/analyzers/attention/__init__.py +19 -0
  116. evalrx/analyzers/attention/relative_attn.py +610 -0
  117. evalrx/analyzers/attention/rollout.py +73 -0
  118. evalrx/analyzers/attention/sink.py +56 -0
  119. evalrx/analyzers/attention/summary.py +190 -0
  120. evalrx/analyzers/attribution/__init__.py +6 -0
  121. evalrx/analyzers/attribution/generic_attn.py +31 -0
  122. evalrx/analyzers/attribution/gradcam.py +30 -0
  123. evalrx/analyzers/base.py +12 -0
  124. evalrx/analyzers/geometry/__init__.py +6 -0
  125. evalrx/analyzers/geometry/cka.py +70 -0
  126. evalrx/analyzers/geometry/linear_probe.py +157 -0
  127. evalrx/analyzers/hallucination/__init__.py +9 -0
  128. evalrx/analyzers/hallucination/chair.py +78 -0
  129. evalrx/analyzers/hallucination/opera.py +29 -0
  130. evalrx/analyzers/hallucination/pope.py +119 -0
  131. evalrx/analyzers/hallucination/selfcheck.py +155 -0
  132. evalrx/analyzers/hallucination/vcd.py +29 -0
  133. evalrx/analyzers/lens/__init__.py +7 -0
  134. evalrx/analyzers/lens/layer_contrast.py +133 -0
  135. evalrx/analyzers/lens/logit_lens.py +138 -0
  136. evalrx/analyzers/lens/tuned_lens.py +30 -0
  137. evalrx/analyzers/patching/__init__.py +5 -0
  138. evalrx/analyzers/patching/causal_trace.py +30 -0
  139. evalrx/analyzers/perturbation/__init__.py +23 -0
  140. evalrx/analyzers/perturbation/_shapley.py +54 -0
  141. evalrx/analyzers/perturbation/context_shap.py +174 -0
  142. evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
  143. evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
  144. evalrx/analyzers/perturbation/mm_shap.py +146 -0
  145. evalrx/analyzers/perturbation/modality_ablation.py +196 -0
  146. evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
  147. evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
  148. evalrx/analyzers/perturbation/rise.py +94 -0
  149. evalrx/analyzers/perturbation/vl_shap.py +102 -0
  150. evalrx/analyzers/reasoning/__init__.py +33 -0
  151. evalrx/analyzers/reasoning/_text.py +328 -0
  152. evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
  153. evalrx/analyzers/reasoning/arith_audit.py +226 -0
  154. evalrx/analyzers/reasoning/contamination.py +214 -0
  155. evalrx/analyzers/reasoning/knowledge_split.py +253 -0
  156. evalrx/analyzers/reasoning/self_repair.py +246 -0
  157. evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
  158. evalrx/analyzers/reasoning/termination_audit.py +258 -0
  159. evalrx/analyzers/uncertainty/__init__.py +18 -0
  160. evalrx/analyzers/uncertainty/calibration.py +174 -0
  161. evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
  162. evalrx/analyzers/uncertainty/entropy.py +90 -0
  163. evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
  164. evalrx/analyzers/uncertainty/self_consistency.py +204 -0
  165. evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
  166. evalrx/cli.py +411 -0
  167. evalrx/config.py +77 -0
  168. evalrx/contract/__init__.py +179 -0
  169. evalrx/contract/common.py +452 -0
  170. evalrx/contract/emit.py +948 -0
  171. evalrx/contract/export.py +237 -0
  172. evalrx/contract/m1.py +325 -0
  173. evalrx/contract/m2.py +317 -0
  174. evalrx/contract/m3.py +165 -0
  175. evalrx/contract/m4.py +130 -0
  176. evalrx/contract/m5.py +292 -0
  177. evalrx/contract/methodology.py +76 -0
  178. evalrx/contract/pre_m1.py +58 -0
  179. evalrx/contract/typescript.py +140 -0
  180. evalrx/core/__init__.py +85 -0
  181. evalrx/core/analyzer.py +174 -0
  182. evalrx/core/capability.py +54 -0
  183. evalrx/core/case.py +443 -0
  184. evalrx/core/experiment.py +106 -0
  185. evalrx/core/model.py +198 -0
  186. evalrx/core/pipeline.py +42 -0
  187. evalrx/core/registry.py +142 -0
  188. evalrx/core/result.py +64 -0
  189. evalrx/core/spec.py +173 -0
  190. evalrx/core/tokentype.py +165 -0
  191. evalrx/core/tool.py +92 -0
  192. evalrx/datasets/__init__.py +41 -0
  193. evalrx/datasets/base.py +68 -0
  194. evalrx/datasets/gui_os.py +52 -0
  195. evalrx/datasets/llm_qa.py +57 -0
  196. evalrx/datasets/pure_qa.py +12 -0
  197. evalrx/datasets/vlm_qa.py +695 -0
  198. evalrx/datasets/web_search_qa.py +52 -0
  199. evalrx/eval_agent/__init__.py +341 -0
  200. evalrx/eval_agent/_tools.py +81 -0
  201. evalrx/eval_agent/ab_runner.py +50 -0
  202. evalrx/eval_agent/agentic/__init__.py +43 -0
  203. evalrx/eval_agent/agentic/actions.py +216 -0
  204. evalrx/eval_agent/agentic/board.py +107 -0
  205. evalrx/eval_agent/agentic/loop.py +190 -0
  206. evalrx/eval_agent/agentic/tools.py +538 -0
  207. evalrx/eval_agent/checkpoint.py +57 -0
  208. evalrx/eval_agent/cli_agent.py +59 -0
  209. evalrx/eval_agent/cli_skills.py +5 -0
  210. evalrx/eval_agent/evolution.py +396 -0
  211. evalrx/eval_agent/git_manager.py +215 -0
  212. evalrx/eval_agent/hypothesis.py +172 -0
  213. evalrx/eval_agent/label_quarantine.py +209 -0
  214. evalrx/eval_agent/legacy.py +530 -0
  215. evalrx/eval_agent/log_schema.py +497 -0
  216. evalrx/eval_agent/loop.py +2159 -0
  217. evalrx/eval_agent/loop_reports.py +116 -0
  218. evalrx/eval_agent/model_instrumentation.py +282 -0
  219. evalrx/eval_agent/narration.py +193 -0
  220. evalrx/eval_agent/nl_runner.py +460 -0
  221. evalrx/eval_agent/orchestrator.py +61 -0
  222. evalrx/eval_agent/preregister.py +93 -0
  223. evalrx/eval_agent/prompts/__init__.py +1 -0
  224. evalrx/eval_agent/prompts/agentic.py +46 -0
  225. evalrx/eval_agent/prompts/case_discovery.py +25 -0
  226. evalrx/eval_agent/prompts/diagnosis.py +125 -0
  227. evalrx/eval_agent/prompts/experiment_writer.py +265 -0
  228. evalrx/eval_agent/prompts/explore_step.py +37 -0
  229. evalrx/eval_agent/prompts/fix_agent.py +257 -0
  230. evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
  231. evalrx/eval_agent/prompts/nl_runner.py +38 -0
  232. evalrx/eval_agent/prompts/probe_agent.py +25 -0
  233. evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
  234. evalrx/eval_agent/prompts/probe_generator.py +35 -0
  235. evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
  236. evalrx/eval_agent/report.py +58 -0
  237. evalrx/eval_agent/run_context.py +354 -0
  238. evalrx/eval_agent/run_log.schema.json +1215 -0
  239. evalrx/eval_agent/run_logger_v2.py +1764 -0
  240. evalrx/eval_agent/run_metadata.py +208 -0
  241. evalrx/eval_agent/stages/__init__.py +56 -0
  242. evalrx/eval_agent/stages/case_discovery.py +293 -0
  243. evalrx/eval_agent/stages/diagnosis.py +1017 -0
  244. evalrx/eval_agent/stages/experiment_writer.py +1634 -0
  245. evalrx/eval_agent/stages/fix_agent.py +3916 -0
  246. evalrx/eval_agent/stages/fix_internals.py +499 -0
  247. evalrx/eval_agent/stages/fix_pipeline.py +725 -0
  248. evalrx/eval_agent/stages/fix_tiers.py +187 -0
  249. evalrx/eval_agent/stages/fix_tools.py +1034 -0
  250. evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
  251. evalrx/eval_agent/stages/probe.py +439 -0
  252. evalrx/eval_agent/stages/probe_agent.py +1079 -0
  253. evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
  254. evalrx/eval_agent/stages/probe_generator.py +326 -0
  255. evalrx/eval_agent/stages/probe_search_agent.py +106 -0
  256. evalrx/eval_agent/stages/protocol.py +112 -0
  257. evalrx/eval_agent/stages/repair_catalog.py +273 -0
  258. evalrx/eval_agent/stages/surgery.py +524 -0
  259. evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
  260. evalrx/eval_agent/store.py +231 -0
  261. evalrx/logging_utils.py +112 -0
  262. evalrx/models/__init__.py +161 -0
  263. evalrx/models/_discover.py +101 -0
  264. evalrx/models/agent.py +380 -0
  265. evalrx/models/backends/__init__.py +58 -0
  266. evalrx/models/backends/api.py +169 -0
  267. evalrx/models/backends/base.py +57 -0
  268. evalrx/models/backends/gemini_compat.py +579 -0
  269. evalrx/models/backends/hf_local.py +2074 -0
  270. evalrx/models/backends/openai_compat.py +301 -0
  271. evalrx/models/backends/vllm_offline.py +116 -0
  272. evalrx/models/base.py +24 -0
  273. evalrx/models/blackbox/__init__.py +4 -0
  274. evalrx/models/blackbox/agent.py +31 -0
  275. evalrx/models/blackbox/base.py +29 -0
  276. evalrx/models/blackbox/gemini.py +279 -0
  277. evalrx/models/blackbox/llm_api.py +17 -0
  278. evalrx/models/blackbox/vlm_api.py +17 -0
  279. evalrx/models/compose.py +66 -0
  280. evalrx/models/inference.py +88 -0
  281. evalrx/models/paper_methods/__init__.py +8 -0
  282. evalrx/models/paper_methods/aad.py +53 -0
  283. evalrx/models/paper_methods/ifcd.py +204 -0
  284. evalrx/models/paper_methods/pai.py +164 -0
  285. evalrx/models/paper_methods/tcd.py +202 -0
  286. evalrx/models/paper_methods/vcd.py +45 -0
  287. evalrx/models/paper_methods/vicrop.py +137 -0
  288. evalrx/models/toolcodec.py +143 -0
  289. evalrx/models/tools/__init__.py +20 -0
  290. evalrx/models/tools/perception.py +300 -0
  291. evalrx/models/tools/visual.py +174 -0
  292. evalrx/models/whitebox/__init__.py +26 -0
  293. evalrx/models/whitebox/agent.py +31 -0
  294. evalrx/models/whitebox/base.py +24 -0
  295. evalrx/models/whitebox/qwen.py +61 -0
  296. evalrx/models/whitebox/qwen2_5_omni.py +29 -0
  297. evalrx/models/whitebox/qwen2_audio.py +25 -0
  298. evalrx/models/whitebox/qwen_omni.py +53 -0
  299. evalrx/models/whitebox/qwen_vl.py +62 -0
  300. evalrx/observability/__init__.py +21 -0
  301. evalrx/observability/envelope.py +122 -0
  302. evalrx/observability/outbox.py +111 -0
  303. evalrx/observability/tracer.py +882 -0
  304. evalrx/reporting/__init__.py +28 -0
  305. evalrx/reporting/case_study.py +947 -0
  306. evalrx/reporting/compiler.py +587 -0
  307. evalrx/reporting/dynamic.py +1882 -0
  308. evalrx/reporting/html_report.py +2225 -0
  309. evalrx/reporting/langfuse_exporter.py +38 -0
  310. evalrx/reporting/langfuse_source.py +155 -0
  311. evalrx/reporting/model.py +151 -0
  312. evalrx/reporting/run_events.py +184 -0
  313. evalrx/reporting/server.py +557 -0
  314. evalrx/reporting/stages.py +58 -0
  315. evalrx/reporting/static_export.py +142 -0
  316. evalrx/reporting/web_dist/index.html +146 -0
  317. evalrx/specs.py +727 -0
  318. evalrx/stats/__init__.py +47 -0
  319. evalrx/stats/api.py +192 -0
  320. evalrx/stats/bootstrap.py +86 -0
  321. evalrx/stats/ebh.py +27 -0
  322. evalrx/stats/evalue.py +98 -0
  323. evalrx/stats/friedman.py +138 -0
  324. evalrx/stats/mcnemar.py +40 -0
  325. evalrx/stats/multiplicity.py +159 -0
  326. evalrx/stats/subset_sampling.py +55 -0
  327. evalrx/term_links.py +43 -0
  328. evalrx/viz/__init__.py +7 -0
  329. evalrx/viz/labels.py +77 -0
  330. evalrx/viz/prompts.py +39 -0
  331. evalrx/viz/renderer.py +590 -0
  332. evalrx/viz/schema.py +36 -0
  333. evalrx/viz/style.py +134 -0
  334. evalrx-0.1.2.dist-info/METADATA +532 -0
  335. evalrx-0.1.2.dist-info/RECORD +339 -0
  336. evalrx-0.1.2.dist-info/WHEEL +5 -0
  337. evalrx-0.1.2.dist-info/entry_points.txt +3 -0
  338. evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
  339. evalrx-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,120 @@
1
+ """OpenAI Codex CLI wrapped as a text-generation judge model.
2
+
3
+ The judge receives only the prompt placed in an isolated temporary directory.
4
+ It uses ``codex exec`` in read-only mode; coding-agent uses elsewhere in the
5
+ pipeline are configured independently through :class:`CliAgentConfig`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import pathlib
12
+ import shutil
13
+ import subprocess
14
+ import tempfile
15
+ import warnings
16
+ from pathlib import Path
17
+
18
+
19
+ class CodexModel:
20
+ """Codex CLI wrapped behind EvalRX' ``generate`` model protocol."""
21
+
22
+ key = "codex"
23
+
24
+ def __init__(
25
+ self,
26
+ binary_path: str = "",
27
+ timeout_sec: int = 300,
28
+ model: str = "gpt-5.6-terra",
29
+ effort: str = "",
30
+ ) -> None:
31
+ from evalrx.core.capability import Capability
32
+
33
+ binary = binary_path or shutil.which("codex") or ""
34
+ if not binary or not os.path.isfile(binary) or not os.access(binary, os.X_OK):
35
+ raise RuntimeError(
36
+ "CodexModel: 'codex' binary not found or not executable. "
37
+ "Set CODEX_PATH=$(which codex) and re-run, or pass binary_path= explicitly."
38
+ )
39
+ self._binary = binary
40
+ self._timeout_sec = timeout_sec
41
+ self._model = model
42
+ self._effort = effort
43
+ self.capabilities = frozenset({Capability.GENERATE})
44
+ self.modalities = frozenset({"text"})
45
+
46
+ def generate(
47
+ self,
48
+ inputs: object,
49
+ *,
50
+ images: "list[Path] | None" = None,
51
+ **kwargs: object,
52
+ ) -> str:
53
+ """Run an isolated, non-interactive Codex turn and return its answer.
54
+
55
+ The prompt is passed over stdin, never as an argv entry, so an M2
56
+ evidence table cannot hit Linux's per-argument size limit. Images are
57
+ copied into the isolated workdir and named in the prompt; this mirrors
58
+ the existing CLI judge contract without exposing the repository.
59
+ """
60
+ workspace = tempfile.mkdtemp(prefix="codex_judge_")
61
+ output_path = Path(workspace) / "answer.txt"
62
+ try:
63
+ prompt_text = str(inputs)
64
+ valid = [path for path in images or [] if isinstance(path, pathlib.Path) and path.exists()]
65
+ if valid:
66
+ names: list[str] = []
67
+ for path in valid:
68
+ destination = Path(workspace) / path.name
69
+ shutil.copy2(path, destination)
70
+ names.append(destination.name)
71
+ prompt_text = f"Images available in workspace: {', '.join(names)}\n\n{prompt_text}"
72
+
73
+ cmd = [
74
+ self._binary,
75
+ "exec",
76
+ "-",
77
+ "--sandbox", "read-only",
78
+ "--skip-git-repo-check",
79
+ "--ephemeral",
80
+ "--output-last-message", str(output_path),
81
+ "-C", workspace,
82
+ ]
83
+ if self._model:
84
+ cmd += ["--model", self._model]
85
+ if self._effort:
86
+ cmd += ["-c", f'model_reasoning_effort="{self._effort}"']
87
+ try:
88
+ proc = subprocess.run(
89
+ cmd,
90
+ input=prompt_text,
91
+ capture_output=True,
92
+ timeout=self._timeout_sec,
93
+ text=True,
94
+ encoding="utf-8",
95
+ errors="replace",
96
+ env={**os.environ},
97
+ )
98
+ except subprocess.TimeoutExpired as exc:
99
+ raise RuntimeError(
100
+ f"CodexModel: codex timed out after {self._timeout_sec}s"
101
+ ) from exc
102
+
103
+ output = output_path.read_text(encoding="utf-8", errors="replace").strip() if output_path.is_file() else ""
104
+ if proc.returncode != 0 and not output:
105
+ reason = (proc.stderr or proc.stdout or "").strip()[-500:]
106
+ raise RuntimeError(f"CodexModel: codex exited {proc.returncode}: {reason}")
107
+ if not output:
108
+ warnings.warn(
109
+ "CodexModel: codex returned an empty response; the caller will fall back to a non-LLM path.",
110
+ stacklevel=2,
111
+ )
112
+ return output
113
+ finally:
114
+ shutil.rmtree(workspace, ignore_errors=True)
115
+
116
+ def __repr__(self) -> str:
117
+ return (
118
+ f"CodexModel(binary={self._binary!r}, model={self._model!r}, "
119
+ f"effort={self._effort!r})"
120
+ )
@@ -0,0 +1,21 @@
1
+ """CLI coding-provider adapters."""
2
+
3
+ from evalrx.agent_runtime.providers.antigravity import AntigravityAgent
4
+ from evalrx.agent_runtime.providers.base import CliAgentBase
5
+ from evalrx.agent_runtime.providers.claude_code import ClaudeCodeAgent
6
+ from evalrx.agent_runtime.providers.codex import CodexAgent
7
+ from evalrx.agent_runtime.providers.gemini_cli import GeminiCliAgent
8
+ from evalrx.agent_runtime.providers.kimi_cli import KimiCliAgent
9
+ from evalrx.agent_runtime.providers.opencode import OpenCodeAgent
10
+ from evalrx.agent_runtime.providers.registry import create_cli_agent
11
+
12
+ __all__ = [
13
+ "AntigravityAgent",
14
+ "CliAgentBase",
15
+ "ClaudeCodeAgent",
16
+ "CodexAgent",
17
+ "GeminiCliAgent",
18
+ "KimiCliAgent",
19
+ "OpenCodeAgent",
20
+ "create_cli_agent",
21
+ ]
@@ -0,0 +1,31 @@
1
+ """Antigravity CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.providers.base import CliAgentBase
8
+
9
+
10
+ class AntigravityAgent(CliAgentBase):
11
+ """Antigravity CLI backend (``agy -p``)."""
12
+
13
+ _provider_name = "antigravity"
14
+
15
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
16
+ cmd = [
17
+ self._binary,
18
+ "-p",
19
+ prompt,
20
+ # Limit terminal/file access to the explicitly added workdir. A
21
+ # repair author must never inspect benchmark manifests, gold labels,
22
+ # or confirmation outputs while selecting a candidate.
23
+ "--sandbox",
24
+ "--dangerously-skip-permissions",
25
+ "--add-dir",
26
+ str(workdir),
27
+ ]
28
+ if self._model:
29
+ cmd += ["--model", self._model]
30
+ cmd.extend(self._extra_args)
31
+ return cmd
@@ -0,0 +1,145 @@
1
+ """Base class for CLI coding-provider adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ from evalrx.agent_runtime.cli_runtime import ProcessRun, SubprocessRunner, collect_py_files
9
+ from evalrx.agent_runtime.cli_transcript import RAW_OUTPUT_CAP
10
+ from evalrx.agent_runtime.cli_types import CliAgentResult
11
+ from evalrx.agent_runtime.skill_audit import build_agent_audit
12
+ from evalrx.agent_runtime.skills.installer import SkillInstaller
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class CliAgentBase:
18
+ """Subprocess runner shared by all CLI coding providers."""
19
+
20
+ _provider_name: str = "unknown"
21
+
22
+ # Every provider's ``_build_cmd`` places *prompt* directly in argv (``-p
23
+ # <prompt>`` / ``exec <prompt>`` / ``--message <prompt>``). This
24
+ # framework's prompts (a full M1 evidence dump, an L1/L2 fix-candidate
25
+ # brief with per-case tables, ...) can exceed the kernel's argv+envp size
26
+ # limit -- observed in practice as ``OSError: [Errno 7] Argument list
27
+ # too long`` on exec. Above this conservative threshold, ``run()`` spills
28
+ # the prompt to a file in *workdir* instead of passing it inline.
29
+ _LARGE_PROMPT_BYTES = 60_000
30
+
31
+ def __init__(
32
+ self,
33
+ binary_path: str,
34
+ model: str = "",
35
+ max_budget_usd: float = 5.0,
36
+ timeout_sec: int = 600,
37
+ extra_args: list[str] | None = None,
38
+ skills: list[str] | None = None,
39
+ allow_skills: bool = False,
40
+ ) -> None:
41
+ self._binary = binary_path
42
+ self._model = model
43
+ self._max_budget_usd = max_budget_usd
44
+ self._timeout_sec = timeout_sec
45
+ self._extra_args: list[str] = extra_args or []
46
+ self._skills: list[str] = list(skills or [])
47
+ self._allow_skills: bool = bool(allow_skills or self._skills)
48
+ self._runner = SubprocessRunner()
49
+ self._skill_installer = self._make_skill_installer(self._skills)
50
+
51
+ def _make_skill_installer(self, skills: list[str]) -> SkillInstaller:
52
+ return SkillInstaller(skills)
53
+
54
+ def _install_skills(self, workdir: Path) -> None:
55
+ """Install configured skills into the provider-visible workspace."""
56
+ self._skill_installer.install(workdir)
57
+
58
+ def run(
59
+ self,
60
+ prompt: str,
61
+ workdir: Path,
62
+ timeout_sec: int | None = None,
63
+ ) -> CliAgentResult:
64
+ """Invoke the CLI provider with *prompt* and return collected files."""
65
+ timeout = timeout_sec if timeout_sec is not None else self._timeout_sec
66
+ workdir.mkdir(parents=True, exist_ok=True)
67
+ self._install_skills(workdir)
68
+ cli_prompt = prompt
69
+ if len(prompt.encode("utf-8", errors="replace")) > self._LARGE_PROMPT_BYTES:
70
+ (workdir / "prompt.txt").write_text(prompt, encoding="utf-8")
71
+ cli_prompt = (
72
+ "Your full task instructions are in the file `prompt.txt` in "
73
+ "this workspace (too large to pass inline). Read it "
74
+ "completely and follow it exactly -- do not summarize, "
75
+ "truncate, or skip any part of it."
76
+ )
77
+ cmd = self._build_cmd(cli_prompt, workdir)
78
+ logger.debug("%s: running %s", self._provider_name, cmd[0])
79
+
80
+ run = self._run_subprocess(cmd, workdir, timeout)
81
+ return self._build_result(workdir, run)
82
+
83
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]: # pragma: no cover
84
+ raise NotImplementedError
85
+
86
+ def _postprocess_output(self, stdout: str) -> tuple[str, dict | None]:
87
+ return stdout[:RAW_OUTPUT_CAP], None
88
+
89
+ def _run_subprocess(self, cmd: list[str], workdir: Path, timeout_sec: int) -> ProcessRun:
90
+ return self._runner.run(cmd, workdir, timeout_sec)
91
+
92
+ def _build_result(self, workdir: Path, run: ProcessRun) -> CliAgentResult:
93
+ files = collect_py_files(workdir)
94
+ error: str | None = None
95
+ if run.timed_out:
96
+ error = f"[TIMEOUT] agent killed after {run.elapsed_sec:.0f}s"
97
+ elif run.returncode != 0 and not files:
98
+ error = f"Exited {run.returncode}: {run.stderr[:500]}"
99
+
100
+ # Persist the UNTRUNCATED raw agent stream before any rendering caps
101
+ # apply — this is the most detailed audit layer (every tool call and
102
+ # tool result). The truncated rendering stays in CliAgentResult.raw_output
103
+ # for UIs; the full stream is durable evidence in the workdir.
104
+ raw_stream_path = ""
105
+ if run.stdout:
106
+ try:
107
+ stream_file = workdir / "agent_raw_stream.txt"
108
+ stream_file.write_text(run.stdout, encoding="utf-8")
109
+ raw_stream_path = "agent_raw_stream.txt"
110
+ except OSError:
111
+ logger.debug("could not persist raw agent stream for %s", self._provider_name)
112
+
113
+ raw_output, usage = self._postprocess_output(run.stdout)
114
+ audit = build_agent_audit(
115
+ provider=self._provider_name,
116
+ skills=self._skills,
117
+ workdir=workdir,
118
+ stdout=run.stdout,
119
+ stderr=run.stderr,
120
+ returncode=run.returncode,
121
+ timed_out=run.timed_out,
122
+ elapsed_sec=run.elapsed_sec,
123
+ files=files,
124
+ error=error,
125
+ )
126
+ if audit is not None and raw_stream_path:
127
+ audit.setdefault("execution", {})["raw_stream"] = raw_stream_path
128
+ logger.debug(
129
+ "%s: rc=%d files=%s elapsed=%.1fs timed_out=%s",
130
+ self._provider_name,
131
+ run.returncode,
132
+ list(files),
133
+ run.elapsed_sec,
134
+ run.timed_out,
135
+ )
136
+ return CliAgentResult(
137
+ files=files,
138
+ provider_name=self._provider_name,
139
+ elapsed_sec=run.elapsed_sec,
140
+ raw_output=raw_output,
141
+ usage=usage,
142
+ error=error,
143
+ audit=audit,
144
+ raw_stream_path=raw_stream_path,
145
+ )
@@ -0,0 +1,49 @@
1
+ """Claude Code CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.cli_transcript import render_claude_stream
8
+ from evalrx.agent_runtime.providers.base import CliAgentBase
9
+
10
+
11
+ class ClaudeCodeAgent(CliAgentBase):
12
+ """Claude Code CLI backend (``claude -p``)."""
13
+
14
+ _provider_name = "claude_code"
15
+
16
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
17
+ # No --dangerously-skip-permissions. The narrow grant below already says
18
+ # exactly what this agent may do, so the blanket flag added nothing --
19
+ # and it is refused outright under root unless something sets
20
+ # IS_SANDBOX=1, which our compose files do and a bare root box (Colab, a
21
+ # plain container, CI) does not. There the coder exited in 0.7s having
22
+ # written no files, ExperimentWriter aborted for want of a .py, and every
23
+ # L2-and-above repair candidate was dead on arrival while the log said
24
+ # only "CLI agent produced no .py files".
25
+ #
26
+ # Same reasoning as the judge in agent_runtime/judges/claude.py: ask for
27
+ # the tools the work needs, not for every permission there is.
28
+ allowed = "Bash Edit Write Read" + (" Skill" if self._allow_skills else "")
29
+ cmd = [
30
+ self._binary,
31
+ "-p",
32
+ prompt,
33
+ "--output-format",
34
+ "stream-json",
35
+ "--verbose",
36
+ "--allowed-tools",
37
+ allowed,
38
+ "--add-dir",
39
+ str(workdir),
40
+ ]
41
+ if self._model:
42
+ cmd += ["--model", self._model]
43
+ if self._max_budget_usd:
44
+ cmd += ["--max-budget-usd", str(self._max_budget_usd)]
45
+ cmd.extend(self._extra_args)
46
+ return cmd
47
+
48
+ def _postprocess_output(self, stdout: str) -> tuple[str, dict | None]:
49
+ return render_claude_stream(stdout)
@@ -0,0 +1,37 @@
1
+ """OpenAI Codex CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.providers.base import CliAgentBase
8
+ from evalrx.agent_runtime.skills.installer import CodexSkillInstaller, SkillInstaller
9
+
10
+
11
+ class CodexAgent(CliAgentBase):
12
+ """OpenAI Codex CLI backend (``codex exec``)."""
13
+
14
+ _provider_name = "codex"
15
+
16
+ def _make_skill_installer(self, skills: list[str]) -> SkillInstaller:
17
+ return CodexSkillInstaller(skills)
18
+
19
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
20
+ cmd = [
21
+ self._binary,
22
+ "exec",
23
+ prompt,
24
+ # Generated-code workspaces are intentionally isolated artifact
25
+ # directories, not Git checkouts. Codex otherwise exits before
26
+ # doing any work with "Not inside a trusted directory".
27
+ "--skip-git-repo-check",
28
+ "--sandbox",
29
+ "workspace-write",
30
+ "--json",
31
+ "-C",
32
+ str(workdir),
33
+ ]
34
+ if self._model:
35
+ cmd += ["-m", self._model]
36
+ cmd.extend(self._extra_args)
37
+ return cmd
@@ -0,0 +1,26 @@
1
+ """Gemini CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.providers.base import CliAgentBase
8
+
9
+
10
+ class GeminiCliAgent(CliAgentBase):
11
+ """Gemini CLI backend (``gemini -p``)."""
12
+
13
+ _provider_name = "gemini_cli"
14
+
15
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
16
+ cmd = [
17
+ self._binary,
18
+ "-p",
19
+ prompt,
20
+ "--cwd",
21
+ str(workdir),
22
+ ]
23
+ if self._model:
24
+ cmd += ["--model", self._model]
25
+ cmd.extend(self._extra_args)
26
+ return cmd
@@ -0,0 +1,27 @@
1
+ """Kimi CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.providers.base import CliAgentBase
8
+
9
+
10
+ class KimiCliAgent(CliAgentBase):
11
+ """Kimi CLI backend (``kimi chat``)."""
12
+
13
+ _provider_name = "kimi_cli"
14
+
15
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
16
+ cmd = [
17
+ self._binary,
18
+ "chat",
19
+ "--message",
20
+ prompt,
21
+ "--workdir",
22
+ str(workdir),
23
+ ]
24
+ if self._model:
25
+ cmd += ["--model", self._model]
26
+ cmd.extend(self._extra_args)
27
+ return cmd
@@ -0,0 +1,27 @@
1
+ """OpenCode CLI coding-provider adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from evalrx.agent_runtime.providers.base import CliAgentBase
8
+
9
+
10
+ class OpenCodeAgent(CliAgentBase):
11
+ """OpenCode CLI backend (``opencode run``)."""
12
+
13
+ _provider_name = "opencode"
14
+
15
+ def _build_cmd(self, prompt: str, workdir: Path) -> list[str]:
16
+ cmd = [
17
+ self._binary,
18
+ "run",
19
+ "--message",
20
+ prompt,
21
+ "--cwd",
22
+ str(workdir),
23
+ ]
24
+ if self._model:
25
+ cmd += ["--model", self._model]
26
+ cmd.extend(self._extra_args)
27
+ return cmd
@@ -0,0 +1,58 @@
1
+ """Provider registry and factory for CLI coding agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+
7
+ from evalrx.agent_runtime.cli_types import BINARY_DEFAULTS, CliAgentConfig
8
+ from evalrx.agent_runtime.providers.antigravity import AntigravityAgent
9
+ from evalrx.agent_runtime.providers.base import CliAgentBase
10
+ from evalrx.agent_runtime.providers.claude_code import ClaudeCodeAgent
11
+ from evalrx.agent_runtime.providers.codex import CodexAgent
12
+ from evalrx.agent_runtime.providers.gemini_cli import GeminiCliAgent
13
+ from evalrx.agent_runtime.providers.kimi_cli import KimiCliAgent
14
+ from evalrx.agent_runtime.providers.opencode import OpenCodeAgent
15
+
16
+ PROVIDER_CLASSES: dict[str, type[CliAgentBase]] = {
17
+ "claude_code": ClaudeCodeAgent,
18
+ "codex": CodexAgent,
19
+ "opencode": OpenCodeAgent,
20
+ "gemini_cli": GeminiCliAgent,
21
+ "kimi_cli": KimiCliAgent,
22
+ "antigravity": AntigravityAgent,
23
+ }
24
+
25
+
26
+ def create_cli_agent(config: CliAgentConfig) -> CliAgentBase:
27
+ """Instantiate the appropriate CLI coding provider for *config*."""
28
+ provider = config.provider
29
+
30
+ if provider == "llm":
31
+ raise ValueError(
32
+ "'llm' is not a CLI provider. Use ExperimentWriter directly "
33
+ "(leave cli_agent=None or CliAgentConfig(provider='llm'))."
34
+ )
35
+
36
+ cls = PROVIDER_CLASSES.get(provider)
37
+ if cls is None:
38
+ raise ValueError(
39
+ f"Unknown CLI provider: {provider!r}. Valid: {sorted(PROVIDER_CLASSES)}"
40
+ )
41
+
42
+ binary = config.binary_path or shutil.which(BINARY_DEFAULTS[provider]) or ""
43
+ if not binary:
44
+ raise RuntimeError(
45
+ f"CLI agent binary for {provider!r} not found in PATH. "
46
+ f"Install '{BINARY_DEFAULTS[provider]}' or pass "
47
+ f"CliAgentConfig(binary_path='/path/to/{BINARY_DEFAULTS[provider]}')."
48
+ )
49
+
50
+ return cls(
51
+ binary_path=binary,
52
+ model=config.model,
53
+ max_budget_usd=config.max_budget_usd,
54
+ timeout_sec=config.timeout_sec,
55
+ extra_args=list(config.extra_args),
56
+ skills=list(config.skills),
57
+ allow_skills=config.allow_skills,
58
+ )