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
evalrx/cli.py ADDED
@@ -0,0 +1,411 @@
1
+ """Top-level EvalRX command-line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from evalrx.analysis.dashboard import launch_dashboard
9
+ from evalrx.analysis.explore_run import run_explore
10
+ from evalrx.analysis.run_codebase import run_codebase_cli
11
+
12
+
13
+ def serve_report(
14
+ run_dir: str | Path | None,
15
+ *,
16
+ port: int,
17
+ no_audio: bool = False,
18
+ open_browser: bool = True,
19
+ block: bool = True,
20
+ runs_root: str | Path | None = None,
21
+ ) -> int:
22
+ """CLI seam for the dynamic UI (signature kept for compatibility/tests)."""
23
+ del no_audio, block
24
+ from evalrx.reporting.server import serve_dynamic_report
25
+
26
+ return serve_dynamic_report(run_dir, port=port, open_browser=open_browser, runs_root=runs_root)
27
+
28
+
29
+ def _langfuse_cache(trace_id: str) -> Path:
30
+ """Stable local cache for a Langfuse-backed static report, not run storage."""
31
+ safe_id = "".join(char for char in trace_id if char.isalnum() or char in "-_")
32
+ if not safe_id:
33
+ raise ValueError("--trace-id must contain at least one letter or number")
34
+ return Path(".evalrx-cache") / safe_id
35
+
36
+
37
+ def _resolve_report_source(source: str, trace_id: str | None, run_dir: str | None) -> str | None:
38
+ if source == "auto":
39
+ if trace_id:
40
+ try:
41
+ from evalrx.reporting.langfuse_source import LangfuseRunSource
42
+
43
+ return str(LangfuseRunSource().materialize(trace_id, _langfuse_cache(trace_id)))
44
+ except (ImportError, LookupError, RuntimeError):
45
+ pass
46
+ return run_dir
47
+ if source == "local":
48
+ return run_dir
49
+ if not trace_id:
50
+ raise ValueError("--trace-id is required with --source langfuse")
51
+ from evalrx.reporting.langfuse_source import LangfuseRunSource
52
+
53
+ return str(LangfuseRunSource().materialize(trace_id, _langfuse_cache(trace_id)))
54
+
55
+
56
+ def main(argv: list[str] | None = None) -> int:
57
+ parser = argparse.ArgumentParser(
58
+ prog="evalrx",
59
+ description="EvalRX command-line interface.",
60
+ )
61
+ parser.add_argument(
62
+ "-v", "--verbose",
63
+ action="store_true",
64
+ help="Print evalrx' internal stage-by-stage narration "
65
+ "(evalrx.enable_console_logging()) to stdout. Applies to any subcommand.",
66
+ )
67
+ sub = parser.add_subparsers(dest="command")
68
+
69
+ explore = sub.add_parser(
70
+ "explore",
71
+ help="Run a single-shot exploratory analysis over a results directory.",
72
+ description="Run one Lambda-style exploratory analysis (no interactive REPL).",
73
+ )
74
+ explore.add_argument("path", nargs="?", help="File or directory of JSON/JSONL results.")
75
+ explore.add_argument(
76
+ "-q",
77
+ "--question",
78
+ default="Explore this dataset and surface the patterns that matter.",
79
+ help="Natural-language analysis question for the local coding agent.",
80
+ )
81
+ explore.add_argument(
82
+ "--outcome-col",
83
+ default=None,
84
+ help="Name of the target/outcome column, if any (e.g. 'label'). Omit to "
85
+ "auto-detect by name heuristics, or fall back to unsupervised EDA "
86
+ "when the data has no recognizable outcome.",
87
+ )
88
+ explore.add_argument(
89
+ "--out",
90
+ default="evalrx_explore_output",
91
+ help="Output directory for report/code/figures/tables.",
92
+ )
93
+ explore.add_argument(
94
+ "--backend",
95
+ "--coder-provider",
96
+ dest="coder_provider",
97
+ default="antigravity",
98
+ choices=["antigravity", "codex", "claude_code", "opencode", "gemini_cli", "kimi_cli"],
99
+ help="Local CLI coding-agent backend.",
100
+ )
101
+ explore.add_argument("--model", "--coder-model", dest="coder_model", default="")
102
+ explore.add_argument("--coder-binary", default="")
103
+ explore.add_argument("--max-rows", type=int, default=2000)
104
+ explore.add_argument("--max-files", type=int, default=200)
105
+ explore.add_argument("--include-tool-calls", action="store_true")
106
+ explore.add_argument("--timeout-sec", type=int, default=120)
107
+ explore.add_argument("--max-attempts", type=int, default=2)
108
+ explore.add_argument(
109
+ "--dashboard",
110
+ action="store_true",
111
+ help="Deprecated alias for --serve-report.",
112
+ )
113
+ explore.add_argument("--serve-report", action="store_true",
114
+ help="Serve the generated static HTML report when done.")
115
+ explore.add_argument("--port", type=int, default=None, help="Optional report-server port.")
116
+ explore.add_argument(
117
+ "--skill", action="append", default=[], metavar="DIR",
118
+ help="Agent-Skill directory (with SKILL.md) to style agent-authored "
119
+ "figures (e.g. nature-figure). Repeatable. claude/agy/codex backends.",
120
+ )
121
+ explore.add_argument(
122
+ "--allow-skills", action="store_true",
123
+ help="Enable the Skill tool so ~/.claude/skills are usable too "
124
+ "(implied by --skill).",
125
+ )
126
+ explore.add_argument(
127
+ "--no-skills", dest="use_bundled_skills", action="store_false", default=True,
128
+ help="Do not apply the package's bundled skills (e.g. nature-figure).",
129
+ )
130
+ explore.add_argument(
131
+ "--no-hypotheses", dest="propose_hypotheses", action="store_false", default=True,
132
+ help="Skip M3 (falsifiable hypotheses proposed from the M2 takeaways). "
133
+ "Runs by default after a successful explore.",
134
+ )
135
+ explore.add_argument(
136
+ "--holdout-frac", type=float, default=0.0,
137
+ help="Fraction of rows to hold out BEFORE exploration (outcome-"
138
+ "stratified, deterministic). 0 disables (default).",
139
+ )
140
+ explore.add_argument(
141
+ "--holdout-confirm", action="store_true",
142
+ help="After exploring, re-test the frozen recipes + hypotheses on the "
143
+ "held-out rows (writes confirm_report.json — the dashboard's "
144
+ "Held-out Verdicts tab). Requires --holdout-frac > 0.",
145
+ )
146
+ explore.add_argument("--holdout-seed", type=int, default=0,
147
+ help="Seed for the held-out split (default 0).")
148
+ explore.add_argument(
149
+ "--judge-model", default="claude-opus-4-8",
150
+ help="LLM judge grading each hypothesis against the held-out table "
151
+ "(only used with --holdout-confirm).",
152
+ )
153
+ explore.add_argument("--progress-path", default="",
154
+ help="Append durable workbench progress events to this JSONL path.")
155
+ explore.add_argument(
156
+ "--no-narrate", dest="narrate", action="store_false", default=True,
157
+ help="Do not print live per-stage narration (M2/M3/... lines) to the terminal.",
158
+ )
159
+ explore.add_argument("--thread-id", default="", help=argparse.SUPPRESS)
160
+ explore.add_argument("--turn-id", default="", help=argparse.SUPPRESS)
161
+
162
+ run_codebase = sub.add_parser(
163
+ "run-codebase",
164
+ help="Run a user's evaluation codebase, then explore the results it produces.",
165
+ description="A CLI coding agent runs the codebase at PATH inside an isolated copy, "
166
+ "harvests its per-case records (a records.json/.jsonl output contract), "
167
+ "and runs `evalrx explore` (M2+M3) over them.",
168
+ )
169
+ run_codebase.add_argument("path", help="Directory containing the codebase to run.")
170
+ run_codebase.add_argument(
171
+ "-q", "--question",
172
+ default="Explore this dataset and surface the patterns that matter.",
173
+ help="Natural-language analysis question, also given to the run agent as task context.",
174
+ )
175
+ run_codebase.add_argument(
176
+ "--outcome-col", default=None,
177
+ help="Name of the target/outcome column, if any (e.g. 'label'). Omit to auto-detect.",
178
+ )
179
+ run_codebase.add_argument("--out", default="evalrx_run_codebase_output",
180
+ help="Output directory for workspace/records/report/figures/tables.")
181
+ run_codebase.add_argument(
182
+ "--backend", "--coder-provider", dest="coder_provider", default="claude_code",
183
+ choices=["antigravity", "codex", "claude_code", "opencode", "gemini_cli", "kimi_cli"],
184
+ help="Local CLI coding-agent backend used both to run the codebase and to explore it.",
185
+ )
186
+ run_codebase.add_argument("--model", "--coder-model", dest="coder_model", default="")
187
+ run_codebase.add_argument("--coder-binary", default="")
188
+ run_codebase.add_argument("--records-name", default=None,
189
+ help="Output-contract filename the run agent must write "
190
+ "(default: records.json).")
191
+ run_codebase.add_argument("--timeout-sec", type=int, default=1200)
192
+ run_codebase.add_argument("--max-attempts", type=int, default=2)
193
+ run_codebase.add_argument(
194
+ "--no-explore", dest="analyze", action="store_false", default=True,
195
+ help="Only run the codebase and harvest records; skip the explore (M2+M3) step.",
196
+ )
197
+ run_codebase.add_argument(
198
+ "--dashboard", action="store_true",
199
+ help="Deprecated alias for --serve-report.",
200
+ )
201
+ run_codebase.add_argument("--serve-report", action="store_true",
202
+ help="Serve the generated static HTML report when done.")
203
+ run_codebase.add_argument("--port", type=int, default=None, help="Optional report-server port.")
204
+ run_codebase.add_argument(
205
+ "--no-narrate", dest="narrate", action="store_false", default=True,
206
+ help="Do not print live per-stage narration (run/M2/M3/... lines) to the terminal.",
207
+ )
208
+
209
+ dashboard = sub.add_parser(
210
+ "dashboard",
211
+ help="Deprecated alias for the dependency-free 'serve' command.",
212
+ description="Deprecated alias for the dependency-free static report server.",
213
+ )
214
+ dashboard.add_argument("run_dir", help="An explore output dir or a loop-run dir.")
215
+ dashboard.add_argument("--port", type=int, default=None, help="Optional report-server port.")
216
+
217
+ serve = sub.add_parser(
218
+ "serve",
219
+ help="Publish (if needed) and serve the dynamic completed-run UI.",
220
+ description="Generate (if needed) ReportData and serve the agent-composed React UI backed by Langfuse or a local run cache.",
221
+ )
222
+ serve.add_argument("run_dir", nargs="?", default=None, help="Run directory. Omit to start empty and drop a zipped run on the page.")
223
+ serve.add_argument("--port", type=int, default=8501, help="Loopback port (default: 8501).")
224
+ serve.add_argument("--no-audio", action="store_true", help=argparse.SUPPRESS)
225
+ serve.add_argument("--no-browser", action="store_true", help="Do not open a browser automatically.")
226
+ serve.add_argument("--source", choices=["auto", "local", "langfuse"], default="auto", help="Run data source (default: Langfuse when --trace-id is set, otherwise local).")
227
+ serve.add_argument("--trace-id", default=None, help="Langfuse trace id (required for --source langfuse).")
228
+ serve.add_argument("--runs-root", default=None, help="Where the runs panel looks for other experiments (default: run_dir's parent, or the current directory).")
229
+
230
+ report_cmd = sub.add_parser(
231
+ "report",
232
+ help="Explicitly export a portable self-contained HTML snapshot.",
233
+ description="Uses the same React/json-render layout as `serve`; the dynamic UI remains the primary experience.",
234
+ )
235
+ report_cmd.add_argument("run_dir", nargs="?", default="outputs", help="Run directory holding run.json/M1..M5, or logs/")
236
+ report_cmd.add_argument("--example-dir", default=None, help="Root holding data/ manifest.")
237
+ report_cmd.add_argument("--out", "-o", default=None, help="Output HTML path (default: <run_dir>/report.html).")
238
+ report_cmd.add_argument("--no-audio", action="store_true", help="Skip audio transcoding.")
239
+ report_cmd.add_argument("--embed-media", choices=["representative", "all", "none"], default="representative", help="Media to inline in the portable export (default: representative).")
240
+ report_cmd.add_argument("--audio-bitrate", default="48k", help="Transcode inlined lossless audio to mono MP3 at this bitrate via ffmpeg (default: 48k; 'none' = inline the original bytes).")
241
+ report_cmd.add_argument("--source", choices=["local", "langfuse"], default="local", help="Run data source.")
242
+ report_cmd.add_argument("--trace-id", default=None, help="Langfuse trace id (required for --source langfuse).")
243
+
244
+ publish_cmd = sub.add_parser(
245
+ "publish-report",
246
+ help="Compile and cache ReportData plus a validated json-render layout.",
247
+ )
248
+ publish_cmd.add_argument("run_dir", nargs="?", default="outputs", help="Completed run directory.")
249
+ publish_cmd.add_argument("--example-dir", default=None, help="Optional benchmark/example root for legacy runs.")
250
+ publish_cmd.add_argument("--source", choices=["auto", "local", "langfuse"], default="auto")
251
+ publish_cmd.add_argument("--trace-id", default=None)
252
+
253
+ langfuse_cmd = sub.add_parser(
254
+ "export-langfuse",
255
+ help="Export a diagnostic run to Langfuse trace JSON format or sync live.",
256
+ description="Map an EvalRX run (M1-M4, fixes, scores) to Langfuse Traces, Spans, and Scores.",
257
+ )
258
+ langfuse_cmd.add_argument("run_dir", nargs="?", default="outputs", help="Run directory.")
259
+ langfuse_cmd.add_argument("--out", "-o", default=None, help="Output JSON path.")
260
+ langfuse_cmd.add_argument("--sync", action="store_true", help="Sync live to Langfuse server.")
261
+
262
+ backfill_langfuse = sub.add_parser(
263
+ "backfill-langfuse",
264
+ help="Queue an existing run (run.json/M1..M5) for reliable Langfuse ingestion.",
265
+ )
266
+ backfill_langfuse.add_argument("run_dir", help="Run directory holding run.json/M1..M5, or logs/.")
267
+ backfill_langfuse.add_argument("--dry-run", action="store_true", help="Inspect the run without writing an outbox.")
268
+
269
+ args = parser.parse_args(argv)
270
+ if args.verbose:
271
+ from evalrx.logging_utils import enable_console_logging
272
+
273
+ enable_console_logging()
274
+ if args.command == "explore":
275
+ if not args.path:
276
+ parser.error("evalrx explore requires a results path")
277
+ from evalrx.analysis.narration import MultiSink, TerminalNarrator
278
+
279
+ event_sink = None
280
+ if args.progress_path:
281
+ from evalrx.analysis.workbench import EventSink
282
+
283
+ event_sink = EventSink(
284
+ args.progress_path,
285
+ thread_id=args.thread_id or "standalone",
286
+ turn_id=args.turn_id or "explore",
287
+ )
288
+ event_sink.emit("job", "started", "Analysis worker started")
289
+ # "job" bookkeeping above/below stays workbench-only (event_sink), not
290
+ # narrated -- it wraps the whole process rather than one M-stage.
291
+ progress_sink = MultiSink(TerminalNarrator() if args.narrate else None, event_sink) or None
292
+ code = run_explore(
293
+ args.path,
294
+ question=args.question,
295
+ out=args.out,
296
+ coder_provider=args.coder_provider,
297
+ coder_model=args.coder_model,
298
+ coder_binary=args.coder_binary,
299
+ max_rows=args.max_rows,
300
+ max_files=args.max_files,
301
+ include_tool_calls=args.include_tool_calls,
302
+ timeout_sec=args.timeout_sec,
303
+ max_attempts=args.max_attempts,
304
+ dashboard=args.dashboard or args.serve_report,
305
+ dashboard_port=args.port,
306
+ skills=args.skill,
307
+ allow_skills=args.allow_skills,
308
+ use_bundled_skills=args.use_bundled_skills,
309
+ outcome_col=args.outcome_col,
310
+ propose_hypotheses=args.propose_hypotheses,
311
+ holdout_frac=args.holdout_frac,
312
+ holdout_seed=args.holdout_seed,
313
+ holdout_confirm=args.holdout_confirm,
314
+ judge_model=args.judge_model,
315
+ progress_sink=progress_sink,
316
+ )
317
+ if event_sink is not None:
318
+ event_sink.emit(
319
+ "job", "completed" if code == 0 else "failed",
320
+ "Analysis worker completed" if code == 0 else "Analysis worker failed",
321
+ )
322
+ return code
323
+ if args.command == "run-codebase":
324
+ from evalrx.analysis.explorer import RECORDS_FILENAME
325
+ from evalrx.analysis.narration import TerminalNarrator
326
+
327
+ return run_codebase_cli(
328
+ args.path,
329
+ out=args.out,
330
+ coder_provider=args.coder_provider,
331
+ coder_model=args.coder_model,
332
+ coder_binary=args.coder_binary,
333
+ outcome_col=args.outcome_col,
334
+ records_name=args.records_name or RECORDS_FILENAME,
335
+ timeout_sec=args.timeout_sec,
336
+ max_attempts=args.max_attempts,
337
+ question=args.question,
338
+ analyze=args.analyze,
339
+ dashboard=args.dashboard or args.serve_report,
340
+ dashboard_port=args.port,
341
+ progress_sink=TerminalNarrator() if args.narrate else None,
342
+ )
343
+ if args.command == "dashboard":
344
+ return launch_dashboard(args.run_dir, port=args.port)
345
+ if args.command == "serve":
346
+ try:
347
+ source_dir = _resolve_report_source(args.source, args.trace_id, args.run_dir)
348
+ except (ImportError, LookupError, RuntimeError, ValueError) as exc:
349
+ parser.error(str(exc))
350
+ try:
351
+ return serve_report(
352
+ source_dir, port=args.port, no_audio=args.no_audio,
353
+ open_browser=not args.no_browser, runs_root=args.runs_root,
354
+ )
355
+ except ImportError as exc:
356
+ parser.error(str(exc))
357
+ if args.command == "publish-report":
358
+ from evalrx.reporting.dynamic import publish_report
359
+
360
+ try:
361
+ source_dir = _resolve_report_source(args.source, args.trace_id, args.run_dir)
362
+ published = publish_report(source_dir, example_dir=args.example_dir)
363
+ except (ImportError, LookupError, RuntimeError, ValueError) as exc:
364
+ parser.error(str(exc))
365
+ print(f"Published report data: {published.data_path}")
366
+ print(f"Published report layout: {published.spec_path} ({published.generated_by})")
367
+ return 0
368
+ if args.command == "report":
369
+ from evalrx.reporting.static_export import export_static_report
370
+
371
+ try:
372
+ source_dir = _resolve_report_source(args.source, args.trace_id, args.run_dir)
373
+ except (ImportError, LookupError, RuntimeError, ValueError) as exc:
374
+ parser.error(str(exc))
375
+
376
+ export_static_report(
377
+ run_dir=source_dir,
378
+ example_dir=args.example_dir,
379
+ out_path=args.out,
380
+ embed_media="none" if args.no_audio else args.embed_media,
381
+ audio_bitrate=None if str(getattr(args, "audio_bitrate", "48k")).lower() == "none" else args.audio_bitrate,
382
+ )
383
+ return 0
384
+ if args.command == "export-langfuse":
385
+ from evalrx.reporting.langfuse_exporter import (
386
+ export_to_langfuse_bundle,
387
+ sync_to_langfuse_live,
388
+ )
389
+
390
+ if args.sync:
391
+ return 0 if sync_to_langfuse_live(args.run_dir) else 1
392
+ out_p = args.out or (Path(args.run_dir) / "langfuse_trace.json")
393
+ export_to_langfuse_bundle(args.run_dir, out_p)
394
+ return 0
395
+ if args.command == "backfill-langfuse":
396
+ from evalrx.observability import backfill_run_to_langfuse
397
+
398
+ summary = backfill_run_to_langfuse(args.run_dir, dry_run=args.dry_run)
399
+ print(
400
+ "Langfuse backfill: "
401
+ f"trace={summary['trace_id']} events={summary['events']} "
402
+ f"published={summary['published']} pending={summary['pending']}"
403
+ )
404
+ return 0
405
+
406
+ parser.print_help()
407
+ return 0
408
+
409
+
410
+ if __name__ == "__main__": # pragma: no cover
411
+ raise SystemExit(main())
evalrx/config.py ADDED
@@ -0,0 +1,77 @@
1
+ """Config loading and dataclasses for evalrx."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import yaml
10
+
11
+
12
+ @dataclass
13
+ class ModelConfig:
14
+ """Declarative model selection — resolved to a spec + backend at load time.
15
+
16
+ ``name`` is a :mod:`evalrx.specs` key (e.g. ``"qwen2.5-7b-instruct"``) or a
17
+ legacy alias (e.g. ``"qwen"``); resolution happens in
18
+ :func:`evalrx.models.load_model` via ``compose(spec, backend)``. No
19
+ checkpoint is baked here — identity lives in the spec.
20
+
21
+ Attributes:
22
+ name: Spec key or legacy alias.
23
+ checkpoint: Optional override of the spec's ``hf_repo``.
24
+ device: Runtime device (``"auto"``, ``"cuda"``, ``"cpu"``, ``"cuda:N"``).
25
+ dtype: Runtime dtype (``"bfloat16"``, ``"float16"``, ``"float32"``).
26
+ backend: ``"hf_local"`` (internals) / ``"api"`` / ``"vllm_offline"``.
27
+ want: Capability names the backend must provide (e.g. ``["attention"]``).
28
+ """
29
+
30
+ name: str
31
+ checkpoint: str | None = None
32
+ device: str = "auto"
33
+ dtype: str = "bfloat16"
34
+ backend: str = "hf_local"
35
+ want: list[str] = field(default_factory=list)
36
+
37
+
38
+ @dataclass
39
+ class AnalysisConfig:
40
+ model: ModelConfig
41
+ analysis: str
42
+ analysis_kwargs: dict[str, Any] = field(default_factory=dict)
43
+
44
+
45
+ def load_config(path: str | Path) -> AnalysisConfig:
46
+ """Load an AnalysisConfig from a YAML file.
47
+
48
+ Supports both short form::
49
+
50
+ model: qwen2.5-7b-instruct # spec key (or a legacy alias like "qwen")
51
+ analysis: attention
52
+
53
+ and full form::
54
+
55
+ model:
56
+ name: qwen2.5-7b-instruct
57
+ backend: hf_local # hf_local | api | vllm_offline
58
+ device: auto
59
+ dtype: bfloat16
60
+ want: [attention] # capabilities the backend must provide
61
+ analysis: attention # registered analyzer name
62
+ analysis_kwargs:
63
+ layer: -1
64
+ head: mean
65
+ top_k: 10
66
+ """
67
+ with open(path) as f:
68
+ raw = yaml.safe_load(f)
69
+
70
+ model_raw = raw["model"]
71
+ model_cfg = ModelConfig(name=model_raw) if isinstance(model_raw, str) else ModelConfig(**model_raw)
72
+
73
+ return AnalysisConfig(
74
+ model=model_cfg,
75
+ analysis=raw["analysis"],
76
+ analysis_kwargs=raw.get("analysis_kwargs", {}),
77
+ )
@@ -0,0 +1,179 @@
1
+ """Wire contract for the M1-M4 diagnosis pipeline.
2
+
3
+ The single machine-readable definition of what crosses each stage boundary.
4
+ Python is the source of truth; JSON Schema and TypeScript are generated from it
5
+ (``python -m evalrx.contract.export``) so the three can never drift.
6
+
7
+ from evalrx.contract import STAGE_IO, ProbeOutput
8
+
9
+ ProbeOutput.model_validate_json(open("probe.json").read()) # backend gate
10
+ STAGE_IO["m1"].output.model_json_schema() # frontend types
11
+
12
+ Scope: this describes the SERIALIZED shape, not the in-memory dataclasses. They
13
+ differ (``AnalysisReport.to_dict()`` drops ``raw_results``), and only the
14
+ serialized shape is what another process actually receives.
15
+
16
+ Reader-facing text
17
+ ------------------
18
+ Every field a frontend puts on screen is written for a reader with no
19
+ background in this field -- assume a bright high-school student who has never
20
+ heard of an e-value, a tier, or a paired flip. That obligation sits here, on
21
+ the producer, and not on the frontend, for one reason: a consumer handed
22
+ ``mcnemar_evalue`` or ``audio_evidence_then_answer`` can only guess, and a
23
+ guess dressed as a label ("Mcnemar Evalue") reads as though the run explained
24
+ itself when it did not.
25
+
26
+ So the contract separates the two jobs wherever they collide:
27
+
28
+ * an IDENTIFIER (``FixAttemptWire.name``, a signal's dotted path) joins records
29
+ and must never change to suit a reader;
30
+ * a LABEL (``StatsToolResultWire.measured``, ``FixAttemptWire.ref``) is what a
31
+ reader points at;
32
+ * a SENTENCE (``StatsToolResultWire.means``, ``FixAttemptWire.headline``,
33
+ ``MethodologyWire.summary``) says what it means in plain language.
34
+
35
+ A producer with nothing readable to offer leaves the label and sentence empty.
36
+ An honest blank is a smaller error than a title-cased slug, because the blank
37
+ is visible and the slug is not.
38
+
39
+ Requires the ``contract`` extra: ``pip install evalrx[contract]``.
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ from typing import NamedTuple
45
+
46
+ from evalrx.contract.common import (
47
+ SCHEMA_VERSION,
48
+ ArtifactRef,
49
+ CaseBatchRef,
50
+ CaseBatchWire,
51
+ EvidenceGrade,
52
+ ExternalRef,
53
+ FailureCaseWire,
54
+ HypothesisStatus,
55
+ InputsWire,
56
+ JoinReport,
57
+ Label,
58
+ MediaRef,
59
+ Modality,
60
+ ProvenanceWire,
61
+ Source,
62
+ StageEnvelope,
63
+ StageState,
64
+ StageStatus,
65
+ StepWire,
66
+ TrajectoryWire,
67
+ )
68
+ from evalrx.contract.m1 import (
69
+ AnalyzerSelection,
70
+ FindingsWire,
71
+ ModelRef,
72
+ PerCaseRow,
73
+ ProbeInput,
74
+ ProbeOutput,
75
+ ProtocolWire,
76
+ ResultWire,
77
+ )
78
+ from evalrx.contract.m2 import (
79
+ AnalysisFindingWire,
80
+ AnalysisInput,
81
+ CorrectedRejections,
82
+ ExploreContextWire,
83
+ StatsReportWire,
84
+ StatsToolResultWire,
85
+ )
86
+ from evalrx.contract.m3 import (
87
+ DiagnosisInput,
88
+ DiagnosisOutput,
89
+ HypothesisWire,
90
+ )
91
+ from evalrx.contract.m4 import (
92
+ HypothesisTestInput,
93
+ HypothesisTestOutput,
94
+ HypothesisTestResultWire,
95
+ TestEvidence,
96
+ )
97
+ from evalrx.contract.m5 import (
98
+ FixAttemptWire,
99
+ FixInput,
100
+ FixOutput,
101
+ InterventionOutput,
102
+ SurgeryInput,
103
+ )
104
+ from evalrx.contract.methodology import MethodologyWire
105
+ from evalrx.contract.pre_m1 import ProbeSearchInput, ProbeSearchOutput
106
+
107
+
108
+ class StageContract(NamedTuple):
109
+ """One stage's input/output pair plus what it is for."""
110
+
111
+ stage: str
112
+ purpose: str
113
+ input: type
114
+ output: type
115
+ optional: bool = False
116
+
117
+
118
+ #: The pipeline, as data. Docs, schema export and stage-map UIs all read this
119
+ #: instead of restating the wiring in prose that then drifts.
120
+ #:
121
+ #: Execution order note: the numbering is registration order, not run order.
122
+ #: ``VLDiagnoseLoop`` runs M1->M2->M3->M4 as its cycle and calls M5 once after,
123
+ #: because M5 is expensive and should only run on a hypothesis M4 verified.
124
+ STAGE_IO: dict[str, StageContract] = {
125
+ "pre_m1": StageContract(
126
+ "pre_m1", "Synthesize new failing cases (output is DATA, not a verdict).",
127
+ ProbeSearchInput, ProbeSearchOutput, optional=True,
128
+ ),
129
+ "m1": StageContract(
130
+ "m1", "Measure: run analyzers, emit per-case numbers. No pass/fail judgement.",
131
+ ProbeInput, ProbeOutput,
132
+ ),
133
+ "m2": StageContract(
134
+ "m2", "Screen: join numbers to labels, test which signals track failure, correct for multiplicity.",
135
+ AnalysisInput, StatsReportWire,
136
+ ),
137
+ "m3": StageContract(
138
+ "m3", "Explain: propose falsifiable mechanism hypotheses, each with a routable test_design.",
139
+ DiagnosisInput, DiagnosisOutput,
140
+ ),
141
+ "m4": StageContract(
142
+ "m4", "Adjudicate: statistical gate AND protocol-consistency gate. Both, or not SUPPORTED.",
143
+ HypothesisTestInput, HypothesisTestOutput,
144
+ ),
145
+ "m5_surgery": StageContract(
146
+ "m5_surgery", "Intervene: change one variable, re-run, read as causal evidence. Does not repair.",
147
+ SurgeryInput, InterventionOutput,
148
+ ),
149
+ "m5_fix": StageContract(
150
+ "m5_fix", "Repair: propose candidates, validate paired against the unmodified baseline.",
151
+ FixInput, FixOutput, optional=True,
152
+ ),
153
+ }
154
+
155
+ __all__ = [
156
+ "SCHEMA_VERSION", "STAGE_IO", "StageContract",
157
+ # common
158
+ "ArtifactRef", "CaseBatchRef", "CaseBatchWire", "EvidenceGrade", "ExternalRef",
159
+ "FailureCaseWire", "HypothesisStatus", "InputsWire", "JoinReport", "Label",
160
+ "MediaRef", "Modality", "ProvenanceWire", "Source", "StageEnvelope", "StageState",
161
+ "StageStatus", "StepWire", "TrajectoryWire",
162
+ # pre-M1
163
+ "ProbeSearchInput", "ProbeSearchOutput",
164
+ # M1
165
+ "ProbeInput", "ProbeOutput", "ResultWire", "FindingsWire", "PerCaseRow",
166
+ "ModelRef", "ProtocolWire", "AnalyzerSelection",
167
+ # M2
168
+ "AnalysisInput", "StatsReportWire", "StatsToolResultWire", "AnalysisFindingWire",
169
+ "CorrectedRejections", "ExploreContextWire",
170
+ # M3
171
+ "DiagnosisInput", "DiagnosisOutput", "HypothesisWire",
172
+ # M5
173
+ "SurgeryInput", "InterventionOutput",
174
+ "FixInput", "FixOutput", "FixAttemptWire",
175
+ # methodology
176
+ "MethodologyWire",
177
+ # M4
178
+ "HypothesisTestInput", "HypothesisTestOutput", "HypothesisTestResultWire", "TestEvidence",
179
+ ]