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,497 @@
1
+ """Published, machine-consumable schema for a run's logged events.
2
+
3
+ A run's events (``run_start``, ``probe``, ``diagnosis``, ...) are the
4
+ contract between a diagnosis run and everything downstream (dashboards,
5
+ parsers, the agent's own memory) — RunLoggerV2 stores them bucketed into
6
+ ``run.json`` + per-stage ``M<n>/log.json`` rather than a flat JSONL file, but
7
+ each individual event has this same shape wherever it lives. Until this
8
+ module existed that contract lived only in docstrings + a table in
9
+ ``docs/extending.md`` and an opaque ``schema_version`` int — a consumer had
10
+ nothing to *validate* against.
11
+
12
+ This module is that contract, as code:
13
+
14
+ * :func:`build_schema` returns a JSON Schema (Draft 2020-12) describing every
15
+ event type, built purely from the stdlib so importing it adds **no runtime
16
+ dependency** (the package's light-install promise is preserved — see
17
+ pyproject ``dependencies``).
18
+ * The rendered schema is committed next to this file as
19
+ ``run_log.schema.json`` (shipped as package data) so non-Python consumers can
20
+ validate too; :func:`load_schema` reads it and a drift test keeps the two in
21
+ lock-step.
22
+ * :func:`validate_event` / :func:`iter_log_errors` validate real log lines.
23
+ They need the ``jsonschema`` package (a *dev*/optional dep, not core) and
24
+ raise a clear, actionable error if it is missing rather than failing obscurely.
25
+
26
+ Design note — the schema is intentionally **permissive**: it pins the common
27
+ envelope, the event discriminator, required fields and core types, but does
28
+ *not* forbid extra properties. That matches ``RUN_LOG_SCHEMA_VERSION``'s rule
29
+ that *additive* fields don't bump the version, so a newer producer emitting an
30
+ extra field still validates against an older schema. What it catches is the
31
+ breakage that actually matters: a missing required field, a wrong type, a
32
+ malformed timestamp, or an unknown event name.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ from pathlib import Path
39
+ from typing import Any
40
+
41
+ # Bump when an existing event's fields are renamed, removed, or change meaning,
42
+ # or when a new event TYPE is added (additive fields on an existing event don't
43
+ # need a bump). A downstream parser can branch on this instead of guessing
44
+ # from `evalrx_version`.
45
+ # v2: `analysis`'s stats_tool_results/stats_results/stats_plan/
46
+ # corrected_rejections are now conditionally externalized (see
47
+ # _externalize_if_large) — a {path, n_items, bytes} summary instead of
48
+ # the raw value once it exceeds _INLINE_MAX_BYTES.
49
+ # v3: two new event types for AgenticDiagnoseLoop — `agent_decision` (one judge
50
+ # decision turn: chosen tool + rationale, keyed by `step` not `cycle`) and
51
+ # `agent_tool` (the dispatch layer's accept/reject outcome for that tool
52
+ # call). VLDiagnoseLoop/AutoDiagnoseLoop's events are unchanged.
53
+ # v5: the EvalVitals → EvalRX rename renamed the `evalvitals_version` field to
54
+ # `evalrx_version` on every event and moved the published schema's `$id`
55
+ # to https://evalrx.dev/schemas/run_log.schema.json. Old logs still parse
56
+ # (the schema is permissive on unknown fields) but a strict
57
+ # `evalrx_version`-keyed reader needs this bump to tell them apart from
58
+ # pre-rename logs.
59
+ # v6: folded in the bucketed-storage events (`model_call`, `diagnose_report`,
60
+ # `unrouted`) and the `stage`/`event_seq`/`span_id` envelope fields that
61
+ # were previously a separate, RunLoggerV2-only schema layered on top of
62
+ # this one — there is now one event schema, not a base plus an overlay.
63
+ RUN_LOG_SCHEMA_VERSION = 6
64
+
65
+ #: The event types a run logs.
66
+ EVENT_TYPES: tuple[str, ...] = (
67
+ "run_start",
68
+ "probe",
69
+ "explore",
70
+ "analysis",
71
+ "diagnosis",
72
+ "surgery",
73
+ "experiment",
74
+ "fix",
75
+ "loop_end",
76
+ "tool_codegen",
77
+ "tool_registry",
78
+ "agent_decision",
79
+ "agent_tool",
80
+ "case_record",
81
+ "report_published",
82
+ "stage_skipped",
83
+ "model_call",
84
+ "diagnose_report",
85
+ "unrouted",
86
+ )
87
+
88
+ #: Path to the committed, rendered schema shipped as package data.
89
+ SCHEMA_PATH = Path(__file__).with_name("run_log.schema.json")
90
+
91
+ # Reusable sub-schemas -------------------------------------------------------
92
+
93
+ _HYPOTHESIS = {
94
+ "type": "object",
95
+ "properties": {
96
+ "statement": {"type": "string"},
97
+ "plain_statement": {"type": ["string", "null"]},
98
+ "failure_mode": {"type": ["string", "null"]},
99
+ "status": {"type": ["string", "null"]},
100
+ "test_design": {"type": ["string", "null"]},
101
+ },
102
+ }
103
+
104
+ _JUDGE_IO = {
105
+ "type": "object",
106
+ "properties": {
107
+ "prompt_path": {"type": "string"},
108
+ "prompt_chars": {"type": "integer"},
109
+ "raw_path": {"type": "string"},
110
+ "raw_chars": {"type": "integer"},
111
+ },
112
+ }
113
+
114
+ # An M2 stats field is either its raw value (object/array) or, once it exceeds
115
+ # the inline-size threshold, an externalized {path, n_items, bytes} pointer.
116
+ _INLINE_OR_EXTERNAL = {"type": ["object", "array"]}
117
+
118
+ # Per-event field schemas + the fields each event always carries -------------
119
+ # (event, schema_version, ts, trace_id come from the shared envelope.)
120
+
121
+ _EVENTS: dict[str, dict[str, Any]] = {
122
+ "run_start": {
123
+ "required": [],
124
+ "properties": {
125
+ "evalrx_version": {"type": "string"},
126
+ "python_version": {"type": "string"},
127
+ "git_commit": {"type": ["string", "null"]},
128
+ "data_fingerprint": {"type": "string"},
129
+ "label_distribution": {"type": "object"},
130
+ "n_cases": {"type": "integer"},
131
+ "model": {"type": "string"},
132
+ # AgenticDiagnoseLoop only: the decision judge (distinct from the M3
133
+ # stage judge already covered by "judge") and its action-turn budget.
134
+ "decision_judge": {"type": "string"},
135
+ "max_actions": {"type": "integer"},
136
+ # In-cycle explore step configured (provider:model of its coder).
137
+ "explorer": {"type": "string"},
138
+ },
139
+ },
140
+ "probe": {
141
+ "required": ["cycle", "analyzers", "findings", "artifact_paths"],
142
+ "properties": {
143
+ "analyzers": {"type": "array", "items": {"type": "string"}},
144
+ "findings": {"type": "object"},
145
+ "artifact_paths": {"type": "object"},
146
+ # Additive M1 fields (permissive schema): result_paths (per-analyzer
147
+ # complete result json), failed_analyzers (selected-but-errored),
148
+ # selected_analyzers — see RunLoggerV2.log_probe.
149
+ "selection_rationale": {"type": "string"},
150
+ "judge_io": _JUDGE_IO,
151
+ "duration_sec": {"type": "number"},
152
+ # Every generate/forward/logprobs/chat call an analyzer made against
153
+ # the target model this cycle (see model_instrumentation.py); full
154
+ # verbatim records live in the sibling model_calls.jsonl this points
155
+ # to, kept out of this event so an analyzer making dozens of calls
156
+ # per case doesn't drown out the cycle-level narrative.
157
+ "n_model_calls": {"type": "integer"},
158
+ "model_calls_path": {"type": "string"},
159
+ },
160
+ },
161
+ "analysis": {
162
+ "required": ["cycle", "severity", "n_findings", "findings", "narrative"],
163
+ "properties": {
164
+ "severity": {"type": "string"},
165
+ "n_findings": {"type": "integer"},
166
+ "findings": {"type": "array", "items": {"type": "string"}},
167
+ "narrative": {"type": "string"},
168
+ "conclusion": {"type": "string"},
169
+ "evidence_chain": {"type": "array"},
170
+ "stats_tool_results": _INLINE_OR_EXTERNAL,
171
+ "stats_plan": _INLINE_OR_EXTERNAL,
172
+ "stats_results": _INLINE_OR_EXTERNAL,
173
+ "corrected_rejections": _INLINE_OR_EXTERNAL,
174
+ "visualizations": {"type": "array"},
175
+ "figures": {"type": "array"},
176
+ "judge_io": _JUDGE_IO,
177
+ "duration_sec": {"type": "number"},
178
+ },
179
+ },
180
+ "explore": {
181
+ # Optional in-cycle explore step (VLDiagnoseLoop(explorer=...)): a
182
+ # free-form EDA pass over M1's per-case table between M1 and M2.
183
+ # DESCRIPTIVE ONLY — counts, observations, rendered figure paths and
184
+ # where the report/tables/figures were persisted; the explorer's
185
+ # in-sample candidate verdicts appear only as counts under
186
+ # `adjudication` and never enter the confirmatory M2 family.
187
+ "required": ["cycle", "ok", "n_observations", "n_charts", "n_tables"],
188
+ "properties": {
189
+ "ok": {"type": "boolean"},
190
+ "n_observations": {"type": "integer"},
191
+ "n_charts": {"type": "integer"},
192
+ "n_charts_rendered": {"type": "integer"},
193
+ "n_tables": {"type": "integer"},
194
+ "n_candidate_signals": {"type": "integer"},
195
+ "n_hypotheses": {"type": "integer"},
196
+ "adjudication": {"type": "object"},
197
+ "observations": {"type": "array", "items": {"type": "string"}},
198
+ "caveats": {"type": "array", "items": {"type": "string"}},
199
+ "figures": {"type": "array", "items": {"type": "string"}},
200
+ "attempts": {"type": "integer"},
201
+ "error": {"type": "string"},
202
+ "out_dir": {"type": "string"},
203
+ "report_path": {"type": "string"},
204
+ "duration_sec": {"type": "number"},
205
+ },
206
+ },
207
+ "diagnosis": {
208
+ "required": ["cycle", "model_name", "n_hypotheses", "hypotheses", "raw_judge_output"],
209
+ "properties": {
210
+ "model_name": {"type": ["string", "null"]},
211
+ "n_hypotheses": {"type": "integer"},
212
+ "hypotheses": {"type": "array", "items": _HYPOTHESIS},
213
+ "raw_judge_output": {"type": ["string", "null"]},
214
+ # Provenance of the (UNCONFIRMED) explorer mechanism notes M3 was shown.
215
+ # Descriptive/display-only — never enters M2/M4/fix.
216
+ "referenced_charts": {"type": "array", "items": {"type": "string"}},
217
+ "explore_context_used": {"type": "boolean"},
218
+ "explore_figures": {"type": "array", "items": {"type": "string"}},
219
+ # Whether a (clustered, descriptive-only) FailureModeReport was
220
+ # shown to M3 — additive field, no schema version bump needed.
221
+ "failure_modes_used": {"type": "boolean"},
222
+ "judge_io": _JUDGE_IO,
223
+ "duration_sec": {"type": "number"},
224
+ },
225
+ },
226
+ "surgery": {
227
+ "required": ["cycle", "module", "hypothesis", "failure_mode", "status", "fixed"],
228
+ "properties": {
229
+ "module": {"type": "string"},
230
+ "hypothesis": {"type": "string"},
231
+ "failure_mode": {"type": ["string", "null"]},
232
+ "status": {"type": "string"},
233
+ "fixed": {"type": "boolean"},
234
+ "confidence_score": {"type": ["number", "null"]},
235
+ "evidence": {"type": ["object", "null"]},
236
+ "n_refocused_cases": {"type": ["integer", "null"]},
237
+ "duration_sec": {"type": "number"},
238
+ # Additive: M4 protocol-consistency judge prompt/response, persisted
239
+ # under prompts/ (same pattern as probe/analysis/diagnosis).
240
+ "judge_io": _JUDGE_IO,
241
+ },
242
+ },
243
+ "experiment": {
244
+ "required": ["cycle", "module", "hypothesis", "failure_mode", "status", "fixed"],
245
+ "properties": {
246
+ "module": {"type": "string"},
247
+ "hypothesis": {"type": "string"},
248
+ "failure_mode": {"type": ["string", "null"]},
249
+ "status": {"type": ["string", "null"]},
250
+ "fixed": {"type": "boolean"},
251
+ "provider": {"type": ["string", "null"]},
252
+ "metrics": {"type": ["object", "null"]},
253
+ "returncode": {"type": ["integer", "null"]},
254
+ "timed_out": {"type": ["boolean", "null"]},
255
+ "llm_calls": {"type": ["integer", "null"]},
256
+ "code_paths": {"type": "object"},
257
+ "output_paths": {"type": "object"},
258
+ "workspace_snapshot": {"type": ["object", "null"]},
259
+ "record": {"type": "string"},
260
+ },
261
+ },
262
+ "fix": {
263
+ "required": ["cycle", "module"],
264
+ "properties": {
265
+ "module": {"type": "string"},
266
+ "record": {"type": "string"},
267
+ },
268
+ },
269
+ "loop_end": {
270
+ "required": ["cycles"],
271
+ "properties": {
272
+ "cycles": {"type": "integer"},
273
+ "tokens_used": {"type": ["integer", "null"]},
274
+ "timings_sec": {"type": "object"},
275
+ "total_duration_sec": {"type": "number"},
276
+ "resolved": {"type": "boolean"},
277
+ "n_hypotheses": {"type": "integer"},
278
+ "final_hypotheses": {"type": "array", "items": _HYPOTHESIS},
279
+ "stopped_by": {"type": ["string", "null"]},
280
+ "n_verified": {"type": "integer"},
281
+ "verified_hypotheses": {"type": "array"},
282
+ },
283
+ },
284
+ "tool_codegen": {
285
+ "required": ["cycle", "module", "tool_name", "need", "source", "ok"],
286
+ "properties": {
287
+ "module": {"type": "string"},
288
+ "tool_name": {"type": "string"},
289
+ "need": {"type": "string"},
290
+ "source": {"type": "string"},
291
+ "ok": {"type": "boolean"},
292
+ "error": {"type": ["string", "null"]},
293
+ "code_paths": {"type": "object"},
294
+ },
295
+ },
296
+ "tool_registry": {
297
+ "required": ["cycle", "module", "n_tools", "tools"],
298
+ "properties": {
299
+ "module": {"type": "string"},
300
+ "n_tools": {"type": "integer"},
301
+ "tools": {"type": "array"},
302
+ },
303
+ },
304
+ "agent_decision": {
305
+ "required": ["step", "action", "valid"],
306
+ "properties": {
307
+ "step": {"type": "integer"},
308
+ "action": {"type": "string"},
309
+ "params": {"type": "object"},
310
+ "rationale": {"type": "string"},
311
+ "valid": {"type": "boolean"},
312
+ "repair_attempts": {"type": "integer"},
313
+ "fallback_used": {"type": "boolean"},
314
+ "judge_io": _JUDGE_IO,
315
+ "duration_sec": {"type": "number"},
316
+ },
317
+ },
318
+ "agent_tool": {
319
+ "required": ["step", "tool", "ok"],
320
+ "properties": {
321
+ "step": {"type": "integer"},
322
+ "tool": {"type": "string"},
323
+ "ok": {"type": "boolean"},
324
+ "summary": {"type": "string"},
325
+ "error": {"type": ["string", "null"]},
326
+ "duration_sec": {"type": "number"},
327
+ },
328
+ },
329
+ "case_record": {
330
+ "required": ["case_id", "case", "media_paths"],
331
+ "properties": {
332
+ "case_id": {"type": "string"},
333
+ "case": {"type": "object"},
334
+ "media_paths": {"type": "array", "items": {"type": "string"}},
335
+ },
336
+ },
337
+ "report_published": {
338
+ "required": ["report_schema_version", "catalog_version", "sha256", "generated_by"],
339
+ "properties": {
340
+ "report_schema_version": {"type": "integer"},
341
+ "catalog_version": {"type": "string"},
342
+ "json_render_version": {"type": "string"},
343
+ "source_event_seq": {"type": "integer"},
344
+ "sha256": {"type": "string"},
345
+ "generated_by": {"type": "object"},
346
+ "report_paths": {"type": "array", "items": {"type": "string"}},
347
+ },
348
+ },
349
+ "stage_skipped": {
350
+ "required": ["stage", "cycle", "reason_code"],
351
+ "properties": {
352
+ "stage": {"type": "string"},
353
+ "reason_code": {"type": "string"},
354
+ "detail": {"type": "string"},
355
+ },
356
+ },
357
+ "model_call": {
358
+ # A single generate/forward/logprobs/chat call against the target
359
+ # model, recorded immediately (see model_instrumentation.py). Either
360
+ # an analyzer's direct call (analyzer/method/call_index) or a
361
+ # judge/coder exchange (role/operation).
362
+ "required": ["cycle", "inputs", "output"],
363
+ "properties": {
364
+ "duration_sec": {"type": "number"},
365
+ "error": {"type": ["string", "null"]},
366
+ },
367
+ "anyOf": [{"required": ["analyzer", "method", "call_index"]},
368
+ {"required": ["role", "operation"]}],
369
+ },
370
+ "diagnose_report": {
371
+ "required": ["cycles", "hypotheses", "m4_results", "discovery"],
372
+ "properties": {key: {"type": "array"} for key in
373
+ ("hypotheses", "m4_results", "discovery")},
374
+ },
375
+ "unrouted": {
376
+ # A caller passed a tag _resolve_stage couldn't map to M1-M5 — filed
377
+ # here instead of being lost.
378
+ "required": ["key", "tag"],
379
+ "properties": {
380
+ "key": {"type": "string"},
381
+ "tag": {"type": ["string", "null"]},
382
+ },
383
+ },
384
+ }
385
+
386
+
387
+ def build_schema() -> dict[str, Any]:
388
+ """Return the JSON Schema (Draft 2020-12) for a run's logged events.
389
+
390
+ Built from the stdlib only — no third-party import — so this stays callable
391
+ in the light install. The single source of truth for the version pin is
392
+ :data:`RUN_LOG_SCHEMA_VERSION`. Validates one event at a time regardless of
393
+ where it's stored — a line in a flat log, or a record inside a bucketed
394
+ ``run.json``/``M<n>/log.json`` array.
395
+ """
396
+ envelope = {
397
+ "type": "object",
398
+ "required": ["event", "schema_version", "ts", "trace_id", "event_seq", "stage", "span_id"],
399
+ "properties": {
400
+ "event": {"type": "string", "enum": list(EVENT_TYPES)},
401
+ "schema_version": {"const": RUN_LOG_SCHEMA_VERSION},
402
+ # `format` is for documentation/interop; jsonschema only enforces it
403
+ # when the optional rfc3339-validator is installed, so a `pattern`
404
+ # pins the ISO-8601 shape unconditionally (it matches the producer's
405
+ # datetime.now(UTC).isoformat(timespec="microseconds")).
406
+ "ts": {
407
+ "type": "string",
408
+ "format": "date-time",
409
+ "pattern": r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$",
410
+ },
411
+ "trace_id": {"type": "string"},
412
+ "event_seq": {"type": "integer", "minimum": 1},
413
+ "span_id": {"type": "string"},
414
+ "stage": {"enum": ["RUN", "M1", "M2", "M3", "M4", "M5"]},
415
+ "cycle": {"type": "integer"},
416
+ },
417
+ }
418
+
419
+ defs: dict[str, Any] = {"envelope": envelope}
420
+ branches: list[dict[str, Any]] = []
421
+ for name, spec in _EVENTS.items():
422
+ variant: dict[str, Any] = {
423
+ "type": "object",
424
+ "properties": {"event": {"const": name}, **spec["properties"]},
425
+ "required": ["event", *spec["required"]],
426
+ }
427
+ if "anyOf" in spec:
428
+ variant["anyOf"] = spec["anyOf"]
429
+ branch = {"allOf": [{"$ref": "#/$defs/envelope"}, variant]}
430
+ defs[name] = branch
431
+ branches.append({"$ref": f"#/$defs/{name}"})
432
+
433
+ return {
434
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
435
+ "$id": "https://evalrx.dev/schemas/run_log.schema.json",
436
+ "title": "EvalRX run event",
437
+ "description": (
438
+ "One logged event, wherever a run stores it (run.json / "
439
+ "M<n>/log.json). Discriminated on `event`; matches schema_version "
440
+ f"{RUN_LOG_SCHEMA_VERSION}. Permissive: extra (additive) fields "
441
+ "are allowed."
442
+ ),
443
+ "$defs": defs,
444
+ "oneOf": branches,
445
+ }
446
+
447
+
448
+ def load_schema() -> dict[str, Any]:
449
+ """Load the committed, rendered :data:`SCHEMA_PATH` (the shipped artifact)."""
450
+ return json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
451
+
452
+
453
+ def _validator(schema: dict[str, Any] | None = None):
454
+ """Build a ``jsonschema`` validator with the date-time format checker on.
455
+
456
+ Raises a clear :class:`ImportError` if ``jsonschema`` (an optional dep) is
457
+ not installed, so the failure points at the fix instead of an obscure
458
+ ``ModuleNotFoundError`` deep in a call stack.
459
+ """
460
+ try:
461
+ import jsonschema
462
+ except ImportError as exc: # pragma: no cover - exercised via message only
463
+ raise ImportError(
464
+ "validating run_log.jsonl needs the 'jsonschema' package — "
465
+ "install it with `pip install jsonschema` or `pip install evalrx[dev]`."
466
+ ) from exc
467
+ schema = schema if schema is not None else build_schema()
468
+ cls = jsonschema.validators.validator_for(schema)
469
+ cls.check_schema(schema)
470
+ return cls(schema, format_checker=cls.FORMAT_CHECKER)
471
+
472
+
473
+ def validate_event(event: dict[str, Any], *, schema: dict[str, Any] | None = None) -> None:
474
+ """Validate a single decoded event; raise ``jsonschema.ValidationError`` if bad."""
475
+ _validator(schema).validate(event)
476
+
477
+
478
+ def iter_log_errors(path: str | Path, *, schema: dict[str, Any] | None = None):
479
+ """Yield ``(line_number, message)`` for every invalid line in a JSONL log.
480
+
481
+ A convenience for CI / downstream consumers: parse-and-validate a whole
482
+ ``run_log.jsonl`` in one call. Lines that aren't valid JSON are reported
483
+ too. An empty iterator means the file fully conforms.
484
+ """
485
+ validator = _validator(schema)
486
+ with open(path, encoding="utf-8") as fh:
487
+ for i, line in enumerate(fh, start=1):
488
+ line = line.strip()
489
+ if not line:
490
+ continue
491
+ try:
492
+ event = json.loads(line)
493
+ except json.JSONDecodeError as exc:
494
+ yield i, f"invalid JSON: {exc}"
495
+ continue
496
+ for err in validator.iter_errors(event):
497
+ yield i, err.message