agentinputlab 0.1.0a1__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 (469) hide show
  1. agentinputlab/__init__.py +28 -0
  2. agentinputlab/algorithms/__init__.py +6 -0
  3. agentinputlab/algorithms/acquire/__init__.py +145 -0
  4. agentinputlab/algorithms/acquire/adaptive_rag/__init__.py +43 -0
  5. agentinputlab/algorithms/acquire/adaptive_rag/replay.py +367 -0
  6. agentinputlab/algorithms/acquire/adaptive_rag/runtime.py +1202 -0
  7. agentinputlab/algorithms/acquire/adaptive_rag_common_support.py +542 -0
  8. agentinputlab/algorithms/acquire/hyde.py +296 -0
  9. agentinputlab/algorithms/acquire/hyde_controlled.py +200 -0
  10. agentinputlab/algorithms/acquire/hyde_external.py +148 -0
  11. agentinputlab/algorithms/acquire/ircot.py +28 -0
  12. agentinputlab/algorithms/acquire/ircot_common_support.py +478 -0
  13. agentinputlab/algorithms/acquire/ircot_native.py +355 -0
  14. agentinputlab/algorithms/acquire/ircot_replay.py +327 -0
  15. agentinputlab/algorithms/acquire/qrag/__init__.py +79 -0
  16. agentinputlab/algorithms/acquire/qrag/bridge.py +828 -0
  17. agentinputlab/algorithms/acquire/qrag/common_support.py +891 -0
  18. agentinputlab/algorithms/acquire/qrag/native_axis.py +71 -0
  19. agentinputlab/algorithms/acquire/qrag/reader_adapter.py +139 -0
  20. agentinputlab/algorithms/acquire/r2ag/__init__.py +57 -0
  21. agentinputlab/algorithms/acquire/r2ag/bridge.py +1312 -0
  22. agentinputlab/algorithms/acquire/r2ag/common_support.py +1029 -0
  23. agentinputlab/algorithms/acquire/r2ag/socket_runtime.py +255 -0
  24. agentinputlab/algorithms/clarify/__init__.py +61 -0
  25. agentinputlab/algorithms/clarify/_structured.py +64 -0
  26. agentinputlab/algorithms/clarify/active_task_disambiguation.py +371 -0
  27. agentinputlab/algorithms/clarify/ask_or_assume/__init__.py +17 -0
  28. agentinputlab/algorithms/clarify/ask_or_assume/monitor.py +410 -0
  29. agentinputlab/algorithms/clarify/future_turns.py +379 -0
  30. agentinputlab/algorithms/clarify/udcs.py +255 -0
  31. agentinputlab/algorithms/clarify/udcs_deterministic.py +196 -0
  32. agentinputlab/algorithms/curate/__init__.py +65 -0
  33. agentinputlab/algorithms/curate/acon/__init__.py +27 -0
  34. agentinputlab/algorithms/curate/acon/common.py +284 -0
  35. agentinputlab/algorithms/curate/acon/common_support.py +136 -0
  36. agentinputlab/algorithms/curate/acon/history_bridge.py +309 -0
  37. agentinputlab/algorithms/curate/acon/observation_bridge.py +290 -0
  38. agentinputlab/algorithms/curate/llmlingua/__init__.py +30 -0
  39. agentinputlab/algorithms/curate/llmlingua/adapters.py +537 -0
  40. agentinputlab/algorithms/curate/llmlingua/budget.py +48 -0
  41. agentinputlab/algorithms/curate/llmlingua/controlled.py +175 -0
  42. agentinputlab/algorithms/curate/llmlingua/multilingual.py +105 -0
  43. agentinputlab/algorithms/curate/llmlingua/span_map.py +167 -0
  44. agentinputlab/algorithms/curate/recomp/__init__.py +47 -0
  45. agentinputlab/algorithms/curate/recomp/external.py +1129 -0
  46. agentinputlab/algorithms/curate/recomp/selective.py +304 -0
  47. agentinputlab/algorithms/decompose/__init__.py +107 -0
  48. agentinputlab/algorithms/decompose/decomp/__init__.py +53 -0
  49. agentinputlab/algorithms/decompose/decomp/common_support.py +478 -0
  50. agentinputlab/algorithms/decompose/decomp/planner_component.py +199 -0
  51. agentinputlab/algorithms/decompose/decomp/worker_bridge.py +416 -0
  52. agentinputlab/algorithms/decompose/llm_compiler/__init__.py +35 -0
  53. agentinputlab/algorithms/decompose/llm_compiler/bridge.py +520 -0
  54. agentinputlab/algorithms/decompose/llm_compiler/common_support.py +504 -0
  55. agentinputlab/algorithms/decompose/plan_and_budget/__init__.py +43 -0
  56. agentinputlab/algorithms/decompose/plan_and_budget/cached_soft_budget.py +362 -0
  57. agentinputlab/algorithms/decompose/plan_and_budget/decomposer_component.py +107 -0
  58. agentinputlab/algorithms/decompose/plan_and_budget/full.py +205 -0
  59. agentinputlab/algorithms/decompose/plan_and_budget/hard_cap.py +246 -0
  60. agentinputlab/algorithms/decompose/plan_and_budget/regenerated_soft_budget.py +448 -0
  61. agentinputlab/algorithms/decompose/plan_and_solve/__init__.py +33 -0
  62. agentinputlab/algorithms/decompose/plan_and_solve/plan_component.py +192 -0
  63. agentinputlab/algorithms/decompose/plan_and_solve/ps_prompt.py +127 -0
  64. agentinputlab/algorithms/decompose/self_ask/__init__.py +27 -0
  65. agentinputlab/algorithms/decompose/self_ask/loop.py +362 -0
  66. agentinputlab/algorithms/decompose/self_ask/planner_component.py +117 -0
  67. agentinputlab/algorithms/reformulate/__init__.py +85 -0
  68. agentinputlab/algorithms/reformulate/infocqr/__init__.py +40 -0
  69. agentinputlab/algorithms/reformulate/infocqr/cache_replay.py +362 -0
  70. agentinputlab/algorithms/reformulate/infocqr/external.py +675 -0
  71. agentinputlab/algorithms/reformulate/infocqr/student.py +189 -0
  72. agentinputlab/algorithms/reformulate/query2doc/__init__.py +42 -0
  73. agentinputlab/algorithms/reformulate/query2doc/controlled_live.py +190 -0
  74. agentinputlab/algorithms/reformulate/query2doc/models.py +95 -0
  75. agentinputlab/algorithms/reformulate/query2doc/official_cached.py +335 -0
  76. agentinputlab/algorithms/reformulate/query2doc/querygym_bridge.py +223 -0
  77. agentinputlab/algorithms/reformulate/query2doc/querygym_runtime.py +326 -0
  78. agentinputlab/algorithms/reformulate/reformer/__init__.py +23 -0
  79. agentinputlab/algorithms/reformulate/reformer/external.py +417 -0
  80. agentinputlab/algorithms/reformulate/rrr.py +696 -0
  81. agentinputlab/annotation/__init__.py +5 -0
  82. agentinputlab/annotation/independent.py +428 -0
  83. agentinputlab/annotation/server.py +233 -0
  84. agentinputlab/annotation/static/app.js +121 -0
  85. agentinputlab/annotation/static/index.html +60 -0
  86. agentinputlab/annotation/static/review.css +2 -0
  87. agentinputlab/annotation/static/review.html +10 -0
  88. agentinputlab/annotation/static/review.js +11 -0
  89. agentinputlab/annotation/static/styles.css +55 -0
  90. agentinputlab/annotation/store.py +444 -0
  91. agentinputlab/api.py +368 -0
  92. agentinputlab/artifacts/__init__.py +15 -0
  93. agentinputlab/artifacts/json_file.py +104 -0
  94. agentinputlab/artifacts/manifest.py +98 -0
  95. agentinputlab/artifacts/store.py +157 -0
  96. agentinputlab/backends/__init__.py +98 -0
  97. agentinputlab/backends/embedding/__init__.py +25 -0
  98. agentinputlab/backends/embedding/base.py +145 -0
  99. agentinputlab/backends/embedding/callable.py +75 -0
  100. agentinputlab/backends/embedding/replay.py +129 -0
  101. agentinputlab/backends/llm/__init__.py +31 -0
  102. agentinputlab/backends/llm/base.py +149 -0
  103. agentinputlab/backends/llm/callable.py +108 -0
  104. agentinputlab/backends/llm/decomp_completion.py +88 -0
  105. agentinputlab/backends/llm/final_content_stop.py +72 -0
  106. agentinputlab/backends/llm/gpt_oss_final_channel.py +84 -0
  107. agentinputlab/backends/llm/openai_compatible.py +332 -0
  108. agentinputlab/backends/llm/transformers.py +171 -0
  109. agentinputlab/backends/retrieval/__init__.py +35 -0
  110. agentinputlab/backends/retrieval/base.py +50 -0
  111. agentinputlab/backends/retrieval/dense.py +777 -0
  112. agentinputlab/backends/retrieval/memory.py +119 -0
  113. agentinputlab/backends/retrieval/sqlite_fts.py +155 -0
  114. agentinputlab/backends/user/__init__.py +15 -0
  115. agentinputlab/backends/user/base.py +66 -0
  116. agentinputlab/backends/user/callable.py +54 -0
  117. agentinputlab/backends/user/replay.py +89 -0
  118. agentinputlab/backends/vector.py +38 -0
  119. agentinputlab/benchmarks/__init__.py +246 -0
  120. agentinputlab/benchmarks/acquisition.py +106 -0
  121. agentinputlab/benchmarks/ale.py +540 -0
  122. agentinputlab/benchmarks/appworld_ul.py +669 -0
  123. agentinputlab/benchmarks/assets.py +226 -0
  124. agentinputlab/benchmarks/base.py +68 -0
  125. agentinputlab/benchmarks/beir_archive.py +235 -0
  126. agentinputlab/benchmarks/beir_sample.py +269 -0
  127. agentinputlab/benchmarks/bfcl_native.py +245 -0
  128. agentinputlab/benchmarks/bfcl_v3.py +607 -0
  129. agentinputlab/benchmarks/cache.py +218 -0
  130. agentinputlab/benchmarks/clariq.py +203 -0
  131. agentinputlab/benchmarks/data/registry.json +321 -0
  132. agentinputlab/benchmarks/data/smoke_assets.json +511 -0
  133. agentinputlab/benchmarks/data/statebench_v0_8_1_task_ids.json +475 -0
  134. agentinputlab/benchmarks/data/toolsandbox_165848b_scenario_names.json +1 -0
  135. agentinputlab/benchmarks/execution.py +1111 -0
  136. agentinputlab/benchmarks/interactive.py +257 -0
  137. agentinputlab/benchmarks/ir_query.py +204 -0
  138. agentinputlab/benchmarks/longbench.py +203 -0
  139. agentinputlab/benchmarks/paired_replay.py +479 -0
  140. agentinputlab/benchmarks/preprocess.py +2313 -0
  141. agentinputlab/benchmarks/production.py +236 -0
  142. agentinputlab/benchmarks/qa.py +963 -0
  143. agentinputlab/benchmarks/registry.py +110 -0
  144. agentinputlab/benchmarks/retrieval.py +459 -0
  145. agentinputlab/benchmarks/runtime.py +509 -0
  146. agentinputlab/benchmarks/statebench.py +938 -0
  147. agentinputlab/benchmarks/tau3.py +725 -0
  148. agentinputlab/benchmarks/toolsandbox.py +626 -0
  149. agentinputlab/benchmarks/trec_dl_sample.py +263 -0
  150. agentinputlab/campaign.py +654 -0
  151. agentinputlab/campaign_runner.py +953 -0
  152. agentinputlab/cli/__init__.py +6 -0
  153. agentinputlab/cli/main.py +933 -0
  154. agentinputlab/connectors/__init__.py +46 -0
  155. agentinputlab/connectors/knowledge/__init__.py +4 -0
  156. agentinputlab/connectors/tools/__init__.py +30 -0
  157. agentinputlab/connectors/tools/base.py +57 -0
  158. agentinputlab/connectors/tools/mcp.py +222 -0
  159. agentinputlab/connectors/tools/memory.py +41 -0
  160. agentinputlab/connectors/tools/openapi.py +695 -0
  161. agentinputlab/connectors/tools/schema.py +102 -0
  162. agentinputlab/contracts/__init__.py +91 -0
  163. agentinputlab/contracts/availability.py +24 -0
  164. agentinputlab/contracts/dependency.py +58 -0
  165. agentinputlab/contracts/enums.py +82 -0
  166. agentinputlab/contracts/errors.py +63 -0
  167. agentinputlab/contracts/evidence.py +43 -0
  168. agentinputlab/contracts/intent.py +110 -0
  169. agentinputlab/contracts/lifecycle.py +164 -0
  170. agentinputlab/contracts/patch.py +167 -0
  171. agentinputlab/contracts/serialization.py +93 -0
  172. agentinputlab/contracts/state.py +127 -0
  173. agentinputlab/contracts/trace.py +59 -0
  174. agentinputlab/contracts/worker.py +42 -0
  175. agentinputlab/core/__init__.py +7 -0
  176. agentinputlab/data/__init__.py +32 -0
  177. agentinputlab/data/checksums.py +9 -0
  178. agentinputlab/data/downloader.py +235 -0
  179. agentinputlab/data/http.py +171 -0
  180. agentinputlab/data/index.py +21 -0
  181. agentinputlab/data/overlap.py +28 -0
  182. agentinputlab/data/preprocess.py +32 -0
  183. agentinputlab/data/storage.py +57 -0
  184. agentinputlab/data/trec_sample.py +427 -0
  185. agentinputlab/evaluation/__init__.py +5 -0
  186. agentinputlab/evaluation/outcomes.py +32 -0
  187. agentinputlab/experiments/__init__.py +278 -0
  188. agentinputlab/experiments/analysis.py +981 -0
  189. agentinputlab/experiments/artifacts.py +687 -0
  190. agentinputlab/experiments/automatic_measurement.py +678 -0
  191. agentinputlab/experiments/auxiliary_matrix.py +203 -0
  192. agentinputlab/experiments/bindings.py +430 -0
  193. agentinputlab/experiments/blueprints.py +147 -0
  194. agentinputlab/experiments/builders/__init__.py +45 -0
  195. agentinputlab/experiments/builders/executable_external_payload.py +423 -0
  196. agentinputlab/experiments/builders/external_process_arena.py +1018 -0
  197. agentinputlab/experiments/builders/registry_intervention.py +463 -0
  198. agentinputlab/experiments/cli.py +158 -0
  199. agentinputlab/experiments/controls.py +340 -0
  200. agentinputlab/experiments/driver_guard.py +205 -0
  201. agentinputlab/experiments/drivers/__init__.py +89 -0
  202. agentinputlab/experiments/drivers/external_arena.py +2908 -0
  203. agentinputlab/experiments/drivers/method_process.py +662 -0
  204. agentinputlab/experiments/drivers/parity_process.py +1484 -0
  205. agentinputlab/experiments/drivers/static_production.py +1574 -0
  206. agentinputlab/experiments/executor.py +448 -0
  207. agentinputlab/experiments/loader.py +605 -0
  208. agentinputlab/experiments/measurement.py +675 -0
  209. agentinputlab/experiments/paper_analysis.py +1818 -0
  210. agentinputlab/experiments/parity_identity.py +605 -0
  211. agentinputlab/experiments/plan.py +277 -0
  212. agentinputlab/experiments/production_builders.py +3739 -0
  213. agentinputlab/experiments/readiness.py +432 -0
  214. agentinputlab/experiments/schema.py +514 -0
  215. agentinputlab/experiments/secrets.py +177 -0
  216. agentinputlab/experiments/stateful_analysis.py +1738 -0
  217. agentinputlab/experiments/transfer_analysis.py +1316 -0
  218. agentinputlab/experiments/unseen_analysis.py +1025 -0
  219. agentinputlab/integrations/__init__.py +1 -0
  220. agentinputlab/integrations/action_space/__init__.py +1 -0
  221. agentinputlab/integrations/external_workers/__init__.py +76 -0
  222. agentinputlab/integrations/external_workers/ace.py +431 -0
  223. agentinputlab/integrations/external_workers/base.py +181 -0
  224. agentinputlab/integrations/external_workers/decomp_live.py +249 -0
  225. agentinputlab/integrations/external_workers/decomp_planner.py +30 -0
  226. agentinputlab/integrations/external_workers/infocqr_student.py +280 -0
  227. agentinputlab/integrations/external_workers/ircot.py +576 -0
  228. agentinputlab/integrations/external_workers/llmcompiler_live.py +360 -0
  229. agentinputlab/integrations/external_workers/longllmlingua.py +162 -0
  230. agentinputlab/integrations/external_workers/method.py +314 -0
  231. agentinputlab/integrations/external_workers/process.py +678 -0
  232. agentinputlab/integrations/external_workers/supervisor.py +1071 -0
  233. agentinputlab/integrations/full_architecture/__init__.py +1 -0
  234. agentinputlab/integrations/full_architecture/hyde.py +212 -0
  235. agentinputlab/integrations/full_architecture/plan_and_budget.py +188 -0
  236. agentinputlab/integrations/live_history.py +222 -0
  237. agentinputlab/integrations/prompt_optimization/__init__.py +1 -0
  238. agentinputlab/integrations/training/__init__.py +1 -0
  239. agentinputlab/metrics/__init__.py +50 -0
  240. agentinputlab/metrics/catalog.py +313 -0
  241. agentinputlab/metrics/harm.py +95 -0
  242. agentinputlab/metrics/paired.py +40 -0
  243. agentinputlab/metrics/qa.py +76 -0
  244. agentinputlab/metrics/text.py +33 -0
  245. agentinputlab/metrics/third_party.py +161 -0
  246. agentinputlab/models/__init__.py +4 -0
  247. agentinputlab/observability/__init__.py +56 -0
  248. agentinputlab/observability/budget.py +316 -0
  249. agentinputlab/observability/instrumentation.py +754 -0
  250. agentinputlab/observability/models.py +403 -0
  251. agentinputlab/observability/monitor.py +380 -0
  252. agentinputlab/optimizers/__init__.py +1 -0
  253. agentinputlab/optimizers/clarify/__init__.py +12 -0
  254. agentinputlab/optimizers/context/__init__.py +22 -0
  255. agentinputlab/optimizers/decompose/__init__.py +64 -0
  256. agentinputlab/optimizers/evidence/__init__.py +22 -0
  257. agentinputlab/optimizers/rewrite/__init__.py +30 -0
  258. agentinputlab/parity/__init__.py +5 -0
  259. agentinputlab/parity/base.py +206 -0
  260. agentinputlab/production_campaign.py +763 -0
  261. agentinputlab/py.typed +1 -0
  262. agentinputlab/recipes/__init__.py +333 -0
  263. agentinputlab/recipes/ace.py +418 -0
  264. agentinputlab/recipes/ace_bfcl.py +273 -0
  265. agentinputlab/recipes/ace_common_support.py +169 -0
  266. agentinputlab/recipes/ace_native_plan.py +322 -0
  267. agentinputlab/recipes/ace_online.py +711 -0
  268. agentinputlab/recipes/ace_retrieval.py +330 -0
  269. agentinputlab/recipes/ace_statebench.py +382 -0
  270. agentinputlab/recipes/ace_tau.py +463 -0
  271. agentinputlab/recipes/ace_toolsandbox.py +432 -0
  272. agentinputlab/recipes/acon_guideline.py +22 -0
  273. agentinputlab/recipes/adaptive_rag.py +822 -0
  274. agentinputlab/recipes/llmlingua2_inference.py +146 -0
  275. agentinputlab/recipes/llmlingua2_training.py +796 -0
  276. agentinputlab/recipes/mce.py +1486 -0
  277. agentinputlab/recipes/mce_final.py +335 -0
  278. agentinputlab/recipes/mce_text_fit.py +499 -0
  279. agentinputlab/recipes/mce_text_only.py +298 -0
  280. agentinputlab/recipes/qrag.py +729 -0
  281. agentinputlab/recipes/qrag_inference.py +364 -0
  282. agentinputlab/recipes/r2ag_features.py +236 -0
  283. agentinputlab/recipes/r2ag_inference.py +503 -0
  284. agentinputlab/recipes/r2ag_training.py +948 -0
  285. agentinputlab/recipes/recomp_abstractive.py +592 -0
  286. agentinputlab/recipes/recomp_extractive.py +525 -0
  287. agentinputlab/recipes/reformer.py +17 -0
  288. agentinputlab/recipes/rqrag.py +1516 -0
  289. agentinputlab/recipes/rqrag_common_support.py +193 -0
  290. agentinputlab/recipes/rqrag_completion.py +147 -0
  291. agentinputlab/recipes/skillopt.py +462 -0
  292. agentinputlab/recipes/skillopt_common_support.py +154 -0
  293. agentinputlab/recipes/skillopt_environment.py +165 -0
  294. agentinputlab/recipes/skillopt_environment_cli.py +5 -0
  295. agentinputlab/recipes/skillopt_retrieval.py +188 -0
  296. agentinputlab/recipes/smartsearch.py +897 -0
  297. agentinputlab/recipes/smartsearch_curriculum.py +283 -0
  298. agentinputlab/recipes/smartsearch_grpo_contracts.py +58 -0
  299. agentinputlab/recipes/smartsearch_grpo_data.py +314 -0
  300. agentinputlab/recipes/smartsearch_grpo_meter.py +324 -0
  301. agentinputlab/recipes/smartsearch_grpo_prefix.py +144 -0
  302. agentinputlab/recipes/smartsearch_grpo_rollout.py +119 -0
  303. agentinputlab/recipes/smartsearch_grpo_tools.py +364 -0
  304. agentinputlab/recipes/smartsearch_grpo_training.py +305 -0
  305. agentinputlab/recipes/smartsearch_grpo_transport.py +248 -0
  306. agentinputlab/recipes/smartsearch_grpo_worker.py +364 -0
  307. agentinputlab/recipes/smartsearch_inference.py +524 -0
  308. agentinputlab/recipes/smartsearch_native_data.py +172 -0
  309. agentinputlab/recipes/smartsearch_policy.py +616 -0
  310. agentinputlab/recipes/smartsearch_policy_worker.py +9 -0
  311. agentinputlab/recipes/smartsearch_sampling.py +232 -0
  312. agentinputlab/recipes/smartsearch_teacher.py +203 -0
  313. agentinputlab/recipes/smartsearch_training.py +265 -0
  314. agentinputlab/registry/__init__.py +46 -0
  315. agentinputlab/registry/beta.py +727 -0
  316. agentinputlab/registry/data/algorithms.json +1959 -0
  317. agentinputlab/registry/data/auxiliary_method_profiles.json +34 -0
  318. agentinputlab/registry/loader.py +100 -0
  319. agentinputlab/registry/result_manifest.py +184 -0
  320. agentinputlab/registry/runtime.py +195 -0
  321. agentinputlab/registry/schema.py +449 -0
  322. agentinputlab/runner/__init__.py +30 -0
  323. agentinputlab/runner/balance.py +248 -0
  324. agentinputlab/runner/cache.py +135 -0
  325. agentinputlab/runner/composite.py +130 -0
  326. agentinputlab/runner/engine.py +285 -0
  327. agentinputlab/runner/runtime_guard.py +268 -0
  328. agentinputlab/runner/worker.py +80 -0
  329. agentinputlab/runtime_bootstrap.py +288 -0
  330. agentinputlab/selectors/__init__.py +196 -0
  331. agentinputlab/selectors/advanced.py +1514 -0
  332. agentinputlab/selectors/artifacts.py +1087 -0
  333. agentinputlab/selectors/base.py +112 -0
  334. agentinputlab/selectors/campaigns.py +1893 -0
  335. agentinputlab/selectors/catalog.py +86 -0
  336. agentinputlab/selectors/discovery.py +1860 -0
  337. agentinputlab/selectors/evaluation.py +1348 -0
  338. agentinputlab/selectors/features.py +383 -0
  339. agentinputlab/selectors/learned.py +502 -0
  340. agentinputlab/selectors/no_op.py +28 -0
  341. agentinputlab/selectors/outcome_table.py +475 -0
  342. agentinputlab/selectors/producer.py +853 -0
  343. agentinputlab/selectors/rules.py +109 -0
  344. agentinputlab/selectors/runtime.py +420 -0
  345. agentinputlab/studies/__init__.py +16 -0
  346. agentinputlab/studies/design.py +165 -0
  347. agentinputlab/studies/paired.py +322 -0
  348. agentinputlab/studio/__init__.py +6 -0
  349. agentinputlab/studio/campaigns.py +819 -0
  350. agentinputlab/studio/engine.py +1408 -0
  351. agentinputlab/studio/model_backends.py +425 -0
  352. agentinputlab/studio/prepared.py +410 -0
  353. agentinputlab/studio/server.py +613 -0
  354. agentinputlab/studio/static/app.js +397 -0
  355. agentinputlab/studio/static/campaigns.html +175 -0
  356. agentinputlab/studio/static/campaigns.js +426 -0
  357. agentinputlab/studio/static/index.html +192 -0
  358. agentinputlab/studio/static/result.html +103 -0
  359. agentinputlab/studio/static/result.js +273 -0
  360. agentinputlab/studio/static/styles.css +467 -0
  361. agentinputlab-0.1.0a1.data/data/share/agentinputlab/CITATION.cff +22 -0
  362. agentinputlab-0.1.0a1.dist-info/METADATA +392 -0
  363. agentinputlab-0.1.0a1.dist-info/RECORD +469 -0
  364. agentinputlab-0.1.0a1.dist-info/WHEEL +5 -0
  365. agentinputlab-0.1.0a1.dist-info/entry_points.txt +6 -0
  366. agentinputlab-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  367. agentinputlab-0.1.0a1.dist-info/licenses/NOTICE +31 -0
  368. agentinputlab-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +94 -0
  369. agentinputlab-0.1.0a1.dist-info/top_level.txt +2 -0
  370. workers/README.md +94 -0
  371. workers/ace/README.md +261 -0
  372. workers/ace/__init__.py +1 -0
  373. workers/ace/compatibility_manifest.json +39 -0
  374. workers/ace/driver.py +1682 -0
  375. workers/acon/README.md +215 -0
  376. workers/acon/__init__.py +5 -0
  377. workers/acon/compatibility_manifest.json +93 -0
  378. workers/acon/driver.py +1859 -0
  379. workers/acon/fitted.py +395 -0
  380. workers/acon/guideline.py +470 -0
  381. workers/acon/guideline_source_manifest.json +20 -0
  382. workers/adaptive_rag/README.md +96 -0
  383. workers/adaptive_rag/__init__.py +15 -0
  384. workers/adaptive_rag/compatibility_manifest.json +61 -0
  385. workers/adaptive_rag/driver.py +1470 -0
  386. workers/adaptive_rag/fixture_runtime.py +163 -0
  387. workers/adaptive_rag/runtime.py +252 -0
  388. workers/ask_or_assume/README.md +124 -0
  389. workers/ask_or_assume/__init__.py +1 -0
  390. workers/ask_or_assume/compatibility_manifest.json +36 -0
  391. workers/ask_or_assume/driver.py +1227 -0
  392. workers/decomp/README.md +133 -0
  393. workers/decomp/UPSTREAM_LICENSE.Apache-2.0.txt +201 -0
  394. workers/decomp/__init__.py +5 -0
  395. workers/decomp/compatibility_manifest.json +205 -0
  396. workers/decomp/driver.py +1015 -0
  397. workers/decomp/live.py +137 -0
  398. workers/decomp/runtime.py +231 -0
  399. workers/infocqr/README.md +106 -0
  400. workers/infocqr/__init__.py +1 -0
  401. workers/infocqr/compatibility_manifest.json +84 -0
  402. workers/infocqr/driver.py +1766 -0
  403. workers/ircot/README.md +167 -0
  404. workers/ircot/__init__.py +7 -0
  405. workers/ircot/compatibility_manifest.json +121 -0
  406. workers/ircot/controlled.py +457 -0
  407. workers/ircot/driver.py +1373 -0
  408. workers/ircot/fixture_bindings.json +39 -0
  409. workers/ircot/fixture_config.json +48 -0
  410. workers/ircot/fixture_prompt.txt +2 -0
  411. workers/ircot/native_qa.py +398 -0
  412. workers/ircot/qa_compatibility_manifest.json +57 -0
  413. workers/ircot/runtime.py +97 -0
  414. workers/llmcompiler/README.md +160 -0
  415. workers/llmcompiler/__init__.py +1 -0
  416. workers/llmcompiler/compatibility_manifest.json +45 -0
  417. workers/llmcompiler/driver.py +1514 -0
  418. workers/llmcompiler/export_prompts.py +74 -0
  419. workers/llmcompiler/live.py +174 -0
  420. workers/llmcompiler/patch_manifest.json +147 -0
  421. workers/mce/README.md +198 -0
  422. workers/mce/__init__.py +29 -0
  423. workers/mce/compatibility_manifest.json +115 -0
  424. workers/mce/driver.py +2585 -0
  425. workers/mce/final_eval.py +173 -0
  426. workers/official_arenas/README.md +25 -0
  427. workers/official_arenas/__init__.py +1 -0
  428. workers/official_arenas/capture_records.py +110 -0
  429. workers/official_arenas/driver.py +619 -0
  430. workers/official_arenas/protocol.py +334 -0
  431. workers/official_arenas/providers/__init__.py +1 -0
  432. workers/official_arenas/providers/_live_history.py +228 -0
  433. workers/official_arenas/providers/_message_compat.py +77 -0
  434. workers/official_arenas/providers/appworld_ul.py +196 -0
  435. workers/official_arenas/providers/fixture.py +112 -0
  436. workers/official_arenas/providers/statebench.py +960 -0
  437. workers/official_arenas/providers/tau3.py +1672 -0
  438. workers/official_arenas/providers/tau_retail.py +586 -0
  439. workers/official_arenas/providers/toolsandbox.py +1321 -0
  440. workers/official_arenas/providers/tooltalk.py +699 -0
  441. workers/official_arenas/retail_capture.py +188 -0
  442. workers/official_arenas/retail_driver.py +275 -0
  443. workers/qrag/README.md +172 -0
  444. workers/qrag/__init__.py +1 -0
  445. workers/qrag/compatibility_manifest.json +64 -0
  446. workers/qrag/driver.py +673 -0
  447. workers/r2ag/README.md +154 -0
  448. workers/r2ag/__init__.py +5 -0
  449. workers/r2ag/compatibility_manifest.json +55 -0
  450. workers/r2ag/driver.py +1287 -0
  451. workers/r2ag/feature_schema.json +8 -0
  452. workers/r2ag/runtime.py +589 -0
  453. workers/recomp/README.md +120 -0
  454. workers/recomp/__init__.py +1 -0
  455. workers/recomp/compatibility_manifest.json +43 -0
  456. workers/recomp/driver.py +1653 -0
  457. workers/reformer/README.md +125 -0
  458. workers/reformer/__init__.py +1 -0
  459. workers/reformer/compatibility_manifest.json +68 -0
  460. workers/reformer/driver.py +1459 -0
  461. workers/reformer/induction.py +457 -0
  462. workers/rrr/README.md +126 -0
  463. workers/rrr/__init__.py +1 -0
  464. workers/rrr/compatibility_manifest.json +57 -0
  465. workers/rrr/driver.py +1133 -0
  466. workers/smartsearch/README.md +331 -0
  467. workers/smartsearch/__init__.py +1 -0
  468. workers/smartsearch/compatibility_manifest.json +41 -0
  469. workers/smartsearch/driver.py +1643 -0
@@ -0,0 +1,1818 @@
1
+ """Protocol-checked analyses for the frozen KDD paper experiment blocks.
2
+
3
+ The general analysis helpers intentionally accept many matched-arm layouts.
4
+ This module adds the stricter paper contracts: five separate three-arm
5
+ family-native runs, one six-arm common-support run, four matched five-arm
6
+ retriever-factorial runs, and the 526-group/14-policy Selector campaign.
7
+
8
+ All entry points require complete, checksum-verified run directories. They
9
+ never execute experiments, mutate artifacts, or authorize paper claims.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+ import random
16
+ from collections import defaultdict
17
+ from collections.abc import Mapping, Sequence
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+ from statistics import fmean
21
+ from typing import Any
22
+
23
+ from agentinputlab.contracts.enums import ProfileKind, SemanticFamily
24
+ from agentinputlab.contracts.errors import ArtifactError, ValidationError
25
+ from agentinputlab.contracts.serialization import content_hash, freeze_mapping
26
+
27
+ from .analysis import (
28
+ _arm_metrics,
29
+ _cluster_bootstrap_interval,
30
+ _cluster_id,
31
+ _collapse_confirmatory_clusters,
32
+ _holm_adjust,
33
+ _linear_slope,
34
+ _load_manifest,
35
+ _paired_cluster_bootstrap_slope_interval,
36
+ _permutation_slope_p_value,
37
+ _pre_action_defect_scores,
38
+ _primary_families,
39
+ _required_record_text,
40
+ _validate_resample_arguments,
41
+ analyze_confirmatory_interactions,
42
+ load_verified_cell_results,
43
+ )
44
+ from .schema import ExperimentManifest, ExperimentStage
45
+
46
+ PAPER_FAMILY_NATIVE_PROTOCOL = "paper-family-native-v1"
47
+ PAPER_COMMON_SUPPORT_PROTOCOL = "paper-common-support-v1"
48
+ PAPER_RETRIEVER_FACTORIAL_PROTOCOL = "paper-retriever-factorial-v1"
49
+ PAPER_SELECTOR_PROTOCOL = "paper-selector-14-policy-v1"
50
+ LEGACY_CONFIRMATORY_PROTOCOL = "legacy-11-arm-v1"
51
+
52
+ _CENSUS_PROTOCOL = "agentinputlab.experiment_census.v1"
53
+ _FAMILY_NATIVE_KIND = "family_native"
54
+ _COMMON_SUPPORT_KIND = "common_support"
55
+ _RETRIEVER_FACTORIAL_KIND = "retriever_factorial_cell"
56
+ _RETRIEVER_FACTORIAL_PRIMARY_KIND = "retriever_factorial_primary_cell"
57
+ _RETRIEVER_FACTORIAL_AUXILIARY_KIND = "retriever_factorial_auxiliary_cell"
58
+ _PAPER_SELECTOR_POLICIES = frozenset(
59
+ {
60
+ "no_op",
61
+ "random_eligible",
62
+ "fixed_reformulate",
63
+ "fixed_decompose",
64
+ "fixed_acquire",
65
+ "fixed_clarify",
66
+ "fixed_curate",
67
+ "best_fixed_global",
68
+ "defect_rule",
69
+ "zero_shot_router",
70
+ "penalized_t_learner",
71
+ "gbdt_t_learner",
72
+ "eligibility_aware_selector",
73
+ "observed_action_oracle",
74
+ }
75
+ )
76
+ _PAPER_RETRIEVERS = ("BAAI/bge-m3", "intfloat/e5-large-v2")
77
+ _FORMAL_PAPER_RETRIEVERS = (
78
+ "bm25",
79
+ "intfloat/e5-large-v2",
80
+ "BAAI/bge-m3",
81
+ "Qwen/Qwen3-Embedding-0.6B",
82
+ )
83
+ _PAPER_TOP_K = (5, 20)
84
+ _SELECTOR_GATE_ARTIFACT = "agentinputlab.paper_selector_claim_gates"
85
+
86
+
87
+ @dataclass(frozen=True, slots=True)
88
+ class _VerifiedPaperRun:
89
+ root: Path
90
+ manifest: ExperimentManifest
91
+ records: tuple[Mapping[str, Any], ...]
92
+ campaign_kind: str
93
+ independent_groups: int
94
+ expected_plan_cells: int
95
+ expected_agent_episodes: int
96
+ cluster_ids: frozenset[str]
97
+
98
+
99
+ def infer_paper_run_protocol(run_dir: str | Path) -> str:
100
+ """Infer a paper protocol only from a frozen manifest census declaration."""
101
+
102
+ manifest = _load_manifest(Path(run_dir).expanduser().resolve())
103
+ census = manifest.metadata.get("experiment_census")
104
+ if not isinstance(census, Mapping):
105
+ return LEGACY_CONFIRMATORY_PROTOCOL
106
+ kind = census.get("campaign_kind")
107
+ if kind == _FAMILY_NATIVE_KIND:
108
+ return PAPER_FAMILY_NATIVE_PROTOCOL
109
+ if kind == _COMMON_SUPPORT_KIND:
110
+ return PAPER_COMMON_SUPPORT_PROTOCOL
111
+ if kind in {
112
+ _RETRIEVER_FACTORIAL_KIND,
113
+ _RETRIEVER_FACTORIAL_PRIMARY_KIND,
114
+ _RETRIEVER_FACTORIAL_AUXILIARY_KIND,
115
+ }:
116
+ return PAPER_RETRIEVER_FACTORIAL_PROTOCOL
117
+ return LEGACY_CONFIRMATORY_PROTOCOL
118
+
119
+
120
+ def analyze_confirmatory_protocol(
121
+ run_dirs: Sequence[str | Path],
122
+ *,
123
+ protocol: str = "auto",
124
+ metric: str = "valid_success",
125
+ algorithm_failure_value: float | None = None,
126
+ bootstrap_samples: int = 2_000,
127
+ permutation_samples: int = 2_000,
128
+ random_seed: int = 2027,
129
+ ) -> Mapping[str, Any]:
130
+ """Route old and paper-v1 confirmatory layouts without weakening either."""
131
+
132
+ roots = _run_roots(run_dirs)
133
+ selected = protocol
134
+ if selected == "auto":
135
+ inferred = {infer_paper_run_protocol(root) for root in roots}
136
+ if len(inferred) != 1:
137
+ raise ValidationError("confirmatory run directories mix incompatible protocols")
138
+ selected = next(iter(inferred))
139
+ if selected == LEGACY_CONFIRMATORY_PROTOCOL:
140
+ if len(roots) != 1:
141
+ raise ValidationError("legacy 11-arm confirmation accepts exactly one run directory")
142
+ return analyze_confirmatory_interactions(
143
+ roots[0],
144
+ metric=metric,
145
+ algorithm_failure_value=algorithm_failure_value,
146
+ bootstrap_samples=bootstrap_samples,
147
+ permutation_samples=permutation_samples,
148
+ random_seed=random_seed,
149
+ )
150
+ if selected == PAPER_FAMILY_NATIVE_PROTOCOL:
151
+ return analyze_paper_family_confirmation(
152
+ roots,
153
+ metric=metric,
154
+ algorithm_failure_value=algorithm_failure_value,
155
+ bootstrap_samples=bootstrap_samples,
156
+ permutation_samples=permutation_samples,
157
+ random_seed=random_seed,
158
+ )
159
+ if selected == PAPER_COMMON_SUPPORT_PROTOCOL:
160
+ if len(roots) != 1:
161
+ raise ValidationError("paper common-support analysis accepts exactly one run directory")
162
+ return analyze_paper_common_support(
163
+ roots[0],
164
+ metric=metric,
165
+ algorithm_failure_value=algorithm_failure_value,
166
+ bootstrap_samples=bootstrap_samples,
167
+ random_seed=random_seed,
168
+ )
169
+ if selected == PAPER_RETRIEVER_FACTORIAL_PROTOCOL:
170
+ analyzer = (
171
+ analyze_paper_dual_arena_retriever_factorial
172
+ if len(roots) == 16
173
+ else analyze_paper_retriever_factorial
174
+ )
175
+ return analyzer(
176
+ roots,
177
+ metric=metric,
178
+ algorithm_failure_value=algorithm_failure_value,
179
+ bootstrap_samples=bootstrap_samples,
180
+ random_seed=random_seed,
181
+ )
182
+ raise ValidationError(f"unsupported confirmatory analysis protocol: {selected!r}")
183
+
184
+
185
+ def analyze_paper_family_confirmation(
186
+ run_dirs: Sequence[str | Path],
187
+ *,
188
+ metric: str,
189
+ algorithm_failure_value: float | None = None,
190
+ bootstrap_samples: int = 2_000,
191
+ permutation_samples: int = 2_000,
192
+ random_seed: int = 2027,
193
+ ) -> Mapping[str, Any]:
194
+ """Analyze the exact five-run, three-arm family-native paper protocol."""
195
+
196
+ _validate_resample_arguments(
197
+ bootstrap_samples=bootstrap_samples,
198
+ permutation_samples=permutation_samples,
199
+ random_seed=random_seed,
200
+ )
201
+ roots = _run_roots(run_dirs)
202
+ if len(roots) != 5:
203
+ raise ValidationError("paper family confirmation requires exactly five run directories")
204
+ runs = tuple(
205
+ _load_and_validate_paper_run(root, expected_kind=_FAMILY_NATIVE_KIND) for root in roots
206
+ )
207
+ by_family: dict[SemanticFamily, _VerifiedPaperRun] = {}
208
+ all_clusters: set[str] = set()
209
+ for run in runs:
210
+ family = _family_native_arm_contract(run.manifest)
211
+ if family in by_family:
212
+ raise ArtifactError(f"duplicate family-native run for {family.value}")
213
+ overlap = all_clusters.intersection(run.cluster_ids)
214
+ if overlap:
215
+ raise ArtifactError(
216
+ "paper family-native runs reuse benchmark/unit groups across families"
217
+ )
218
+ all_clusters.update(run.cluster_ids)
219
+ by_family[family] = run
220
+ expected_families = set(_primary_families())
221
+ if set(by_family) != expected_families:
222
+ missing = sorted(family.value for family in expected_families - set(by_family))
223
+ raise ArtifactError(f"paper family confirmation is missing families: {missing}")
224
+
225
+ observations: dict[str, list[tuple[str, str, float, float]]] = {
226
+ family.value: [] for family in _primary_families()
227
+ }
228
+ model_effects: dict[str, dict[str, list[float]]] = defaultdict(lambda: defaultdict(list))
229
+ for family in _primary_families():
230
+ run = by_family[family]
231
+ baseline_arm_id, treatment_arm_ids = _family_native_arm_ids(run.manifest, family)
232
+ for record in run.records:
233
+ values = _arm_metrics(
234
+ record,
235
+ metric=metric,
236
+ algorithm_failure_value=algorithm_failure_value,
237
+ )
238
+ if values is None: # pragma: no cover - verified all-succeeded invariant
239
+ raise ArtifactError("family-native record is not a completed cell")
240
+ cell = record["cell"]
241
+ cluster_id = _cluster_id(cell)
242
+ model_slot = _required_record_text(cell, "model_slot", label="experiment cell")
243
+ defect = _pre_action_defect_scores(record)[family.value]
244
+ baseline = values[baseline_arm_id]
245
+ effect = fmean(values[arm_id] - baseline for arm_id in treatment_arm_ids)
246
+ observations[family.value].append((cluster_id, model_slot, defect, effect))
247
+ model_effects[model_slot][family.value].append(effect)
248
+
249
+ rng = random.Random(random_seed)
250
+ contrasts: dict[str, Any] = {}
251
+ raw_p_values: dict[str, float] = {}
252
+ for family in _primary_families():
253
+ family_name = family.value
254
+ collapsed = _collapse_confirmatory_clusters(observations[family_name])
255
+ effects = [row[2] for row in collapsed]
256
+ defects = [row[1] for row in collapsed]
257
+ slope = _linear_slope(defects, effects)
258
+ raw_p = _permutation_slope_p_value(
259
+ defects,
260
+ effects,
261
+ observed=slope,
262
+ samples=permutation_samples,
263
+ rng=rng,
264
+ )
265
+ raw_p_values[family_name] = raw_p
266
+ contrasts[family_name] = {
267
+ "cluster_count": len(collapsed),
268
+ "family_effect_mean": fmean(effects),
269
+ "family_effect_cluster_bootstrap_95ci": list(
270
+ _cluster_bootstrap_interval(effects, samples=bootstrap_samples, rng=rng)
271
+ ),
272
+ "matched_defect_slope": slope,
273
+ "matched_defect_slope_cluster_bootstrap_95ci": list(
274
+ _paired_cluster_bootstrap_slope_interval(
275
+ defects,
276
+ effects,
277
+ samples=bootstrap_samples,
278
+ rng=rng,
279
+ )
280
+ ),
281
+ "permutation_p_value_raw": raw_p,
282
+ }
283
+ for family_name, adjusted in _holm_adjust(raw_p_values).items():
284
+ contrasts[family_name]["holm_adjusted_p_value"] = adjusted
285
+
286
+ return {
287
+ "schema_version": 1,
288
+ "analysis": "paper_five_family_native_confirmation",
289
+ "analysis_protocol": PAPER_FAMILY_NATIVE_PROTOCOL,
290
+ "metric": metric,
291
+ "run_count": 5,
292
+ "independent_groups": 3_000,
293
+ "verified_plan_cells": 6_000,
294
+ "verified_agent_episodes": 18_000,
295
+ "arm_layout": "five disjoint runs: NO_OP + two frozen representatives",
296
+ "cluster_definition": "benchmark_id/split/unit_id",
297
+ "replicate_handling": (
298
+ "equal-weight mean over two frozen implementations, model slots, and "
299
+ "seeds within each benchmark/unit cluster"
300
+ ),
301
+ "bootstrap_samples": bootstrap_samples,
302
+ "permutation_samples": permutation_samples,
303
+ "random_seed": random_seed,
304
+ "algorithm_failure_value": algorithm_failure_value,
305
+ "contrasts": contrasts,
306
+ "model_strata": {
307
+ model_slot: {
308
+ family: {
309
+ "count": len(values),
310
+ "mean_effect_vs_no_op": fmean(values),
311
+ }
312
+ for family, values in sorted(families.items())
313
+ }
314
+ for model_slot, families in sorted(model_effects.items())
315
+ },
316
+ "multiplicity": "Holm family-wise correction across the five matched contrasts",
317
+ "paper_claim_authorized": False,
318
+ }
319
+
320
+
321
+ def analyze_paper_common_support(
322
+ run_dir: str | Path,
323
+ *,
324
+ metric: str,
325
+ algorithm_failure_value: float | None = None,
326
+ bootstrap_samples: int = 2_000,
327
+ random_seed: int = 2027,
328
+ ) -> Mapping[str, Any]:
329
+ """Analyze the exact 526-group, six-arm horizontal-comparison block."""
330
+
331
+ _validate_resample_arguments(
332
+ bootstrap_samples=bootstrap_samples,
333
+ permutation_samples=1,
334
+ random_seed=random_seed,
335
+ )
336
+ run = _load_and_validate_paper_run(run_dir, expected_kind=_COMMON_SUPPORT_KIND)
337
+ baseline, arms = _common_support_arm_contract(run.manifest)
338
+ rows_by_cluster: dict[str, list[Mapping[str, float]]] = defaultdict(list)
339
+ model_effects: dict[str, dict[str, list[float]]] = defaultdict(lambda: defaultdict(list))
340
+ for record in run.records:
341
+ values = _arm_metrics(
342
+ record,
343
+ metric=metric,
344
+ algorithm_failure_value=algorithm_failure_value,
345
+ )
346
+ if values is None: # pragma: no cover
347
+ raise ArtifactError("common-support record is not a completed cell")
348
+ cluster_id = _cluster_id(record["cell"])
349
+ model_slot = _required_record_text(
350
+ record["cell"],
351
+ "model_slot",
352
+ label="experiment cell",
353
+ )
354
+ row = {"no_op": values[baseline]}
355
+ for family, arm_id in arms.items():
356
+ row[family.value] = values[arm_id]
357
+ model_effects[model_slot][family.value].append(values[arm_id] - values[baseline])
358
+ rows_by_cluster[cluster_id].append(row)
359
+ collapsed = {
360
+ cluster_id: {
361
+ key: fmean(float(row[key]) for row in rows)
362
+ for key in ("no_op", *(family.value for family in _primary_families()))
363
+ }
364
+ for cluster_id, rows in sorted(rows_by_cluster.items())
365
+ }
366
+ if len(collapsed) != 526:
367
+ raise ArtifactError("paper common-support analysis requires exactly 526 clusters")
368
+
369
+ rng = random.Random(random_seed)
370
+ family_summaries: dict[str, Any] = {}
371
+ for family in _primary_families():
372
+ effects = [row[family.value] - row["no_op"] for row in collapsed.values()]
373
+ family_summaries[family.value] = {
374
+ "cluster_count": len(effects),
375
+ "mean_value": fmean(row[family.value] for row in collapsed.values()),
376
+ "mean_effect_vs_no_op": fmean(effects),
377
+ "cluster_bootstrap_95ci": list(
378
+ _cluster_bootstrap_interval(effects, samples=bootstrap_samples, rng=rng)
379
+ ),
380
+ }
381
+ pairwise: dict[str, Any] = {}
382
+ families = _primary_families()
383
+ for left_index, left in enumerate(families):
384
+ for right in families[left_index + 1 :]:
385
+ effects = [row[left.value] - row[right.value] for row in collapsed.values()]
386
+ pairwise[f"{left.value}_vs_{right.value}"] = {
387
+ "mean_difference": fmean(effects),
388
+ "cluster_bootstrap_95ci": list(
389
+ _cluster_bootstrap_interval(effects, samples=bootstrap_samples, rng=rng)
390
+ ),
391
+ }
392
+ ranking = sorted(
393
+ (
394
+ {
395
+ "family": family.value,
396
+ "mean_value": family_summaries[family.value]["mean_value"],
397
+ }
398
+ for family in families
399
+ ),
400
+ key=lambda row: (-float(row["mean_value"]), str(row["family"])),
401
+ )
402
+ return {
403
+ "schema_version": 1,
404
+ "analysis": "paper_common_support_horizontal_comparison",
405
+ "analysis_protocol": PAPER_COMMON_SUPPORT_PROTOCOL,
406
+ "metric": metric,
407
+ "run_count": 1,
408
+ "independent_groups": 526,
409
+ "verified_plan_cells": 1_052,
410
+ "verified_agent_episodes": 6_312,
411
+ "arm_layout": "NO_OP + one frozen representative per family",
412
+ "minimum_groups_for_ranking": 400,
413
+ "ranking_allowed": len(collapsed) >= 400,
414
+ "family_summaries": family_summaries,
415
+ "pairwise_family_differences": pairwise,
416
+ "descriptive_ranking": ranking,
417
+ "model_strata": {
418
+ model_slot: {
419
+ family: {
420
+ "count": len(values),
421
+ "mean_effect_vs_no_op": fmean(values),
422
+ }
423
+ for family, values in sorted(by_family.items())
424
+ }
425
+ for model_slot, by_family in sorted(model_effects.items())
426
+ },
427
+ "scope_note": (
428
+ "Bounded common-support population; family-native effects remain primary. "
429
+ "No multiplicity-adjusted superiority claim is authorized here."
430
+ ),
431
+ "bootstrap_samples": bootstrap_samples,
432
+ "random_seed": random_seed,
433
+ "algorithm_failure_value": algorithm_failure_value,
434
+ "paper_claim_authorized": False,
435
+ }
436
+
437
+
438
+ def analyze_paper_retriever_factorial(
439
+ run_dirs: Sequence[str | Path],
440
+ *,
441
+ metric: str,
442
+ algorithm_failure_value: float | None = None,
443
+ bootstrap_samples: int = 2_000,
444
+ random_seed: int = 2027,
445
+ ) -> Mapping[str, Any]:
446
+ """Analyze four matched retriever-by-top-k cells with shared unit IDs."""
447
+
448
+ _validate_resample_arguments(
449
+ bootstrap_samples=bootstrap_samples,
450
+ permutation_samples=1,
451
+ random_seed=random_seed,
452
+ )
453
+ roots = _run_roots(run_dirs)
454
+ if len(roots) != 4:
455
+ raise ValidationError("paper retriever factorial requires exactly four run directories")
456
+ runs = tuple(
457
+ _load_and_validate_paper_run(root, expected_kind=_RETRIEVER_FACTORIAL_KIND)
458
+ for root in roots
459
+ )
460
+ by_cell: dict[tuple[str, int], _VerifiedPaperRun] = {}
461
+ reference_arms: tuple[tuple[str, str, str], ...] | None = None
462
+ reference_clusters: frozenset[str] | None = None
463
+ reference_targets: tuple[tuple[str, str], ...] | None = None
464
+ reference_models: tuple[str, ...] | None = None
465
+ reference_seeds: tuple[int, ...] | None = None
466
+ for run in runs:
467
+ key = _retriever_dependency_cell(run.manifest)
468
+ if key in by_cell:
469
+ raise ArtifactError(f"duplicate retriever-factorial dependency cell: {key}")
470
+ _retriever_arm_contract(run.manifest)
471
+ arm_identity = tuple(
472
+ (arm.arm_id, arm.release_id, arm.family.value) for arm in run.manifest.arms
473
+ )
474
+ targets = tuple((target.cell_id, target.split) for target in run.manifest.benchmarks)
475
+ if reference_arms is None:
476
+ reference_arms = arm_identity
477
+ reference_clusters = run.cluster_ids
478
+ reference_targets = targets
479
+ reference_models = run.manifest.model_slots
480
+ reference_seeds = run.manifest.seeds
481
+ elif (
482
+ arm_identity != reference_arms
483
+ or run.cluster_ids != reference_clusters
484
+ or targets != reference_targets
485
+ or run.manifest.model_slots != reference_models
486
+ or run.manifest.seeds != reference_seeds
487
+ ):
488
+ raise ArtifactError(
489
+ "retriever-factorial cells must share arms, groups, targets, model slots, and seeds"
490
+ )
491
+ by_cell[key] = run
492
+ expected_cells = {
493
+ (retriever, top_k) for retriever in _PAPER_RETRIEVERS for top_k in _PAPER_TOP_K
494
+ }
495
+ if set(by_cell) != expected_cells:
496
+ raise ArtifactError("retriever-factorial runs do not form the frozen 2x2 dependency grid")
497
+ dependency_receipts = {key: _retriever_runtime_receipt(run) for key, run in by_cell.items()}
498
+ dependency_balance = _validate_retriever_factorial_balance(dependency_receipts)
499
+
500
+ effects: dict[tuple[str, int], dict[str, dict[str, float]]] = {}
501
+ cell_summaries: dict[str, Any] = {}
502
+ rng = random.Random(random_seed)
503
+ for key in sorted(by_cell):
504
+ run = by_cell[key]
505
+ baseline, treatment_arms = _retriever_arm_contract(run.manifest)
506
+ by_cluster: dict[str, dict[str, float]] = {}
507
+ for record in run.records:
508
+ values = _arm_metrics(
509
+ record,
510
+ metric=metric,
511
+ algorithm_failure_value=algorithm_failure_value,
512
+ )
513
+ if values is None: # pragma: no cover
514
+ raise ArtifactError("retriever-factorial record is not a completed cell")
515
+ cluster_id = _cluster_id(record["cell"])
516
+ by_cluster[cluster_id] = {
517
+ arm_id: values[arm_id] - values[baseline] for arm_id in treatment_arms
518
+ }
519
+ effects[key] = by_cluster
520
+ label = f"{key[0]}::k{key[1]}"
521
+ cell_summaries[label] = {
522
+ "retriever_id": key[0],
523
+ "top_k": key[1],
524
+ "independent_groups": len(by_cluster),
525
+ "arms": {
526
+ arm_id: {
527
+ "mean_effect_vs_no_op": fmean(row[arm_id] for row in by_cluster.values()),
528
+ "cluster_bootstrap_95ci": list(
529
+ _cluster_bootstrap_interval(
530
+ [row[arm_id] for row in by_cluster.values()],
531
+ samples=bootstrap_samples,
532
+ rng=rng,
533
+ )
534
+ ),
535
+ }
536
+ for arm_id in treatment_arms
537
+ },
538
+ }
539
+
540
+ assert reference_clusters is not None # established by four non-empty runs
541
+ assert reference_arms is not None
542
+ treatment_arm_ids = tuple(item[0] for item in reference_arms if item[2] != "no_op")
543
+ interactions: dict[str, Any] = {}
544
+ bge, e5 = _PAPER_RETRIEVERS
545
+ k5, k20 = _PAPER_TOP_K
546
+ for arm_id in treatment_arm_ids:
547
+ retriever_contrast = []
548
+ top_k_contrast = []
549
+ joint_contrast = []
550
+ for cluster_id in sorted(reference_clusters):
551
+ bge_k5 = effects[(bge, k5)][cluster_id][arm_id]
552
+ bge_k20 = effects[(bge, k20)][cluster_id][arm_id]
553
+ e5_k5 = effects[(e5, k5)][cluster_id][arm_id]
554
+ e5_k20 = effects[(e5, k20)][cluster_id][arm_id]
555
+ retriever_contrast.append((bge_k5 + bge_k20 - e5_k5 - e5_k20) / 2.0)
556
+ top_k_contrast.append((bge_k20 + e5_k20 - bge_k5 - e5_k5) / 2.0)
557
+ joint_contrast.append((bge_k20 - bge_k5) - (e5_k20 - e5_k5))
558
+ interactions[arm_id] = {
559
+ "retriever_bge_minus_e5": _effect_summary(
560
+ retriever_contrast,
561
+ bootstrap_samples=bootstrap_samples,
562
+ rng=rng,
563
+ ),
564
+ "top_k_20_minus_5": _effect_summary(
565
+ top_k_contrast,
566
+ bootstrap_samples=bootstrap_samples,
567
+ rng=rng,
568
+ ),
569
+ "retriever_by_top_k": _effect_summary(
570
+ joint_contrast,
571
+ bootstrap_samples=bootstrap_samples,
572
+ rng=rng,
573
+ ),
574
+ }
575
+ return {
576
+ "schema_version": 1,
577
+ "analysis": "paper_retriever_by_top_k_factorial",
578
+ "analysis_protocol": PAPER_RETRIEVER_FACTORIAL_PROTOCOL,
579
+ "metric": metric,
580
+ "run_count": 4,
581
+ "independent_groups": 240,
582
+ "verified_plan_cells": 960,
583
+ "verified_agent_episodes": 4_800,
584
+ "dependency_cells": cell_summaries,
585
+ "dependency_balance": dependency_balance,
586
+ "interactions": interactions,
587
+ "cluster_definition": "same benchmark_id/split/unit_id replayed in all four cells",
588
+ "bootstrap_samples": bootstrap_samples,
589
+ "random_seed": random_seed,
590
+ "algorithm_failure_value": algorithm_failure_value,
591
+ "paper_claim_authorized": False,
592
+ }
593
+
594
+
595
+ def analyze_paper_dual_arena_retriever_factorial(
596
+ run_dirs: Sequence[str | Path],
597
+ *,
598
+ metric: str,
599
+ algorithm_failure_value: float | None = None,
600
+ bootstrap_samples: int = 2_000,
601
+ random_seed: int = 2027,
602
+ ) -> Mapping[str, Any]:
603
+ """Analyze the formal 4-retriever x 2-top-k design in τ³ and BRIGHT.
604
+
605
+ τ³ is the primary estimand. BRIGHT is reported separately as a directional
606
+ auxiliary validation; its observations are never pooled with τ³.
607
+ """
608
+
609
+ _validate_resample_arguments(
610
+ bootstrap_samples=bootstrap_samples,
611
+ permutation_samples=1,
612
+ random_seed=random_seed,
613
+ )
614
+ roots = _run_roots(run_dirs)
615
+ if len(roots) != 16:
616
+ raise ValidationError("formal retriever factorial requires exactly sixteen run directories")
617
+ by_role: dict[str, list[_VerifiedPaperRun]] = {"primary": [], "auxiliary": []}
618
+ for root in roots:
619
+ manifest = _load_manifest(root)
620
+ dependency = manifest.metadata.get("dependency_cell")
621
+ if not isinstance(dependency, Mapping):
622
+ raise ArtifactError("formal retriever-factorial run lacks dependency metadata")
623
+ role = dependency.get("arena_role")
624
+ if role not in by_role:
625
+ raise ArtifactError("formal retriever-factorial arena role is invalid")
626
+ expected_kind = (
627
+ _RETRIEVER_FACTORIAL_PRIMARY_KIND
628
+ if role == "primary"
629
+ else _RETRIEVER_FACTORIAL_AUXILIARY_KIND
630
+ )
631
+ by_role[str(role)].append(_load_and_validate_paper_run(root, expected_kind=expected_kind))
632
+ if any(len(runs) != 8 for runs in by_role.values()):
633
+ raise ArtifactError("formal retriever factorial requires eight cells in each arena")
634
+
635
+ rng = random.Random(random_seed)
636
+ analyses = {
637
+ role: _analyze_formal_retriever_arena(
638
+ runs,
639
+ role=role,
640
+ metric=metric,
641
+ algorithm_failure_value=algorithm_failure_value,
642
+ bootstrap_samples=bootstrap_samples,
643
+ rng=rng,
644
+ )
645
+ for role, runs in by_role.items()
646
+ }
647
+ return {
648
+ "schema_version": 2,
649
+ "analysis": "paper_dual_arena_retriever_by_top_k_factorial",
650
+ "analysis_protocol": PAPER_RETRIEVER_FACTORIAL_PROTOCOL,
651
+ "metric": metric,
652
+ "run_count": 16,
653
+ "primary": analyses["primary"],
654
+ "auxiliary": analyses["auxiliary"],
655
+ "claim_order": [
656
+ "tau3_primary_retriever_factorial",
657
+ "bright_auxiliary_directional_validation",
658
+ ],
659
+ "cross_arena_pooling": False,
660
+ "bootstrap_samples": bootstrap_samples,
661
+ "random_seed": random_seed,
662
+ "algorithm_failure_value": algorithm_failure_value,
663
+ "paper_claim_authorized": False,
664
+ }
665
+
666
+
667
+ def _analyze_formal_retriever_arena(
668
+ runs: Sequence[_VerifiedPaperRun],
669
+ *,
670
+ role: str,
671
+ metric: str,
672
+ algorithm_failure_value: float | None,
673
+ bootstrap_samples: int,
674
+ rng: random.Random,
675
+ ) -> Mapping[str, Any]:
676
+ by_cell: dict[tuple[str, int], _VerifiedPaperRun] = {}
677
+ reference: _VerifiedPaperRun | None = None
678
+ for run in runs:
679
+ key = _retriever_dependency_cell(run.manifest, formal=True)
680
+ if key in by_cell:
681
+ raise ArtifactError(f"duplicate formal retriever-factorial cell: {key}")
682
+ _retriever_arm_contract(run.manifest)
683
+ if reference is None:
684
+ reference = run
685
+ elif (
686
+ tuple((arm.arm_id, arm.release_id, arm.family.value) for arm in run.manifest.arms)
687
+ != tuple(
688
+ (arm.arm_id, arm.release_id, arm.family.value) for arm in reference.manifest.arms
689
+ )
690
+ or run.cluster_ids != reference.cluster_ids
691
+ or run.manifest.model_slots != reference.manifest.model_slots
692
+ or run.manifest.seeds != reference.manifest.seeds
693
+ or tuple((cell.cell_id, cell.split) for cell in run.manifest.benchmarks)
694
+ != tuple((cell.cell_id, cell.split) for cell in reference.manifest.benchmarks)
695
+ ):
696
+ raise ArtifactError(
697
+ "formal retriever-factorial cells must share arms, groups, targets, models, and seeds"
698
+ )
699
+ by_cell[key] = run
700
+ expected = {
701
+ (retriever, top_k) for retriever in _FORMAL_PAPER_RETRIEVERS for top_k in _PAPER_TOP_K
702
+ }
703
+ if set(by_cell) != expected:
704
+ raise ArtifactError("formal retriever-factorial cells do not form the frozen 4x2 grid")
705
+ dependency_balance = _validate_formal_retriever_factorial_balance(
706
+ {key: _formal_retriever_runtime_receipt(run) for key, run in by_cell.items()}
707
+ )
708
+ assert reference is not None
709
+ baseline, treatment_arms = _retriever_arm_contract(reference.manifest)
710
+ effects: dict[tuple[str, int], dict[str, dict[str, dict[str, float]]]] = {}
711
+ cell_summaries: dict[str, Any] = {}
712
+ for key, run in sorted(by_cell.items()):
713
+ by_group: dict[str, dict[str, dict[str, float]]] = defaultdict(dict)
714
+ for record in run.records:
715
+ values = _arm_metrics(
716
+ record,
717
+ metric=metric,
718
+ algorithm_failure_value=algorithm_failure_value,
719
+ )
720
+ if values is None: # pragma: no cover
721
+ raise ArtifactError("formal retriever-factorial record is incomplete")
722
+ cell = record["cell"]
723
+ group_id = _cluster_id(cell)
724
+ model_slot = _required_record_text(cell, "model_slot", label="experiment cell")
725
+ by_group[group_id][model_slot] = {
726
+ arm_id: values[arm_id] - values[baseline] for arm_id in treatment_arms
727
+ }
728
+ if any(set(models) != set(run.manifest.model_slots) for models in by_group.values()):
729
+ raise ArtifactError("formal retriever cell lacks the exact two-model group grid")
730
+ effects[key] = by_group
731
+ cell_summaries[f"{key[0]}::k{key[1]}"] = {
732
+ "retriever_id": key[0],
733
+ "top_k": key[1],
734
+ "independent_groups": len(by_group),
735
+ "model_slots": list(run.manifest.model_slots),
736
+ "arms": {
737
+ arm_id: _effect_summary(
738
+ [
739
+ fmean(model_values[arm_id] for model_values in models.values())
740
+ for models in by_group.values()
741
+ ],
742
+ bootstrap_samples=bootstrap_samples,
743
+ rng=rng,
744
+ )
745
+ for arm_id in treatment_arms
746
+ },
747
+ }
748
+
749
+ interactions: dict[str, Any] = {}
750
+ reference_retriever = "BAAI/bge-m3"
751
+ for arm_id in treatment_arms:
752
+ top_k_contrast: list[float] = []
753
+ retriever_contrasts: dict[str, list[float]] = {
754
+ retriever: []
755
+ for retriever in _FORMAL_PAPER_RETRIEVERS
756
+ if retriever != reference_retriever
757
+ }
758
+ for group_id in sorted(reference.cluster_ids):
759
+ group_top_k_contrasts: list[float] = []
760
+ reference_mean: float | None = None
761
+ for retriever in _FORMAL_PAPER_RETRIEVERS:
762
+ low = fmean(values[arm_id] for values in effects[(retriever, 5)][group_id].values())
763
+ high = fmean(
764
+ values[arm_id] for values in effects[(retriever, 20)][group_id].values()
765
+ )
766
+ if retriever == reference_retriever:
767
+ reference_mean = (low + high) / 2.0
768
+ group_top_k_contrasts.append(high - low)
769
+ top_k_contrast.append(fmean(group_top_k_contrasts))
770
+ if reference_mean is None: # pragma: no cover - frozen grid contains BGE-M3
771
+ raise ArtifactError("formal retriever grid lacks its BGE-M3 reference")
772
+ for retriever in retriever_contrasts:
773
+ candidate = fmean(
774
+ fmean(values[arm_id] for values in effects[(retriever, k)][group_id].values())
775
+ for k in _PAPER_TOP_K
776
+ )
777
+ retriever_contrasts[retriever].append(candidate - reference_mean)
778
+ interactions[arm_id] = {
779
+ "top_k_20_minus_5": _effect_summary(
780
+ top_k_contrast,
781
+ bootstrap_samples=bootstrap_samples,
782
+ rng=rng,
783
+ ),
784
+ "retriever_minus_bge_m3": {
785
+ retriever: _effect_summary(
786
+ values,
787
+ bootstrap_samples=bootstrap_samples,
788
+ rng=rng,
789
+ )
790
+ for retriever, values in retriever_contrasts.items()
791
+ },
792
+ }
793
+ return {
794
+ "arena_role": role,
795
+ "benchmark_id": reference.manifest.benchmarks[0].cell_id,
796
+ "run_count": 8,
797
+ "independent_groups": reference.independent_groups,
798
+ "verified_plan_cells": sum(run.expected_plan_cells for run in runs),
799
+ "verified_agent_episodes": sum(run.expected_agent_episodes for run in runs),
800
+ "dependency_cells": cell_summaries,
801
+ "dependency_balance": dependency_balance,
802
+ "interactions": interactions,
803
+ "cluster_definition": "same benchmark/split/unit replayed across all eight cells",
804
+ }
805
+
806
+
807
+ def _formal_retriever_runtime_receipt(run: _VerifiedPaperRun) -> Mapping[str, Any]:
808
+ expected_arm_ids = {arm.arm_id for arm in run.manifest.arms}
809
+ required_slots = {"backend", "retriever", "downstream_predictor", "seed"}
810
+ by_model: dict[str, dict[str, str]] = {}
811
+ dataset_identity: str | None = None
812
+ for record in run.records:
813
+ cell = record.get("cell")
814
+ result = record.get("result")
815
+ payload = result.get("payload") if isinstance(result, Mapping) else None
816
+ if not isinstance(cell, Mapping) or not isinstance(payload, Mapping):
817
+ raise ArtifactError("formal retriever result lacks cell or payload")
818
+ model_slot = _required_record_text(cell, "model_slot", label="experiment cell")
819
+ observed = payload.get("observed_dependency_hashes")
820
+ if not isinstance(observed, Mapping) or set(observed) != expected_arm_ids:
821
+ raise ArtifactError("formal retriever result lacks exact arm dependency receipts")
822
+ parsed_by_arm: list[dict[str, str]] = []
823
+ for raw in observed.values():
824
+ if not isinstance(raw, Mapping):
825
+ raise ArtifactError("formal retriever arm dependency receipt is invalid")
826
+ parsed = {
827
+ str(slot): str(digest)
828
+ for slot, digest in raw.items()
829
+ if isinstance(slot, str) and isinstance(digest, str)
830
+ }
831
+ if (
832
+ set(parsed) != set(raw)
833
+ or not required_slots <= set(parsed)
834
+ or any(
835
+ len(digest) != 64
836
+ or any(character not in "0123456789abcdef" for character in digest)
837
+ for digest in parsed.values()
838
+ )
839
+ ):
840
+ raise ArtifactError("formal retriever dependency fingerprints are invalid")
841
+ parsed_by_arm.append(parsed)
842
+ first = parsed_by_arm[0]
843
+ if any(value != first for value in parsed_by_arm[1:]):
844
+ raise ArtifactError("formal retriever nuisance dependencies differ across arms")
845
+ previous = by_model.setdefault(model_slot, first)
846
+ if previous != first:
847
+ raise ArtifactError("formal retriever dependencies drift across groups")
848
+ current_dataset = payload.get("dataset_identity_sha256")
849
+ if (
850
+ not isinstance(current_dataset, str)
851
+ or len(current_dataset) != 64
852
+ or any(character not in "0123456789abcdef" for character in current_dataset)
853
+ ):
854
+ raise ArtifactError("formal retriever result lacks dataset identity SHA-256")
855
+ if dataset_identity is None:
856
+ dataset_identity = current_dataset
857
+ elif dataset_identity != current_dataset:
858
+ raise ArtifactError("formal retriever dataset identity drifts across groups/models")
859
+ if set(by_model) != set(run.manifest.model_slots) or dataset_identity is None:
860
+ raise ArtifactError("formal retriever receipt lacks the exact model grid")
861
+ return {"dependencies_by_model": by_model, "dataset_identity_sha256": dataset_identity}
862
+
863
+
864
+ def _validate_formal_retriever_factorial_balance(
865
+ receipts: Mapping[tuple[str, int], Mapping[str, Any]],
866
+ ) -> Mapping[str, Any]:
867
+ dataset_hashes = {str(value["dataset_identity_sha256"]) for value in receipts.values()}
868
+ if len(dataset_hashes) != 1:
869
+ raise ArtifactError("formal retriever cells use different dataset/corpus identities")
870
+ models = set.intersection(
871
+ *(
872
+ set(value["dependencies_by_model"])
873
+ for value in receipts.values()
874
+ if isinstance(value["dependencies_by_model"], Mapping)
875
+ )
876
+ )
877
+ if not models or any(
878
+ set(value["dependencies_by_model"]) != models for value in receipts.values()
879
+ ):
880
+ raise ArtifactError("formal retriever cells expose different model slots")
881
+ varied_slots = {"retriever", "downstream_predictor"}
882
+ summaries: dict[str, Any] = {}
883
+ for model_slot in sorted(models):
884
+ dependency_maps = {
885
+ key: dict(value["dependencies_by_model"][model_slot]) for key, value in receipts.items()
886
+ }
887
+ slot_sets = {frozenset(value) for value in dependency_maps.values()}
888
+ if len(slot_sets) != 1:
889
+ raise ArtifactError("formal retriever cells expose different dependency slots")
890
+ slots = next(iter(slot_sets))
891
+ for slot in slots - varied_slots:
892
+ if len({value[slot] for value in dependency_maps.values()}) != 1:
893
+ raise ArtifactError(
894
+ f"formal retriever non-varied dependency {slot!r} differs across cells"
895
+ )
896
+ retriever_hashes = {}
897
+ for retriever in _FORMAL_PAPER_RETRIEVERS:
898
+ values = {dependency_maps[(retriever, top_k)]["retriever"] for top_k in _PAPER_TOP_K}
899
+ if len(values) != 1:
900
+ raise ArtifactError("formal retriever identity changes across top-k")
901
+ retriever_hashes[retriever] = next(iter(values))
902
+ if len(set(retriever_hashes.values())) != len(_FORMAL_PAPER_RETRIEVERS):
903
+ raise ArtifactError("formal retriever identities are not distinct")
904
+ predictor_hashes = {}
905
+ for top_k in _PAPER_TOP_K:
906
+ values = {
907
+ dependency_maps[(retriever, top_k)]["downstream_predictor"]
908
+ for retriever in _FORMAL_PAPER_RETRIEVERS
909
+ }
910
+ if len(values) != 1:
911
+ raise ArtifactError("formal downstream predictor changes across retrievers")
912
+ predictor_hashes[str(top_k)] = next(iter(values))
913
+ if len(set(predictor_hashes.values())) != len(_PAPER_TOP_K):
914
+ raise ArtifactError("formal top-k identities are not distinct")
915
+ summaries[model_slot] = {
916
+ "shared_dependency_slots": sorted(slots - varied_slots),
917
+ "retriever_slot_hashes": retriever_hashes,
918
+ "predictor_slot_hashes_by_top_k": predictor_hashes,
919
+ }
920
+ return {
921
+ "dataset_identity_sha256": next(iter(dataset_hashes)),
922
+ "models": summaries,
923
+ "balance_verified": True,
924
+ }
925
+
926
+
927
+ def validate_paper_selector_analysis_contract(
928
+ campaign: Mapping[str, Any],
929
+ analysis: Mapping[str, Any],
930
+ ) -> Mapping[str, Any]:
931
+ """Require the exact 14-policy, 526-group three-arena Selector analysis."""
932
+
933
+ _validate_paper_selector_campaign(campaign)
934
+ metadata = campaign["metadata"]
935
+ summaries = analysis.get("policy_summaries")
936
+ if (
937
+ analysis.get("analysis") != "selector_multi_policy_campaign"
938
+ or analysis.get("campaign_hash") != campaign.get("campaign_hash")
939
+ or analysis.get("primary_metric") != "valid_success"
940
+ or analysis.get("primary_metric") != campaign.get("primary_metric")
941
+ or analysis.get("evaluation_split") != campaign.get("evaluation_split")
942
+ or analysis.get("independent_groups") != 526
943
+ or analysis.get("common_support_groups") != 526
944
+ or not isinstance(summaries, Mapping)
945
+ or set(summaries) != _PAPER_SELECTOR_POLICIES
946
+ or any(
947
+ not isinstance(summary, Mapping) or summary.get("independent_groups") != 526
948
+ for summary in summaries.values()
949
+ )
950
+ or analysis.get("observed_action_oracle_used_for_training") is not False
951
+ or analysis.get("observed_action_oracle_used_for_inference") is not False
952
+ or analysis.get("paper_claim_authorized") is not False
953
+ ):
954
+ raise ValidationError("Selector analysis output differs from the frozen paper protocol")
955
+ return {
956
+ "analysis_protocol": PAPER_SELECTOR_PROTOCOL,
957
+ "independent_groups": metadata["required_independent_groups"],
958
+ "common_support_groups": 526,
959
+ "partial_support_groups": 0,
960
+ "policy_count": 14,
961
+ "policy_rows": 7_364,
962
+ "paper_claim_authorized": False,
963
+ }
964
+
965
+
966
+ def build_paper_selector_claim_gates(
967
+ campaign: Mapping[str, Any],
968
+ *,
969
+ alpha: float,
970
+ valid_success_superiority_margin: float,
971
+ best_fixed_noninferiority_margin: float,
972
+ hard_harm_noninferiority_margin: float,
973
+ safety_comparator_policy_id: str,
974
+ minimum_intervention_coverage: float,
975
+ maximum_intervention_coverage: float,
976
+ maximum_mean_cost: float,
977
+ bootstrap_samples: int,
978
+ permutation_samples: int,
979
+ random_seed: int,
980
+ ) -> Mapping[str, Any]:
981
+ """Freeze scientific claim gates before untouched Selector outcomes exist."""
982
+
983
+ _validate_paper_selector_campaign(campaign)
984
+ campaign_hash = campaign.get("campaign_hash")
985
+ if not isinstance(campaign_hash, str) or len(campaign_hash) != 64:
986
+ raise ValidationError("claim gates require an exact Selector campaign hash")
987
+ base = {
988
+ "schema_version": 1,
989
+ "artifact": _SELECTOR_GATE_ARTIFACT,
990
+ "stage": "frozen_before_test_outcomes",
991
+ "campaign_manifest_hash": campaign_hash,
992
+ "primary_policy_id": "eligibility_aware_selector",
993
+ "no_op_policy_id": "no_op",
994
+ "best_fixed_policy_id": "best_fixed_global",
995
+ "safety_comparator_policy_id": safety_comparator_policy_id,
996
+ "alpha": alpha,
997
+ "valid_success_superiority_margin": valid_success_superiority_margin,
998
+ "best_fixed_noninferiority_margin": best_fixed_noninferiority_margin,
999
+ "hard_harm_noninferiority_margin": hard_harm_noninferiority_margin,
1000
+ "minimum_intervention_coverage": minimum_intervention_coverage,
1001
+ "maximum_intervention_coverage": maximum_intervention_coverage,
1002
+ "maximum_mean_cost": maximum_mean_cost,
1003
+ "bootstrap_samples": bootstrap_samples,
1004
+ "permutation_samples": permutation_samples,
1005
+ "random_seed": random_seed,
1006
+ "experiments_executed": False,
1007
+ }
1008
+ candidate = {**base, "gate_hash": content_hash(base)}
1009
+ return freeze_mapping(validate_paper_selector_claim_gates(campaign, candidate))
1010
+
1011
+
1012
+ def validate_paper_selector_claim_gates(
1013
+ campaign: Mapping[str, Any],
1014
+ gates: Mapping[str, Any],
1015
+ ) -> Mapping[str, Any]:
1016
+ """Validate an exact campaign-bound Selector claim hierarchy."""
1017
+
1018
+ _validate_paper_selector_campaign(campaign)
1019
+ campaign_hash = campaign.get("campaign_hash")
1020
+ if not isinstance(campaign_hash, str) or len(campaign_hash) != 64:
1021
+ raise ValidationError("claim gates require an exact Selector campaign hash")
1022
+ expected_keys = {
1023
+ "schema_version",
1024
+ "artifact",
1025
+ "stage",
1026
+ "campaign_manifest_hash",
1027
+ "primary_policy_id",
1028
+ "no_op_policy_id",
1029
+ "best_fixed_policy_id",
1030
+ "safety_comparator_policy_id",
1031
+ "alpha",
1032
+ "valid_success_superiority_margin",
1033
+ "best_fixed_noninferiority_margin",
1034
+ "hard_harm_noninferiority_margin",
1035
+ "minimum_intervention_coverage",
1036
+ "maximum_intervention_coverage",
1037
+ "maximum_mean_cost",
1038
+ "bootstrap_samples",
1039
+ "permutation_samples",
1040
+ "random_seed",
1041
+ "experiments_executed",
1042
+ "gate_hash",
1043
+ }
1044
+ if set(gates) != expected_keys:
1045
+ raise ValidationError("paper Selector claim-gate schema is invalid")
1046
+ if (
1047
+ gates["schema_version"] != 1
1048
+ or gates["artifact"] != _SELECTOR_GATE_ARTIFACT
1049
+ or gates["stage"] != "frozen_before_test_outcomes"
1050
+ or gates["experiments_executed"] is not False
1051
+ or gates["campaign_manifest_hash"] != campaign_hash
1052
+ or gates["primary_policy_id"] != "eligibility_aware_selector"
1053
+ or gates["no_op_policy_id"] != "no_op"
1054
+ or gates["best_fixed_policy_id"] != "best_fixed_global"
1055
+ or gates["safety_comparator_policy_id"] not in {"no_op", "best_fixed_global"}
1056
+ ):
1057
+ raise ValidationError("paper Selector claim gates differ from the frozen campaign")
1058
+ alpha = _bounded_protocol_number(gates["alpha"], lower=0.0, upper=0.5, label="alpha")
1059
+ if not math.isclose(alpha, 0.05, abs_tol=1e-12):
1060
+ raise ValidationError("paper Selector protocol fixes alpha at 0.05")
1061
+ for key in (
1062
+ "valid_success_superiority_margin",
1063
+ "best_fixed_noninferiority_margin",
1064
+ "hard_harm_noninferiority_margin",
1065
+ "minimum_intervention_coverage",
1066
+ "maximum_intervention_coverage",
1067
+ ):
1068
+ _bounded_protocol_number(gates[key], lower=0.0, upper=1.0, label=key)
1069
+ minimum_coverage = float(gates["minimum_intervention_coverage"])
1070
+ maximum_coverage = float(gates["maximum_intervention_coverage"])
1071
+ if minimum_coverage > maximum_coverage:
1072
+ raise ValidationError("paper Selector minimum coverage exceeds maximum coverage")
1073
+ _bounded_protocol_number(
1074
+ gates["maximum_mean_cost"],
1075
+ lower=0.0,
1076
+ upper=None,
1077
+ label="maximum_mean_cost",
1078
+ )
1079
+ for key, minimum in (
1080
+ ("bootstrap_samples", 100),
1081
+ ("permutation_samples", 100),
1082
+ ("random_seed", 0),
1083
+ ):
1084
+ value = gates[key]
1085
+ if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
1086
+ raise ValidationError(f"paper Selector {key} must be an integer >= {minimum}")
1087
+ gate_hash = gates["gate_hash"]
1088
+ if (
1089
+ not isinstance(gate_hash, str)
1090
+ or len(gate_hash) != 64
1091
+ or content_hash({key: value for key, value in gates.items() if key != "gate_hash"})
1092
+ != gate_hash
1093
+ ):
1094
+ raise ValidationError("paper Selector claim-gate hash differs from its content")
1095
+ return freeze_mapping(gates)
1096
+
1097
+
1098
+ def analyze_paper_selector_claim_hierarchy(
1099
+ campaign: Mapping[str, Any],
1100
+ verified_results: Mapping[str, Any],
1101
+ base_analysis: Mapping[str, Any],
1102
+ claim_gates: Mapping[str, Any],
1103
+ *,
1104
+ bootstrap_samples: int | None = None,
1105
+ ) -> Mapping[str, Any]:
1106
+ """Run the preregistered paired Selector claim hierarchy."""
1107
+
1108
+ protocol_receipt = validate_paper_selector_analysis_contract(campaign, base_analysis)
1109
+ gates = validate_paper_selector_claim_gates(campaign, claim_gates)
1110
+ if (
1111
+ verified_results.get("artifact") != "agentinputlab.selector_campaign_results"
1112
+ or verified_results.get("stage") != "verified"
1113
+ or verified_results.get("data_status") != "VERIFIED_REAL_RESULTS"
1114
+ or verified_results.get("is_synthetic") is not False
1115
+ or verified_results.get("campaign_manifest_hash") != campaign.get("campaign_hash")
1116
+ ):
1117
+ raise ValidationError(
1118
+ "paper Selector claim hierarchy requires campaign-bound verified real results"
1119
+ )
1120
+ frozen_bootstrap_samples = int(gates["bootstrap_samples"])
1121
+ if bootstrap_samples is not None and bootstrap_samples != frozen_bootstrap_samples:
1122
+ raise ValidationError(
1123
+ "paper Selector bootstrap_samples differs from the frozen claim gates"
1124
+ )
1125
+ if base_analysis.get("bootstrap_samples") != frozen_bootstrap_samples:
1126
+ raise ValidationError(
1127
+ "Selector campaign analysis bootstrap count differs from the claim gates"
1128
+ )
1129
+ if base_analysis.get("random_seed") != gates["random_seed"]:
1130
+ raise ValidationError("Selector campaign analysis seed differs from the claim gates")
1131
+ _validate_resample_arguments(
1132
+ bootstrap_samples=frozen_bootstrap_samples,
1133
+ permutation_samples=int(gates["permutation_samples"]),
1134
+ random_seed=int(gates["random_seed"]),
1135
+ )
1136
+ records = verified_results.get("records")
1137
+ if not isinstance(records, Sequence) or isinstance(records, str | bytes):
1138
+ raise ValidationError("verified Selector results require records")
1139
+ by_group: dict[str, dict[str, Mapping[str, Any]]] = defaultdict(dict)
1140
+ for row in records:
1141
+ if not isinstance(row, Mapping):
1142
+ raise ValidationError("verified Selector result row must be an object")
1143
+ group_id = row.get("group_id")
1144
+ policy_id = row.get("policy_id")
1145
+ if not isinstance(group_id, str) or not isinstance(policy_id, str):
1146
+ raise ValidationError("verified Selector result row lacks group/policy identity")
1147
+ if policy_id in by_group[group_id]:
1148
+ raise ValidationError("verified Selector results duplicate a group-policy row")
1149
+ by_group[group_id][policy_id] = row
1150
+ if len(by_group) != 526 or any(
1151
+ set(rows) != _PAPER_SELECTOR_POLICIES for rows in by_group.values()
1152
+ ):
1153
+ raise ValidationError("paper Selector claim hierarchy requires 526 complete policy blocks")
1154
+
1155
+ primary_id = str(gates["primary_policy_id"])
1156
+ no_op_id = str(gates["no_op_policy_id"])
1157
+ best_id = str(gates["best_fixed_policy_id"])
1158
+ safety_id = str(gates["safety_comparator_policy_id"])
1159
+ superiority = []
1160
+ best_difference = []
1161
+ harm_difference = []
1162
+ primary_costs = []
1163
+ intervened = []
1164
+ for group_id in sorted(by_group):
1165
+ rows = by_group[group_id]
1166
+ primary = rows[primary_id]
1167
+ no_op = rows[no_op_id]
1168
+ best = rows[best_id]
1169
+ safety = rows[safety_id]
1170
+ superiority.append(float(primary["value"]) - float(no_op["value"]))
1171
+ best_difference.append(float(primary["value"]) - float(best["value"]))
1172
+ harm_difference.append(float(bool(primary["hard_harm"])) - float(bool(safety["hard_harm"])))
1173
+ primary_costs.append(float(primary["cost"]))
1174
+ intervened.append(1.0 if primary["intervened"] else 0.0)
1175
+
1176
+ rng = random.Random(int(gates["random_seed"]))
1177
+ alpha = float(gates["alpha"])
1178
+ superiority_margin = float(gates["valid_success_superiority_margin"])
1179
+ best_margin = float(gates["best_fixed_noninferiority_margin"])
1180
+ harm_margin = float(gates["hard_harm_noninferiority_margin"])
1181
+ superiority_p = _one_sided_sign_flip_p_value(
1182
+ [value - superiority_margin for value in superiority],
1183
+ samples=int(gates["permutation_samples"]),
1184
+ rng=rng,
1185
+ )
1186
+ best_p = _one_sided_sign_flip_p_value(
1187
+ [value + best_margin for value in best_difference],
1188
+ samples=int(gates["permutation_samples"]),
1189
+ rng=rng,
1190
+ )
1191
+ harm_p = _one_sided_sign_flip_p_value(
1192
+ [harm_margin - value for value in harm_difference],
1193
+ samples=int(gates["permutation_samples"]),
1194
+ rng=rng,
1195
+ )
1196
+ superiority_interval = _cluster_bootstrap_interval(
1197
+ superiority,
1198
+ samples=frozen_bootstrap_samples,
1199
+ rng=rng,
1200
+ )
1201
+ best_interval = _cluster_bootstrap_interval(
1202
+ best_difference,
1203
+ samples=frozen_bootstrap_samples,
1204
+ rng=rng,
1205
+ )
1206
+ harm_interval = _cluster_bootstrap_interval(
1207
+ harm_difference,
1208
+ samples=frozen_bootstrap_samples,
1209
+ rng=rng,
1210
+ )
1211
+ coverage = fmean(intervened)
1212
+ mean_cost = fmean(primary_costs)
1213
+ raw_passes = (
1214
+ superiority_p <= alpha and superiority_interval[0] > superiority_margin,
1215
+ best_p <= alpha and best_interval[0] > -best_margin,
1216
+ harm_p <= alpha and harm_interval[1] < harm_margin,
1217
+ (
1218
+ float(gates["minimum_intervention_coverage"])
1219
+ <= coverage
1220
+ <= float(gates["maximum_intervention_coverage"])
1221
+ and mean_cost <= float(gates["maximum_mean_cost"])
1222
+ ),
1223
+ )
1224
+ reached = True
1225
+ formal_passes: list[bool] = []
1226
+ formally_tested: list[bool] = []
1227
+ for passed in raw_passes:
1228
+ formally_tested.append(reached)
1229
+ formal_passes.append(reached and passed)
1230
+ reached = reached and passed
1231
+ steps = (
1232
+ {
1233
+ "step": 1,
1234
+ "claim": "valid_success_superiority_to_no_op",
1235
+ "estimate": fmean(superiority),
1236
+ "cluster_bootstrap_95ci": list(superiority_interval),
1237
+ "margin": superiority_margin,
1238
+ "one_sided_sign_flip_p_value": superiority_p,
1239
+ },
1240
+ {
1241
+ "step": 2,
1242
+ "claim": "noninferiority_to_calibration_frozen_best_fixed",
1243
+ "estimate": fmean(best_difference),
1244
+ "cluster_bootstrap_95ci": list(best_interval),
1245
+ "margin": best_margin,
1246
+ "one_sided_sign_flip_p_value": best_p,
1247
+ },
1248
+ {
1249
+ "step": 3,
1250
+ "claim": f"hard_harm_noninferiority_to_{safety_id}",
1251
+ "estimate": fmean(harm_difference),
1252
+ "cluster_bootstrap_95ci": list(harm_interval),
1253
+ "margin": harm_margin,
1254
+ "one_sided_sign_flip_p_value": harm_p,
1255
+ },
1256
+ {
1257
+ "step": 4,
1258
+ "claim": "frozen_cost_and_coverage_constraint",
1259
+ "intervention_coverage": coverage,
1260
+ "minimum_intervention_coverage": gates["minimum_intervention_coverage"],
1261
+ "maximum_intervention_coverage": gates["maximum_intervention_coverage"],
1262
+ "mean_cost": mean_cost,
1263
+ "maximum_mean_cost": gates["maximum_mean_cost"],
1264
+ },
1265
+ )
1266
+ return freeze_mapping(
1267
+ {
1268
+ "schema_version": 1,
1269
+ "analysis": "paper_selector_hierarchical_claim_gate",
1270
+ "analysis_protocol": PAPER_SELECTOR_PROTOCOL,
1271
+ "campaign_hash": campaign.get("campaign_hash"),
1272
+ "claim_gate_hash": gates["gate_hash"],
1273
+ "independent_groups": protocol_receipt["independent_groups"],
1274
+ "alpha": alpha,
1275
+ "bootstrap_samples": frozen_bootstrap_samples,
1276
+ "permutation_samples": gates["permutation_samples"],
1277
+ "random_seed": gates["random_seed"],
1278
+ "steps": tuple(
1279
+ {
1280
+ **step,
1281
+ "formally_tested": formally_tested[index],
1282
+ "passed_if_reached": raw_passes[index],
1283
+ "hierarchical_pass": formal_passes[index],
1284
+ }
1285
+ for index, step in enumerate(steps)
1286
+ ),
1287
+ "positive_selector_claim": all(formal_passes),
1288
+ "observed_action_oracle_used_for_inference": False,
1289
+ "paper_claim_authorized": False,
1290
+ }
1291
+ )
1292
+
1293
+
1294
+ def _validate_paper_selector_campaign(campaign: Mapping[str, Any]) -> None:
1295
+ if campaign.get("campaign_kind") != "baseline":
1296
+ raise ValidationError("paper Selector protocol requires the baseline campaign")
1297
+ if campaign.get("evaluation_split") != "untouched_test":
1298
+ raise ValidationError("paper Selector protocol requires the untouched test split")
1299
+ policies = campaign.get("policies")
1300
+ if not isinstance(policies, Sequence) or isinstance(policies, str | bytes):
1301
+ raise ValidationError("paper Selector campaign requires a policies array")
1302
+ policy_ids = {item.get("policy_id") for item in policies if isinstance(item, Mapping)}
1303
+ if len(policies) != 14 or policy_ids != _PAPER_SELECTOR_POLICIES:
1304
+ raise ValidationError("paper Selector protocol requires the exact 14-policy roster")
1305
+ metadata = campaign.get("metadata")
1306
+ if (
1307
+ not isinstance(metadata, Mapping)
1308
+ or metadata.get("required_independent_groups") != 526
1309
+ or metadata.get("minimum_common_support_groups") != 526
1310
+ ):
1311
+ raise ValidationError(
1312
+ "paper Selector protocol requires exactly 526 three-arena common-support groups"
1313
+ )
1314
+ if campaign.get("campaign_hash") is not None:
1315
+ campaign_hash = campaign["campaign_hash"]
1316
+ if not isinstance(campaign_hash, str) or len(campaign_hash) != 64:
1317
+ raise ValidationError("paper Selector campaign hash must be SHA-256")
1318
+
1319
+
1320
+ def validate_paper_run_contract(
1321
+ run_dir: str | Path,
1322
+ *,
1323
+ protocol: str,
1324
+ ) -> Mapping[str, Any]:
1325
+ """Validate one paper run and return its census receipt without analyzing it."""
1326
+
1327
+ expected = {
1328
+ PAPER_FAMILY_NATIVE_PROTOCOL: _FAMILY_NATIVE_KIND,
1329
+ PAPER_COMMON_SUPPORT_PROTOCOL: _COMMON_SUPPORT_KIND,
1330
+ PAPER_RETRIEVER_FACTORIAL_PROTOCOL: _RETRIEVER_FACTORIAL_KIND,
1331
+ }.get(protocol)
1332
+ if expected is None:
1333
+ raise ValidationError(f"unsupported paper run protocol: {protocol!r}")
1334
+ if expected == _RETRIEVER_FACTORIAL_KIND:
1335
+ manifest = _load_manifest(Path(run_dir).expanduser().resolve())
1336
+ census = manifest.metadata.get("experiment_census")
1337
+ actual_kind = census.get("campaign_kind") if isinstance(census, Mapping) else None
1338
+ if actual_kind in {
1339
+ _RETRIEVER_FACTORIAL_PRIMARY_KIND,
1340
+ _RETRIEVER_FACTORIAL_AUXILIARY_KIND,
1341
+ }:
1342
+ expected = str(actual_kind)
1343
+ run = _load_and_validate_paper_run(run_dir, expected_kind=expected)
1344
+ if expected == _FAMILY_NATIVE_KIND:
1345
+ _family_native_arm_contract(run.manifest)
1346
+ elif expected == _COMMON_SUPPORT_KIND:
1347
+ _common_support_arm_contract(run.manifest)
1348
+ else:
1349
+ _retriever_arm_contract(run.manifest)
1350
+ _retriever_dependency_cell(
1351
+ run.manifest,
1352
+ formal=expected
1353
+ in {_RETRIEVER_FACTORIAL_PRIMARY_KIND, _RETRIEVER_FACTORIAL_AUXILIARY_KIND},
1354
+ )
1355
+ return {
1356
+ "analysis_protocol": protocol,
1357
+ "study_id": run.manifest.study_id,
1358
+ "campaign_kind": run.campaign_kind,
1359
+ "independent_groups": run.independent_groups,
1360
+ "verified_plan_cells": run.expected_plan_cells,
1361
+ "verified_agent_episodes": run.expected_agent_episodes,
1362
+ "paper_claim_authorized": False,
1363
+ }
1364
+
1365
+
1366
+ def _load_and_validate_paper_run(
1367
+ run_dir: str | Path,
1368
+ *,
1369
+ expected_kind: str,
1370
+ ) -> _VerifiedPaperRun:
1371
+ root = Path(run_dir).expanduser().resolve()
1372
+ manifest = _load_manifest(root)
1373
+ records = load_verified_cell_results(root)
1374
+ census = manifest.metadata.get("experiment_census")
1375
+ if not isinstance(census, Mapping) or census.get("protocol") != _CENSUS_PROTOCOL:
1376
+ raise ArtifactError("paper run lacks the frozen experiment census protocol")
1377
+ if census.get("campaign_kind") != expected_kind:
1378
+ raise ArtifactError(
1379
+ f"paper run campaign kind is {census.get('campaign_kind')!r}, "
1380
+ f"expected {expected_kind!r}"
1381
+ )
1382
+ expected = _paper_census_expectation(expected_kind)
1383
+ for field, value in expected.items():
1384
+ if census.get(field) != value:
1385
+ raise ArtifactError(
1386
+ f"paper {expected_kind} census field {field} is "
1387
+ f"{census.get(field)!r}, expected {value!r}"
1388
+ )
1389
+ if manifest.stage is not ExperimentStage.CONFIRMATORY:
1390
+ raise ArtifactError("paper primary/factorial analyses require confirmatory-stage runs")
1391
+ retriever_kinds = {
1392
+ _RETRIEVER_FACTORIAL_KIND,
1393
+ _RETRIEVER_FACTORIAL_PRIMARY_KIND,
1394
+ _RETRIEVER_FACTORIAL_AUXILIARY_KIND,
1395
+ }
1396
+ profile = (
1397
+ ProfileKind.FACTORIAL_ADAPTER
1398
+ if expected_kind in retriever_kinds
1399
+ else ProfileKind.CONTROLLED_ADAPTER
1400
+ )
1401
+ if manifest.estimand_profile_kind is not profile:
1402
+ raise ArtifactError(f"paper {expected_kind} run has the wrong estimand profile")
1403
+ expected_arm_ids = census.get("expected_arm_ids")
1404
+ expected_models = census.get("expected_model_slots")
1405
+ expected_seeds = census.get("expected_seeds")
1406
+ if (
1407
+ not isinstance(expected_arm_ids, Sequence)
1408
+ or isinstance(expected_arm_ids, str | bytes)
1409
+ or tuple(expected_arm_ids) != tuple(arm.arm_id for arm in manifest.arms)
1410
+ or not isinstance(expected_models, Sequence)
1411
+ or isinstance(expected_models, str | bytes)
1412
+ or tuple(expected_models) != manifest.model_slots
1413
+ or not isinstance(expected_seeds, Sequence)
1414
+ or isinstance(expected_seeds, str | bytes)
1415
+ or tuple(expected_seeds) != manifest.seeds
1416
+ ):
1417
+ raise ArtifactError("paper run census arm/model/seed identities differ from its manifest")
1418
+
1419
+ expected_plan_cells = int(expected["expected_plan_cells"])
1420
+ expected_agent_episodes = int(expected["expected_agent_episodes"])
1421
+ independent_groups = int(expected["independent_group_target"])
1422
+ if len(records) != expected_plan_cells:
1423
+ raise ArtifactError(
1424
+ f"paper run has {len(records)} verified cells, expected {expected_plan_cells}"
1425
+ )
1426
+ clusters: dict[str, set[tuple[str, int]]] = defaultdict(set)
1427
+ episodes = 0
1428
+ for record in records:
1429
+ cell = record.get("cell")
1430
+ if not isinstance(cell, Mapping):
1431
+ raise ArtifactError("paper run record lacks an experiment cell")
1432
+ cluster_id = _cluster_id(cell)
1433
+ model_slot = _required_record_text(cell, "model_slot", label="experiment cell")
1434
+ seed = cell.get("seed")
1435
+ if isinstance(seed, bool) or not isinstance(seed, int):
1436
+ raise ArtifactError("paper experiment cell seed must be an integer")
1437
+ replicate = (model_slot, seed)
1438
+ if replicate in clusters[cluster_id]:
1439
+ raise ArtifactError("paper run duplicates a group/model/seed cell")
1440
+ clusters[cluster_id].add(replicate)
1441
+ arm_ids = cell.get("arm_ids")
1442
+ if not isinstance(arm_ids, Sequence) or isinstance(arm_ids, str | bytes):
1443
+ raise ArtifactError("paper experiment cell lacks an arm inventory")
1444
+ episodes += len(arm_ids)
1445
+ expected_replicates = {
1446
+ (model_slot, seed) for model_slot in manifest.model_slots for seed in manifest.seeds
1447
+ }
1448
+ if len(clusters) != independent_groups:
1449
+ raise ArtifactError(
1450
+ f"paper run has {len(clusters)} independent groups, expected {independent_groups}"
1451
+ )
1452
+ if any(replicates != expected_replicates for replicates in clusters.values()):
1453
+ raise ArtifactError("paper run does not contain the exact model/seed grid per group")
1454
+ if episodes != expected_agent_episodes:
1455
+ raise ArtifactError(
1456
+ f"paper run has {episodes} arm episodes, expected {expected_agent_episodes}"
1457
+ )
1458
+ return _VerifiedPaperRun(
1459
+ root=root,
1460
+ manifest=manifest,
1461
+ records=records,
1462
+ campaign_kind=expected_kind,
1463
+ independent_groups=independent_groups,
1464
+ expected_plan_cells=expected_plan_cells,
1465
+ expected_agent_episodes=expected_agent_episodes,
1466
+ cluster_ids=frozenset(clusters),
1467
+ )
1468
+
1469
+
1470
+ def _paper_census_expectation(kind: str) -> Mapping[str, int]:
1471
+ if kind == _FAMILY_NATIVE_KIND:
1472
+ return {
1473
+ "independent_group_target": 600,
1474
+ "expected_plan_cells": 1_200,
1475
+ "expected_agent_episodes": 3_600,
1476
+ }
1477
+ if kind == _COMMON_SUPPORT_KIND:
1478
+ return {
1479
+ "independent_group_target": 526,
1480
+ "expected_plan_cells": 1_052,
1481
+ "expected_agent_episodes": 6_312,
1482
+ }
1483
+ if kind == _RETRIEVER_FACTORIAL_KIND:
1484
+ return {
1485
+ "independent_group_target": 240,
1486
+ "expected_plan_cells": 240,
1487
+ "expected_agent_episodes": 1_200,
1488
+ }
1489
+ if kind == _RETRIEVER_FACTORIAL_PRIMARY_KIND:
1490
+ return {
1491
+ "independent_group_target": 97,
1492
+ "expected_plan_cells": 194,
1493
+ "expected_agent_episodes": 970,
1494
+ }
1495
+ if kind == _RETRIEVER_FACTORIAL_AUXILIARY_KIND:
1496
+ return {
1497
+ "independent_group_target": 240,
1498
+ "expected_plan_cells": 480,
1499
+ "expected_agent_episodes": 2_400,
1500
+ }
1501
+ raise ValidationError(f"unknown paper census kind: {kind!r}")
1502
+
1503
+
1504
+ def _family_native_arm_contract(manifest: ExperimentManifest) -> SemanticFamily:
1505
+ census = manifest.metadata["experiment_census"]
1506
+ raw_family = census.get("family")
1507
+ try:
1508
+ family = SemanticFamily(raw_family)
1509
+ except (TypeError, ValueError) as exc:
1510
+ raise ArtifactError("family-native census requires one core family") from exc
1511
+ if family not in _primary_families():
1512
+ raise ArtifactError("family-native census family is not one of the five core families")
1513
+ _family_native_arm_ids(manifest, family)
1514
+ if len(manifest.model_slots) != 2 or len(manifest.seeds) != 1:
1515
+ raise ArtifactError("family-native paper run requires two models and one seed")
1516
+ return family
1517
+
1518
+
1519
+ def _family_native_arm_ids(
1520
+ manifest: ExperimentManifest,
1521
+ family: SemanticFamily,
1522
+ ) -> tuple[str, tuple[str, str]]:
1523
+ baselines = tuple(arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.NO_OP)
1524
+ treatments = tuple(arm.arm_id for arm in manifest.arms if arm.family is family)
1525
+ if len(manifest.arms) != 3 or len(baselines) != 1 or len(treatments) != 2:
1526
+ raise ArtifactError(
1527
+ "family-native paper run requires exactly NO_OP plus two same-family arms"
1528
+ )
1529
+ return baselines[0], (treatments[0], treatments[1])
1530
+
1531
+
1532
+ def _common_support_arm_contract(
1533
+ manifest: ExperimentManifest,
1534
+ ) -> tuple[str, Mapping[SemanticFamily, str]]:
1535
+ baselines = tuple(arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.NO_OP)
1536
+ by_family = {
1537
+ family: tuple(arm.arm_id for arm in manifest.arms if arm.family is family)
1538
+ for family in _primary_families()
1539
+ }
1540
+ if (
1541
+ len(manifest.arms) != 6
1542
+ or len(baselines) != 1
1543
+ or any(len(arm_ids) != 1 for arm_ids in by_family.values())
1544
+ or len(manifest.model_slots) != 2
1545
+ or len(manifest.seeds) != 1
1546
+ ):
1547
+ raise ArtifactError(
1548
+ "common-support paper run requires NO_OP, one arm per family, two models, and one seed"
1549
+ )
1550
+ return baselines[0], {family: arm_ids[0] for family, arm_ids in by_family.items()}
1551
+
1552
+
1553
+ def _retriever_arm_contract(
1554
+ manifest: ExperimentManifest,
1555
+ ) -> tuple[str, tuple[str, str, str, str]]:
1556
+ baselines = tuple(arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.NO_OP)
1557
+ reformulate = tuple(
1558
+ arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.REFORMULATE
1559
+ )
1560
+ acquire = tuple(arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.ACQUIRE)
1561
+ campaign_kind = manifest.metadata.get("experiment_census", {}).get("campaign_kind")
1562
+ expected_model_count = (
1563
+ 2
1564
+ if campaign_kind in {_RETRIEVER_FACTORIAL_PRIMARY_KIND, _RETRIEVER_FACTORIAL_AUXILIARY_KIND}
1565
+ else 1
1566
+ )
1567
+ if (
1568
+ len(manifest.arms) != 5
1569
+ or len(baselines) != 1
1570
+ or len(reformulate) != 2
1571
+ or len(acquire) != 2
1572
+ or len(manifest.model_slots) != expected_model_count
1573
+ or len(manifest.seeds) != 1
1574
+ ):
1575
+ raise ArtifactError(
1576
+ "retriever-factorial paper cell requires NO_OP, two reformulation arms, "
1577
+ f"two acquisition arms, {expected_model_count} model(s), and one seed"
1578
+ )
1579
+ return baselines[0], (reformulate[0], reformulate[1], acquire[0], acquire[1])
1580
+
1581
+
1582
+ def _retriever_dependency_cell(
1583
+ manifest: ExperimentManifest,
1584
+ *,
1585
+ formal: bool = False,
1586
+ ) -> tuple[str, int]:
1587
+ dependency = manifest.metadata.get("dependency_cell")
1588
+ if not isinstance(dependency, Mapping):
1589
+ raise ArtifactError("retriever-factorial run lacks dependency_cell metadata")
1590
+ retriever = dependency.get("retriever_id")
1591
+ top_k = dependency.get("top_k")
1592
+ allowed = _FORMAL_PAPER_RETRIEVERS if formal else _PAPER_RETRIEVERS
1593
+ if retriever not in allowed or top_k not in _PAPER_TOP_K:
1594
+ grid = "4x2" if formal else "2x2"
1595
+ raise ArtifactError(
1596
+ f"retriever-factorial dependency cell is outside the frozen {grid} grid"
1597
+ )
1598
+ slugs = {
1599
+ "bm25": "bm25",
1600
+ "intfloat/e5-large-v2": "e5_large_v2",
1601
+ "BAAI/bge-m3": "bge_m3",
1602
+ "Qwen/Qwen3-Embedding-0.6B": "qwen3_embedding",
1603
+ }
1604
+ expected_binding = slugs[str(retriever)] + f":k{top_k}"
1605
+ if any(arm.parameters.get("dependency_cell") != expected_binding for arm in manifest.arms):
1606
+ raise ArtifactError(
1607
+ "retriever-factorial arm dependency binding differs from its declared cell"
1608
+ )
1609
+ return str(retriever), int(top_k)
1610
+
1611
+
1612
+ def _retriever_runtime_receipt(run: _VerifiedPaperRun) -> Mapping[str, Any]:
1613
+ """Collapse one cell's observed runtime identities after strict equality checks."""
1614
+
1615
+ expected_arm_ids = {arm.arm_id for arm in run.manifest.arms}
1616
+ reference_dependencies: dict[str, str] | None = None
1617
+ dataset_identity: str | None = None
1618
+ required_slots = {"backend", "retriever", "downstream_predictor", "seed"}
1619
+ for record in run.records:
1620
+ result = record.get("result")
1621
+ payload = result.get("payload") if isinstance(result, Mapping) else None
1622
+ if not isinstance(payload, Mapping):
1623
+ raise ArtifactError("retriever-factorial result lacks a payload")
1624
+ observed = payload.get("observed_dependency_hashes")
1625
+ if not isinstance(observed, Mapping) or set(observed) != expected_arm_ids:
1626
+ raise ArtifactError(
1627
+ "retriever-factorial dependency receipt differs from the arm inventory"
1628
+ )
1629
+ by_arm: dict[str, dict[str, str]] = {}
1630
+ for arm_id, raw_dependencies in observed.items():
1631
+ if not isinstance(arm_id, str) or not isinstance(raw_dependencies, Mapping):
1632
+ raise ArtifactError("retriever-factorial dependency receipt is malformed")
1633
+ parsed: dict[str, str] = {}
1634
+ for slot, digest in raw_dependencies.items():
1635
+ if (
1636
+ not isinstance(slot, str)
1637
+ or not slot
1638
+ or not isinstance(digest, str)
1639
+ or len(digest) != 64
1640
+ or any(character not in "0123456789abcdef" for character in digest)
1641
+ ):
1642
+ raise ArtifactError(
1643
+ "retriever-factorial dependency fingerprints must be SHA-256 mappings"
1644
+ )
1645
+ parsed[slot] = digest
1646
+ if not parsed:
1647
+ raise ArtifactError("retriever-factorial dependency receipt is empty")
1648
+ by_arm[arm_id] = parsed
1649
+ first = next(iter(by_arm.values()))
1650
+ if any(dependencies != first for dependencies in by_arm.values()):
1651
+ raise ArtifactError(
1652
+ "retriever-factorial nuisance dependencies differ across arms within a cell"
1653
+ )
1654
+ if not required_slots.issubset(first):
1655
+ raise ArtifactError(
1656
+ "retriever-factorial dependency receipt lacks backend, retriever, "
1657
+ "predictor, or seed identity"
1658
+ )
1659
+ if reference_dependencies is None:
1660
+ reference_dependencies = first
1661
+ elif first != reference_dependencies:
1662
+ raise ArtifactError(
1663
+ "retriever-factorial runtime dependencies drift across groups within a cell"
1664
+ )
1665
+ current_dataset = payload.get("dataset_identity_sha256")
1666
+ if (
1667
+ not isinstance(current_dataset, str)
1668
+ or len(current_dataset) != 64
1669
+ or any(character not in "0123456789abcdef" for character in current_dataset)
1670
+ ):
1671
+ raise ArtifactError("retriever-factorial result lacks dataset identity SHA-256")
1672
+ if dataset_identity is None:
1673
+ dataset_identity = current_dataset
1674
+ elif current_dataset != dataset_identity:
1675
+ raise ArtifactError(
1676
+ "retriever-factorial dataset identity drifts across groups within a cell"
1677
+ )
1678
+ if reference_dependencies is None or dataset_identity is None: # pragma: no cover
1679
+ raise ArtifactError("retriever-factorial dependency receipt has no records")
1680
+ return {
1681
+ "dependencies": reference_dependencies,
1682
+ "dataset_identity_sha256": dataset_identity,
1683
+ }
1684
+
1685
+
1686
+ def _validate_retriever_factorial_balance(
1687
+ receipts: Mapping[tuple[str, int], Mapping[str, Any]],
1688
+ ) -> Mapping[str, Any]:
1689
+ """Verify all non-factorial runtime identities across the four dependency cells."""
1690
+
1691
+ dependency_maps = {key: dict(receipt["dependencies"]) for key, receipt in receipts.items()}
1692
+ key_sets = {frozenset(dependencies) for dependencies in dependency_maps.values()}
1693
+ if len(key_sets) != 1:
1694
+ raise ArtifactError("retriever-factorial cells expose different dependency slots")
1695
+ slots = next(iter(key_sets))
1696
+ dataset_hashes = {str(receipt["dataset_identity_sha256"]) for receipt in receipts.values()}
1697
+ if len(dataset_hashes) != 1:
1698
+ raise ArtifactError("retriever-factorial cells use different dataset/corpus identities")
1699
+ varied_slots = {"retriever", "downstream_predictor"}
1700
+ for slot in slots - varied_slots:
1701
+ if len({dependencies[slot] for dependencies in dependency_maps.values()}) != 1:
1702
+ raise ArtifactError(
1703
+ f"retriever-factorial non-varied dependency {slot!r} differs across cells"
1704
+ )
1705
+ retriever_hashes: dict[str, str] = {}
1706
+ for retriever in _PAPER_RETRIEVERS:
1707
+ values = {dependency_maps[(retriever, top_k)]["retriever"] for top_k in _PAPER_TOP_K}
1708
+ if len(values) != 1:
1709
+ raise ArtifactError(
1710
+ "retriever identity changes across top-k cells for the same retriever"
1711
+ )
1712
+ retriever_hashes[retriever] = next(iter(values))
1713
+ if len(set(retriever_hashes.values())) != len(_PAPER_RETRIEVERS):
1714
+ raise ArtifactError("the two declared retrievers resolve to the same runtime identity")
1715
+ predictor_hashes: dict[int, str] = {}
1716
+ for top_k in _PAPER_TOP_K:
1717
+ values = {
1718
+ dependency_maps[(retriever, top_k)]["downstream_predictor"]
1719
+ for retriever in _PAPER_RETRIEVERS
1720
+ }
1721
+ if len(values) != 1:
1722
+ raise ArtifactError(
1723
+ "downstream predictor identity changes across retrievers at fixed top-k"
1724
+ )
1725
+ predictor_hashes[top_k] = next(iter(values))
1726
+ if len(set(predictor_hashes.values())) != len(_PAPER_TOP_K):
1727
+ raise ArtifactError("the two declared top-k cells resolve to the same predictor identity")
1728
+ return {
1729
+ "dataset_identity_sha256": next(iter(dataset_hashes)),
1730
+ "shared_dependency_slots": sorted(slots - varied_slots),
1731
+ "retriever_slot_hashes": retriever_hashes,
1732
+ "predictor_slot_hashes_by_top_k": {
1733
+ str(top_k): digest for top_k, digest in predictor_hashes.items()
1734
+ },
1735
+ "balance_verified": True,
1736
+ }
1737
+
1738
+
1739
+ def _effect_summary(
1740
+ values: Sequence[float],
1741
+ *,
1742
+ bootstrap_samples: int,
1743
+ rng: random.Random,
1744
+ ) -> Mapping[str, Any]:
1745
+ return {
1746
+ "cluster_count": len(values),
1747
+ "mean_difference": fmean(values),
1748
+ "cluster_bootstrap_95ci": list(
1749
+ _cluster_bootstrap_interval(values, samples=bootstrap_samples, rng=rng)
1750
+ ),
1751
+ }
1752
+
1753
+
1754
+ def _one_sided_sign_flip_p_value(
1755
+ shifted_effects: Sequence[float],
1756
+ *,
1757
+ samples: int,
1758
+ rng: random.Random,
1759
+ ) -> float:
1760
+ if len(shifted_effects) < 2:
1761
+ raise ArtifactError("paired sign-flip test requires at least two groups")
1762
+ observed = fmean(shifted_effects)
1763
+ exceedances = 0
1764
+ for _ in range(samples):
1765
+ permuted = fmean(value if rng.random() < 0.5 else -value for value in shifted_effects)
1766
+ if permuted >= observed - 1e-15:
1767
+ exceedances += 1
1768
+ return (exceedances + 1.0) / (samples + 1.0)
1769
+
1770
+
1771
+ def _bounded_protocol_number(
1772
+ value: Any,
1773
+ *,
1774
+ lower: float,
1775
+ upper: float | None,
1776
+ label: str,
1777
+ ) -> float:
1778
+ if (
1779
+ isinstance(value, bool)
1780
+ or not isinstance(value, int | float)
1781
+ or not math.isfinite(value)
1782
+ or value < lower
1783
+ or (upper is not None and value > upper)
1784
+ ):
1785
+ suffix = f" and <= {upper}" if upper is not None else ""
1786
+ raise ValidationError(f"paper Selector {label} must be finite, >= {lower}{suffix}")
1787
+ return float(value)
1788
+
1789
+
1790
+ def _run_roots(run_dirs: Sequence[str | Path]) -> tuple[Path, ...]:
1791
+ if isinstance(run_dirs, str | bytes):
1792
+ raise ValidationError("run_dirs must be a sequence of paths")
1793
+ roots = tuple(Path(path).expanduser().resolve() for path in run_dirs)
1794
+ if not roots:
1795
+ raise ValidationError("at least one run directory is required")
1796
+ if len(roots) != len(set(roots)):
1797
+ raise ValidationError("run directories must be unique")
1798
+ return roots
1799
+
1800
+
1801
+ __all__ = [
1802
+ "LEGACY_CONFIRMATORY_PROTOCOL",
1803
+ "PAPER_COMMON_SUPPORT_PROTOCOL",
1804
+ "PAPER_FAMILY_NATIVE_PROTOCOL",
1805
+ "PAPER_RETRIEVER_FACTORIAL_PROTOCOL",
1806
+ "PAPER_SELECTOR_PROTOCOL",
1807
+ "analyze_confirmatory_protocol",
1808
+ "analyze_paper_common_support",
1809
+ "analyze_paper_dual_arena_retriever_factorial",
1810
+ "analyze_paper_family_confirmation",
1811
+ "analyze_paper_retriever_factorial",
1812
+ "analyze_paper_selector_claim_hierarchy",
1813
+ "build_paper_selector_claim_gates",
1814
+ "infer_paper_run_protocol",
1815
+ "validate_paper_run_contract",
1816
+ "validate_paper_selector_analysis_contract",
1817
+ "validate_paper_selector_claim_gates",
1818
+ ]