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,981 @@
1
+ """Small, schema-checked analyses over completed experiment cell artifacts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import random
9
+ from collections import defaultdict
10
+ from collections.abc import Mapping, Sequence
11
+ from pathlib import Path
12
+ from statistics import fmean, median
13
+ from typing import Any
14
+
15
+ from agentinputlab.contracts.enums import SemanticFamily
16
+ from agentinputlab.contracts.errors import ArtifactError, ValidationError
17
+ from agentinputlab.metrics.paired import summarize_pairs
18
+
19
+ from .artifacts import verify_experiment_run_directory
20
+ from .schema import ExperimentManifest
21
+
22
+
23
+ def load_verified_cell_results(run_dir: str | Path) -> tuple[Mapping[str, Any], ...]:
24
+ """Load a complete plan whose comparisons expose their full arm payloads.
25
+
26
+ A terminal failed cell is complete, but cannot supply an unobserved paired
27
+ arm value. Use ``load_verified_terminal_cell_results`` for lifecycle
28
+ accounting; do not rerun failed cells merely to satisfy this loader.
29
+ """
30
+
31
+ return _load_verified_results(run_dir, require_all_succeeded=True)
32
+
33
+
34
+ def load_verified_terminal_cell_results(
35
+ run_dir: str | Path,
36
+ ) -> tuple[Mapping[str, Any], ...]:
37
+ """Load every verified terminal result, including explicit failed cells.
38
+
39
+ Checksum, frozen-plan, manifest, complete-union and shard guards are the
40
+ same as the paired loader. Missing results are not silently made failures.
41
+ """
42
+
43
+ return _load_verified_results(run_dir, require_all_succeeded=False)
44
+
45
+
46
+ def _load_verified_results(
47
+ run_dir: str | Path, *, require_all_succeeded: bool
48
+ ) -> tuple[Mapping[str, Any], ...]:
49
+
50
+ root = Path(run_dir).expanduser().resolve()
51
+ verification = verify_experiment_run_directory(root)
52
+ if verification["is_shard"]:
53
+ raise ArtifactError(
54
+ "analysis refuses a single experiment shard; rerun the frozen full plan "
55
+ "or merge all shards with a separately verified exact-union workflow"
56
+ )
57
+ if not verification["complete"]:
58
+ raise ArtifactError("experiment run is incomplete; analysis refuses partial-plan results")
59
+ if require_all_succeeded and not verification["all_succeeded"]:
60
+ raise ArtifactError(
61
+ "experiment run contains terminal failed cells; they already have "
62
+ "terminal outcomes, but lack paired arm measurements. Use terminal "
63
+ "accounting or an applicable explicit failure estimand; do not rerun "
64
+ "them merely to obtain all-succeeded artifacts"
65
+ )
66
+ index = _read_object(root / "result_index.json", label="result index")
67
+ if (
68
+ set(index) != {"schema_version", "results"}
69
+ or index["schema_version"] != 1
70
+ or not isinstance(index["results"], Mapping)
71
+ ):
72
+ raise ArtifactError("experiment result index schema is invalid")
73
+ records: list[Mapping[str, Any]] = []
74
+ for cell_id, expected in sorted(index["results"].items()):
75
+ if not isinstance(cell_id, str) or not isinstance(expected, str):
76
+ raise ArtifactError("experiment result index entry is invalid")
77
+ path = root / "results" / f"{cell_id}.json"
78
+ try:
79
+ content = path.read_bytes()
80
+ except OSError as exc:
81
+ raise ArtifactError(f"missing result for cell {cell_id}") from exc
82
+ if hashlib.sha256(content).hexdigest() != expected:
83
+ raise ArtifactError(f"result checksum mismatch for cell {cell_id}")
84
+ record = _read_object(path, label=f"result {cell_id}")
85
+ if record.get("manifest_hash") != _manifest_hash(root):
86
+ raise ArtifactError(f"result manifest identity mismatch for cell {cell_id}")
87
+ cell = record.get("cell")
88
+ result = record.get("result")
89
+ if (
90
+ not isinstance(cell, Mapping)
91
+ or cell.get("cell_id") != cell_id
92
+ or not isinstance(result, Mapping)
93
+ or result.get("cell_id") != cell_id
94
+ ):
95
+ raise ArtifactError(f"result cell identity is invalid for {cell_id}")
96
+ records.append(record)
97
+ if not records:
98
+ raise ArtifactError("experiment run has no completed cell results")
99
+ return tuple(records)
100
+
101
+
102
+ def summarize_terminal_execution_outcomes(run_dir: str | Path) -> Mapping[str, Any]:
103
+ """Describe a complete plan without deleting or repairing its failures.
104
+
105
+ ``succeeded`` here means the driver completed the comparison, not that the
106
+ algorithm solved the benchmark. This summary must not be presented as a
107
+ task-success rate or used to impute missing baseline/arm scores.
108
+ """
109
+
110
+ records = load_verified_terminal_cell_results(run_dir)
111
+ failures: dict[str, int] = defaultdict(int)
112
+ cells = []
113
+ completed_comparisons = 0
114
+ for record in records:
115
+ result = record["result"]
116
+ status = result["status"]
117
+ if status == "succeeded":
118
+ completed_comparisons += 1
119
+ elif status == "failed":
120
+ failure = result["failure"]
121
+ failures[str(failure.get("category", "unspecified"))] += 1
122
+ else: # Also guarded by the artifact verifier; never treat missing as zero.
123
+ raise ArtifactError("execution outcome must be an explicit terminal result")
124
+ cells.append(
125
+ {
126
+ "cell_id": result["cell_id"],
127
+ "status": status,
128
+ "terminal": True,
129
+ "retry_required": False,
130
+ "included_in_execution_denominator": True,
131
+ "task_score_imputed": False,
132
+ }
133
+ )
134
+ count = len(records)
135
+ return {
136
+ "schema_version": 1,
137
+ "artifact": "agentinputlab.terminal_execution_accounting.v1",
138
+ "planned_terminal_cells": count,
139
+ "terminal_cells": count,
140
+ "completed_comparisons": completed_comparisons,
141
+ "terminal_failed_cells": count - completed_comparisons,
142
+ "terminal_complete": True,
143
+ "all_comparisons_completed": completed_comparisons == count,
144
+ "execution_completion_fraction": completed_comparisons / count,
145
+ "task_success_rate": None,
146
+ "failure_categories": dict(sorted(failures.items())),
147
+ "cells": cells,
148
+ "inference_performed": False,
149
+ "raw_results_modified": False,
150
+ "paper_claim_authorized": False,
151
+ "scope_note": (
152
+ "Terminal accounting, not benchmark task-success or paired inference. "
153
+ "Fixed-budget failures require their source-bound failure estimand; "
154
+ "repair attempts remain separate and never replace these outcomes."
155
+ ),
156
+ }
157
+
158
+
159
+ def summarize_paired_arm_metric(
160
+ run_dir: str | Path,
161
+ *,
162
+ metric: str,
163
+ baseline_arm_id: str = "no_op",
164
+ algorithm_failure_value: float | None = None,
165
+ ) -> Mapping[str, Any]:
166
+ """Summarize matched arm values supplied by a production experiment driver.
167
+
168
+ Drivers must put a list named ``arms`` in ``result.payload``. Each item
169
+ contains ``arm_id``, ``status``, and a finite numeric ``metrics`` mapping.
170
+ A planned arm may report a typed algorithm failure. Such failures are
171
+ never silently excluded: callers must provide a preregistered finite
172
+ ``algorithm_failure_value`` or analysis stops.
173
+ """
174
+
175
+ if not isinstance(metric, str) or not metric.strip():
176
+ raise ValidationError("analysis metric must be non-empty")
177
+ if algorithm_failure_value is not None and (
178
+ isinstance(algorithm_failure_value, bool)
179
+ or not isinstance(algorithm_failure_value, (int, float))
180
+ or not math.isfinite(algorithm_failure_value)
181
+ ):
182
+ raise ValidationError("algorithm_failure_value must be finite when provided")
183
+ pairs: dict[str, tuple[list[float], list[float]]] = {}
184
+ arm_values: dict[str, list[float]] = defaultdict(list)
185
+ complete_blocks = 0
186
+ excluded_blocks = 0
187
+ records = load_verified_cell_results(run_dir)
188
+ for record in records:
189
+ normalized = _arm_metrics(
190
+ record,
191
+ metric=metric,
192
+ algorithm_failure_value=algorithm_failure_value,
193
+ )
194
+ if normalized is None or baseline_arm_id not in normalized:
195
+ excluded_blocks += 1
196
+ continue
197
+ complete_blocks += 1
198
+ baseline = normalized[baseline_arm_id]
199
+ for arm_id, value in normalized.items():
200
+ arm_values[arm_id].append(value)
201
+ if arm_id == baseline_arm_id:
202
+ continue
203
+ before, after = pairs.setdefault(arm_id, ([], []))
204
+ before.append(baseline)
205
+ after.append(value)
206
+ if complete_blocks == 0:
207
+ raise ArtifactError("no complete matched blocks expose the requested arm metric")
208
+ summaries: dict[str, Any] = {}
209
+ for arm_id in sorted(arm_values):
210
+ values = arm_values[arm_id]
211
+ payload: dict[str, Any] = {
212
+ "count": len(values),
213
+ "mean": fmean(values),
214
+ }
215
+ if arm_id != baseline_arm_id and arm_id in pairs:
216
+ before, after = pairs[arm_id]
217
+ paired = summarize_pairs(tuple(before), tuple(after))
218
+ payload.update(
219
+ {
220
+ "mean_effect_vs_no_op": paired.mean_effect,
221
+ "win_rate_vs_no_op": paired.win_rate,
222
+ "tie_rate_vs_no_op": paired.tie_rate,
223
+ "harm_rate_vs_no_op": paired.harm_rate,
224
+ }
225
+ )
226
+ summaries[arm_id] = payload
227
+ return {
228
+ "schema_version": 1,
229
+ "metric": metric,
230
+ "baseline_arm_id": baseline_arm_id,
231
+ "complete_blocks": complete_blocks,
232
+ "excluded_incomplete_blocks": excluded_blocks,
233
+ "algorithm_failure_value": algorithm_failure_value,
234
+ "arms": summaries,
235
+ "inference_performed": False,
236
+ "scope_note": (
237
+ "Descriptive matched summaries only; preregistered confidence intervals, "
238
+ "interaction contrasts, and multiplicity correction run in the paper analysis."
239
+ ),
240
+ }
241
+
242
+
243
+ def analyze_confirmatory_interactions(
244
+ run_dir: str | Path,
245
+ *,
246
+ metric: str,
247
+ algorithm_failure_value: float | None = None,
248
+ bootstrap_samples: int = 2_000,
249
+ permutation_samples: int = 2_000,
250
+ random_seed: int = 2027,
251
+ ) -> Mapping[str, Any]:
252
+ """Run the five preregistered family-by-visible-defect contrasts.
253
+
254
+ The unit of resampling is the benchmark/unit group. Replicate seeds,
255
+ model slots, and the two frozen implementations within a family are
256
+ averaged inside that group before uncertainty is estimated.
257
+ """
258
+
259
+ _validate_resample_arguments(
260
+ bootstrap_samples=bootstrap_samples,
261
+ permutation_samples=permutation_samples,
262
+ random_seed=random_seed,
263
+ )
264
+ root = Path(run_dir).expanduser().resolve()
265
+ manifest = _load_manifest(root)
266
+ records = load_verified_cell_results(root)
267
+ baseline_arm_id, arms_by_family = _confirmatory_arm_map(manifest)
268
+ observations: dict[str, list[tuple[str, str, float, float]]] = {
269
+ family.value: [] for family in _primary_families()
270
+ }
271
+ model_effects: dict[str, dict[str, list[float]]] = defaultdict(lambda: defaultdict(list))
272
+ for record in records:
273
+ values = _arm_metrics(
274
+ record,
275
+ metric=metric,
276
+ algorithm_failure_value=algorithm_failure_value,
277
+ )
278
+ if values is None: # pragma: no cover - all-succeeded invariant
279
+ raise ArtifactError("confirmatory record is not a completed cell")
280
+ cell = record["cell"]
281
+ cluster_id = _cluster_id(cell)
282
+ model_slot = _required_record_text(cell, "model_slot", label="experiment cell")
283
+ defect_scores = _pre_action_defect_scores(record)
284
+ baseline = values[baseline_arm_id]
285
+ for family in _primary_families():
286
+ family_name = family.value
287
+ effect = fmean(values[arm_id] - baseline for arm_id in arms_by_family[family])
288
+ observations[family_name].append(
289
+ (cluster_id, model_slot, defect_scores[family_name], effect)
290
+ )
291
+ model_effects[model_slot][family_name].append(effect)
292
+
293
+ rng = random.Random(random_seed)
294
+ contrasts: dict[str, Any] = {}
295
+ raw_p_values: dict[str, float] = {}
296
+ for family in _primary_families():
297
+ family_name = family.value
298
+ collapsed = _collapse_confirmatory_clusters(observations[family_name])
299
+ effects = [row[2] for row in collapsed]
300
+ defects = [row[1] for row in collapsed]
301
+ slope = _linear_slope(defects, effects)
302
+ effect_interval = _cluster_bootstrap_interval(
303
+ effects,
304
+ samples=bootstrap_samples,
305
+ rng=rng,
306
+ )
307
+ slope_interval = _paired_cluster_bootstrap_slope_interval(
308
+ defects,
309
+ effects,
310
+ samples=bootstrap_samples,
311
+ rng=rng,
312
+ )
313
+ p_value = _permutation_slope_p_value(
314
+ defects,
315
+ effects,
316
+ observed=slope,
317
+ samples=permutation_samples,
318
+ rng=rng,
319
+ )
320
+ raw_p_values[family_name] = p_value
321
+ contrasts[family_name] = {
322
+ "cluster_count": len(collapsed),
323
+ "family_effect_mean": fmean(effects),
324
+ "family_effect_cluster_bootstrap_95ci": list(effect_interval),
325
+ "matched_defect_slope": slope,
326
+ "matched_defect_slope_cluster_bootstrap_95ci": list(slope_interval),
327
+ "permutation_p_value_raw": p_value,
328
+ }
329
+ adjusted = _holm_adjust(raw_p_values)
330
+ for family_name, value in adjusted.items():
331
+ contrasts[family_name]["holm_adjusted_p_value"] = value
332
+
333
+ return {
334
+ "schema_version": 1,
335
+ "analysis": "five_family_matched_visible_defect_confirmation",
336
+ "metric": metric,
337
+ "baseline_arm_id": baseline_arm_id,
338
+ "cluster_definition": "benchmark_id/split/unit_id",
339
+ "replicate_handling": (
340
+ "equal-weight mean over frozen implementations, model slots, and seeds "
341
+ "within each benchmark/unit cluster"
342
+ ),
343
+ "bootstrap_samples": bootstrap_samples,
344
+ "permutation_samples": permutation_samples,
345
+ "random_seed": random_seed,
346
+ "algorithm_failure_value": algorithm_failure_value,
347
+ "contrasts": contrasts,
348
+ "model_strata": {
349
+ model_slot: {
350
+ family: {
351
+ "count": len(values),
352
+ "mean_effect_vs_no_op": fmean(values),
353
+ }
354
+ for family, values in sorted(families.items())
355
+ }
356
+ for model_slot, families in sorted(model_effects.items())
357
+ },
358
+ "multiplicity": "Holm family-wise correction across the five matched contrasts",
359
+ "paper_claim_authorized": False,
360
+ }
361
+
362
+
363
+ def analyze_selector_policy(
364
+ run_dir: str | Path,
365
+ *,
366
+ metric: str,
367
+ algorithm_failure_value: float | None = None,
368
+ bootstrap_samples: int = 2_000,
369
+ random_seed: int = 2027,
370
+ calibration_bins: int = 10,
371
+ ) -> Mapping[str, Any]:
372
+ """Evaluate frozen Selector value, risk-coverage, and gain calibration."""
373
+
374
+ _validate_resample_arguments(
375
+ bootstrap_samples=bootstrap_samples,
376
+ permutation_samples=1,
377
+ random_seed=random_seed,
378
+ )
379
+ if (
380
+ isinstance(calibration_bins, bool)
381
+ or not isinstance(calibration_bins, int)
382
+ or calibration_bins < 2
383
+ ):
384
+ raise ValidationError("calibration_bins must be an integer >= 2")
385
+ records = load_verified_cell_results(run_dir)
386
+ required_arms = {
387
+ "no_op",
388
+ "locked_best_eligible_global",
389
+ "eligibility_aware_selector",
390
+ }
391
+ rows: list[dict[str, Any]] = []
392
+ for record in records:
393
+ values = _arm_metrics(
394
+ record,
395
+ metric=metric,
396
+ algorithm_failure_value=algorithm_failure_value,
397
+ )
398
+ if values is None: # pragma: no cover - all-succeeded invariant
399
+ raise ArtifactError("selector record is not a completed cell")
400
+ if set(values) != required_arms:
401
+ raise ArtifactError(
402
+ "selector confirmation requires exactly NO_OP, locked global, and selector arms"
403
+ )
404
+ decision = _selector_decision(record)
405
+ baseline = values["no_op"]
406
+ selector_value = values["eligibility_aware_selector"]
407
+ rows.append(
408
+ {
409
+ "cluster_id": _cluster_id(record["cell"]),
410
+ "model_slot": _required_record_text(
411
+ record["cell"],
412
+ "model_slot",
413
+ label="experiment cell",
414
+ ),
415
+ "baseline": baseline,
416
+ "global": values["locked_best_eligible_global"],
417
+ "selector": selector_value,
418
+ "selector_gain": selector_value - baseline,
419
+ "predicted_gain": decision["predicted_gain"],
420
+ "confidence": decision["confidence"],
421
+ "selected_family": decision["selected_family"],
422
+ "selected_release_id": decision["selected_release_id"],
423
+ "abstained": decision["abstained"],
424
+ "selector_bundle_hash": decision["selector_bundle_hash"],
425
+ }
426
+ )
427
+ collapsed_rows = _collapse_selector_clusters(rows)
428
+ rng = random.Random(random_seed)
429
+ selector_vs_no_op = _cluster_effect_interval(
430
+ collapsed_rows,
431
+ treatment_key="selector",
432
+ baseline_key="baseline",
433
+ bootstrap_samples=bootstrap_samples,
434
+ rng=rng,
435
+ )
436
+ selector_vs_global = _cluster_effect_interval(
437
+ collapsed_rows,
438
+ treatment_key="selector",
439
+ baseline_key="global",
440
+ bootstrap_samples=bootstrap_samples,
441
+ rng=rng,
442
+ )
443
+ coverage = fmean(
444
+ 0.0 if row["abstained"] or row["selected_family"] == SemanticFamily.NO_OP.value else 1.0
445
+ for row in collapsed_rows
446
+ )
447
+ family_distribution: dict[str, int] = defaultdict(int)
448
+ for row in collapsed_rows:
449
+ family_distribution[row["selected_family"]] += 1
450
+ return {
451
+ "schema_version": 1,
452
+ "analysis": "frozen_selector_confirmation",
453
+ "metric": metric,
454
+ "record_count": len(rows),
455
+ "cluster_count": len(collapsed_rows),
456
+ "analysis_unit": (
457
+ "frozen benchmark unit; model-slot and seed outcomes are averaged "
458
+ "within unit after requiring one invariant selector decision"
459
+ ),
460
+ "coverage": coverage,
461
+ "no_op_rate": 1.0 - coverage,
462
+ "family_distribution": dict(sorted(family_distribution.items())),
463
+ "policy_value": {
464
+ "no_op_mean": fmean(row["baseline"] for row in collapsed_rows),
465
+ "locked_best_eligible_global_mean": fmean(row["global"] for row in collapsed_rows),
466
+ "selector_mean": fmean(row["selector"] for row in collapsed_rows),
467
+ "selector_vs_no_op": selector_vs_no_op,
468
+ "selector_vs_locked_global": selector_vs_global,
469
+ "selector_harm_rate_vs_no_op": fmean(
470
+ 1.0 if row["selector"] < row["baseline"] else 0.0 for row in collapsed_rows
471
+ ),
472
+ },
473
+ "risk_coverage": _risk_coverage_curve(collapsed_rows),
474
+ "gain_calibration": _gain_calibration(collapsed_rows, bins=calibration_bins),
475
+ "algorithm_failure_value": algorithm_failure_value,
476
+ "bootstrap_samples": bootstrap_samples,
477
+ "random_seed": random_seed,
478
+ "paper_claim_authorized": False,
479
+ }
480
+
481
+
482
+ def _arm_metrics(
483
+ record: Mapping[str, Any],
484
+ *,
485
+ metric: str,
486
+ algorithm_failure_value: float | None,
487
+ ) -> Mapping[str, float] | None:
488
+ result = record["result"]
489
+ if result.get("status") != "succeeded":
490
+ return None
491
+ payload = result.get("payload")
492
+ if not isinstance(payload, Mapping):
493
+ raise ArtifactError("successful experiment result payload must be an object")
494
+ arms = payload.get("arms")
495
+ if not isinstance(arms, Sequence) or isinstance(arms, (str, bytes)):
496
+ raise ArtifactError("successful experiment result payload requires an arms array")
497
+ normalized: dict[str, float] = {}
498
+ for item in arms:
499
+ if not isinstance(item, Mapping):
500
+ raise ArtifactError("experiment arm outcome must be an object")
501
+ arm_id = item.get("arm_id")
502
+ status = item.get("status")
503
+ metrics = item.get("metrics")
504
+ if not isinstance(arm_id, str) or not arm_id.strip():
505
+ raise ArtifactError("experiment arm outcome requires arm_id")
506
+ if arm_id in normalized:
507
+ raise ArtifactError("experiment result contains duplicate arm outcomes")
508
+ if status == "failed":
509
+ failure = item.get("failure")
510
+ if not isinstance(failure, Mapping) or failure.get("scope") != "algorithm":
511
+ raise ArtifactError(
512
+ f"experiment arm {arm_id} has a non-algorithm or invalid failure"
513
+ )
514
+ if algorithm_failure_value is None:
515
+ raise ArtifactError(
516
+ f"experiment arm {arm_id} failed; provide the preregistered "
517
+ "algorithm_failure_value instead of complete-case deletion"
518
+ )
519
+ normalized[arm_id] = float(algorithm_failure_value)
520
+ continue
521
+ if status != "succeeded":
522
+ raise ArtifactError(f"experiment arm {arm_id} status must be succeeded or failed")
523
+ if not isinstance(metrics, Mapping) or metric not in metrics:
524
+ raise ArtifactError(f"experiment arm {arm_id} lacks metric {metric!r}")
525
+ value = metrics[metric]
526
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
527
+ raise ArtifactError(f"experiment arm metric {metric!r} must be numeric")
528
+ numeric = float(value)
529
+ if not math.isfinite(numeric):
530
+ raise ArtifactError(f"experiment arm metric {metric!r} must be finite")
531
+ normalized[arm_id] = numeric
532
+ cell = record["cell"]
533
+ expected = cell.get("arm_ids")
534
+ if (
535
+ not isinstance(expected, Sequence)
536
+ or isinstance(expected, (str, bytes))
537
+ or any(not isinstance(item, str) for item in expected)
538
+ ):
539
+ raise ArtifactError("experiment cell lacks a valid frozen arm inventory")
540
+ if set(normalized) != set(expected) or len(normalized) != len(expected):
541
+ raise ArtifactError("experiment result arm inventory differs from the frozen plan")
542
+ return normalized
543
+
544
+
545
+ def _load_manifest(root: Path) -> ExperimentManifest:
546
+ raw = _read_object(root / "manifest.json", label="experiment manifest")
547
+ return ExperimentManifest.from_mapping(raw)
548
+
549
+
550
+ def _primary_families() -> tuple[SemanticFamily, ...]:
551
+ return (
552
+ SemanticFamily.REFORMULATE,
553
+ SemanticFamily.DECOMPOSE,
554
+ SemanticFamily.ACQUIRE,
555
+ SemanticFamily.CLARIFY,
556
+ SemanticFamily.CURATE,
557
+ )
558
+
559
+
560
+ def _confirmatory_arm_map(
561
+ manifest: ExperimentManifest,
562
+ ) -> tuple[str, Mapping[SemanticFamily, tuple[str, ...]]]:
563
+ baseline = tuple(arm.arm_id for arm in manifest.arms if arm.family is SemanticFamily.NO_OP)
564
+ if len(baseline) != 1:
565
+ raise ArtifactError("confirmatory manifest requires exactly one NO_OP arm")
566
+ by_family: dict[SemanticFamily, tuple[str, ...]] = {}
567
+ for family in _primary_families():
568
+ arm_ids = tuple(arm.arm_id for arm in manifest.arms if arm.family is family)
569
+ if len(arm_ids) != 2:
570
+ raise ArtifactError(
571
+ f"confirmatory manifest requires two frozen implementations for {family.value}"
572
+ )
573
+ by_family[family] = arm_ids
574
+ return baseline[0], by_family
575
+
576
+
577
+ def _pre_action_defect_scores(record: Mapping[str, Any]) -> Mapping[str, float]:
578
+ result = record["result"]
579
+ payload = result.get("payload")
580
+ pre_action = payload.get("pre_action") if isinstance(payload, Mapping) else None
581
+ if not isinstance(pre_action, Mapping):
582
+ raise ArtifactError("confirmatory payload requires a pre_action feature receipt")
583
+ for key in ("feature_schema_id", "feature_schema_hash", "provenance_hash"):
584
+ _required_record_text(pre_action, key, label="pre_action feature receipt")
585
+ scores = pre_action.get("defect_scores")
586
+ expected = {family.value for family in _primary_families()}
587
+ if not isinstance(scores, Mapping) or set(scores) != expected:
588
+ raise ArtifactError("pre_action defect_scores must contain exactly five families")
589
+ normalized: dict[str, float] = {}
590
+ for family, value in scores.items():
591
+ if (
592
+ isinstance(value, bool)
593
+ or not isinstance(value, (int, float))
594
+ or not math.isfinite(value)
595
+ or not 0 <= value <= 1
596
+ ):
597
+ raise ArtifactError("pre_action defect scores must be finite in [0, 1]")
598
+ normalized[family] = float(value)
599
+ return normalized
600
+
601
+
602
+ def _selector_decision(record: Mapping[str, Any]) -> Mapping[str, Any]:
603
+ payload = record["result"].get("payload")
604
+ arms = payload.get("arms") if isinstance(payload, Mapping) else None
605
+ if not isinstance(arms, Sequence) or isinstance(arms, (str, bytes)):
606
+ raise ArtifactError("selector result payload requires arms")
607
+ matches = [
608
+ item
609
+ for item in arms
610
+ if isinstance(item, Mapping) and item.get("arm_id") == "eligibility_aware_selector"
611
+ ]
612
+ if len(matches) != 1 or not isinstance(matches[0].get("decision"), Mapping):
613
+ raise ArtifactError("selector arm requires exactly one frozen decision receipt")
614
+ decision = matches[0]["decision"]
615
+ required = {
616
+ "selected_family",
617
+ "selected_release_id",
618
+ "abstained",
619
+ "predicted_gain",
620
+ "confidence",
621
+ "selector_bundle_hash",
622
+ }
623
+ if set(decision) != required:
624
+ raise ArtifactError("selector decision receipt fields are invalid")
625
+ selected_family = decision["selected_family"]
626
+ try:
627
+ family = SemanticFamily(selected_family)
628
+ except (TypeError, ValueError) as exc:
629
+ raise ArtifactError("selector decision selected_family is invalid") from exc
630
+ selected_release_id = decision["selected_release_id"]
631
+ bundle_hash = decision["selector_bundle_hash"]
632
+ if not isinstance(selected_release_id, str) or not selected_release_id:
633
+ raise ArtifactError("selector decision selected_release_id must be non-empty")
634
+ if not isinstance(bundle_hash, str) or not _is_sha256(bundle_hash):
635
+ raise ArtifactError("selector decision selector_bundle_hash must be SHA-256")
636
+ abstained = decision["abstained"]
637
+ if not isinstance(abstained, bool):
638
+ raise ArtifactError("selector decision abstained must be boolean")
639
+ predicted_gain = _finite_record_number(
640
+ decision["predicted_gain"],
641
+ label="selector predicted_gain",
642
+ )
643
+ confidence = _finite_record_number(
644
+ decision["confidence"],
645
+ label="selector confidence",
646
+ )
647
+ if not 0 <= confidence <= 1:
648
+ raise ArtifactError("selector confidence must be in [0, 1]")
649
+ if abstained != (family is SemanticFamily.NO_OP):
650
+ raise ArtifactError("selector abstention and selected_family are inconsistent")
651
+ return {
652
+ "selected_family": family.value,
653
+ "selected_release_id": selected_release_id,
654
+ "abstained": abstained,
655
+ "predicted_gain": predicted_gain,
656
+ "confidence": confidence,
657
+ "selector_bundle_hash": bundle_hash,
658
+ }
659
+
660
+
661
+ def _cluster_id(cell: Mapping[str, Any]) -> str:
662
+ benchmark = _required_record_text(cell, "benchmark_id", label="experiment cell")
663
+ split = _required_record_text(cell, "split", label="experiment cell")
664
+ unit = _required_record_text(cell, "unit_id", label="experiment cell")
665
+ return hashlib.sha256(f"{benchmark}\0{split}\0{unit}".encode()).hexdigest()
666
+
667
+
668
+ def _collapse_selector_clusters(
669
+ rows: Sequence[Mapping[str, Any]],
670
+ ) -> tuple[Mapping[str, Any], ...]:
671
+ """Collapse repeated model/seed observations to the frozen-unit estimand."""
672
+
673
+ grouped: dict[str, list[Mapping[str, Any]]] = defaultdict(list)
674
+ for row in rows:
675
+ grouped[str(row["cluster_id"])].append(row)
676
+ collapsed: list[Mapping[str, Any]] = []
677
+ decision_fields = (
678
+ "selected_family",
679
+ "selected_release_id",
680
+ "abstained",
681
+ "predicted_gain",
682
+ "confidence",
683
+ "selector_bundle_hash",
684
+ )
685
+ for cluster_id, members in sorted(grouped.items()):
686
+ reference = {field: members[0][field] for field in decision_fields}
687
+ for member in members[1:]:
688
+ if any(member[field] != value for field, value in reference.items()):
689
+ raise ArtifactError(
690
+ "selector decision changed across model/seed replicates of one "
691
+ "frozen benchmark unit"
692
+ )
693
+ baseline = fmean(float(item["baseline"]) for item in members)
694
+ selector = fmean(float(item["selector"]) for item in members)
695
+ collapsed.append(
696
+ {
697
+ "cluster_id": cluster_id,
698
+ "replicate_count": len(members),
699
+ "baseline": baseline,
700
+ "global": fmean(float(item["global"]) for item in members),
701
+ "selector": selector,
702
+ "selector_gain": selector - baseline,
703
+ **reference,
704
+ }
705
+ )
706
+ if len(collapsed) < 2:
707
+ raise ArtifactError("selector analysis requires at least two independent clusters")
708
+ return tuple(collapsed)
709
+
710
+
711
+ def _collapse_confirmatory_clusters(
712
+ rows: Sequence[tuple[str, str, float, float]],
713
+ ) -> tuple[tuple[str, float, float], ...]:
714
+ grouped: dict[str, list[tuple[float, float]]] = defaultdict(list)
715
+ for cluster_id, _model_slot, defect, effect in rows:
716
+ grouped[cluster_id].append((defect, effect))
717
+ collapsed: list[tuple[str, float, float]] = []
718
+ for cluster_id, values in sorted(grouped.items()):
719
+ defects = [item[0] for item in values]
720
+ if max(defects) - min(defects) > 1e-12:
721
+ raise ArtifactError("pre-action defect score changed across model/seed replicates")
722
+ collapsed.append((cluster_id, fmean(defects), fmean(item[1] for item in values)))
723
+ if len(collapsed) < 2:
724
+ raise ArtifactError("confirmatory analysis requires at least two independent clusters")
725
+ return tuple(collapsed)
726
+
727
+
728
+ def _linear_slope(x: Sequence[float], y: Sequence[float]) -> float:
729
+ if len(x) != len(y) or len(x) < 2:
730
+ raise ArtifactError("matched interaction slope requires paired observations")
731
+ x_mean = fmean(x)
732
+ y_mean = fmean(y)
733
+ denominator = sum((value - x_mean) ** 2 for value in x)
734
+ if denominator <= 0:
735
+ raise ArtifactError("matched defect has no between-cluster variation")
736
+ return (
737
+ sum((x_value - x_mean) * (y_value - y_mean) for x_value, y_value in zip(x, y, strict=True))
738
+ / denominator
739
+ )
740
+
741
+
742
+ def _cluster_bootstrap_interval(
743
+ values: Sequence[float],
744
+ *,
745
+ samples: int,
746
+ rng: random.Random,
747
+ ) -> tuple[float, float]:
748
+ if len(values) < 2:
749
+ raise ArtifactError("cluster bootstrap requires at least two clusters")
750
+ estimates = [fmean(values[rng.randrange(len(values))] for _ in values) for _ in range(samples)]
751
+ return _quantile_interval(estimates)
752
+
753
+
754
+ def _paired_cluster_bootstrap_slope_interval(
755
+ defects: Sequence[float],
756
+ effects: Sequence[float],
757
+ *,
758
+ samples: int,
759
+ rng: random.Random,
760
+ ) -> tuple[float, float]:
761
+ estimates: list[float] = []
762
+ attempts = 0
763
+ max_attempts = samples * 20
764
+ while len(estimates) < samples and attempts < max_attempts:
765
+ attempts += 1
766
+ indices = [rng.randrange(len(defects)) for _ in defects]
767
+ sampled_defects = [defects[index] for index in indices]
768
+ if max(sampled_defects) == min(sampled_defects):
769
+ continue
770
+ estimates.append(
771
+ _linear_slope(
772
+ sampled_defects,
773
+ [effects[index] for index in indices],
774
+ )
775
+ )
776
+ if len(estimates) < samples:
777
+ raise ArtifactError("cluster bootstrap could not preserve defect variation")
778
+ return _quantile_interval(estimates)
779
+
780
+
781
+ def _permutation_slope_p_value(
782
+ defects: Sequence[float],
783
+ effects: Sequence[float],
784
+ *,
785
+ observed: float,
786
+ samples: int,
787
+ rng: random.Random,
788
+ ) -> float:
789
+ exceedances = 0
790
+ shuffled = list(defects)
791
+ for _ in range(samples):
792
+ rng.shuffle(shuffled)
793
+ permuted = _linear_slope(shuffled, effects)
794
+ if abs(permuted) >= abs(observed) - 1e-15:
795
+ exceedances += 1
796
+ return (exceedances + 1.0) / (samples + 1.0)
797
+
798
+
799
+ def _holm_adjust(values: Mapping[str, float]) -> Mapping[str, float]:
800
+ ordered = sorted(values.items(), key=lambda item: (item[1], item[0]))
801
+ adjusted: dict[str, float] = {}
802
+ running = 0.0
803
+ total = len(ordered)
804
+ for index, (name, value) in enumerate(ordered):
805
+ running = max(running, min(1.0, (total - index) * value))
806
+ adjusted[name] = running
807
+ return adjusted
808
+
809
+
810
+ def _cluster_effect_interval(
811
+ rows: Sequence[Mapping[str, Any]],
812
+ *,
813
+ treatment_key: str,
814
+ baseline_key: str,
815
+ bootstrap_samples: int,
816
+ rng: random.Random,
817
+ ) -> Mapping[str, Any]:
818
+ grouped: dict[str, list[float]] = defaultdict(list)
819
+ for row in rows:
820
+ grouped[str(row["cluster_id"])].append(float(row[treatment_key]) - float(row[baseline_key]))
821
+ cluster_effects = [fmean(values) for _, values in sorted(grouped.items())]
822
+ return {
823
+ "cluster_count": len(cluster_effects),
824
+ "mean_effect": fmean(cluster_effects),
825
+ "cluster_bootstrap_95ci": list(
826
+ _cluster_bootstrap_interval(
827
+ cluster_effects,
828
+ samples=bootstrap_samples,
829
+ rng=rng,
830
+ )
831
+ ),
832
+ }
833
+
834
+
835
+ def _risk_coverage_curve(rows: Sequence[Mapping[str, Any]]) -> list[Mapping[str, Any]]:
836
+ confidences = sorted({float(row["confidence"]) for row in rows}, reverse=True)
837
+ if not confidences:
838
+ raise ArtifactError("selector risk-coverage requires confidence values")
839
+ indices = {
840
+ min(len(confidences) - 1, round(position * (len(confidences) - 1) / 10))
841
+ for position in range(11)
842
+ }
843
+ thresholds = [confidences[index] for index in sorted(indices)]
844
+ curve: list[Mapping[str, Any]] = []
845
+ for threshold in thresholds:
846
+ accepted = [
847
+ (not bool(row["abstained"])) and float(row["confidence"]) >= threshold for row in rows
848
+ ]
849
+ deployed = [
850
+ float(row["selector"]) if use_selector else float(row["baseline"])
851
+ for row, use_selector in zip(rows, accepted, strict=True)
852
+ ]
853
+ baseline = [float(row["baseline"]) for row in rows]
854
+ curve.append(
855
+ {
856
+ "confidence_threshold": threshold,
857
+ "coverage": fmean(1.0 if value else 0.0 for value in accepted),
858
+ "policy_value": fmean(deployed),
859
+ "mean_effect_vs_no_op": fmean(
860
+ value - reference for value, reference in zip(deployed, baseline, strict=True)
861
+ ),
862
+ "harm_rate_vs_no_op": fmean(
863
+ 1.0 if value < reference else 0.0
864
+ for value, reference in zip(deployed, baseline, strict=True)
865
+ ),
866
+ }
867
+ )
868
+ return curve
869
+
870
+
871
+ def _gain_calibration(
872
+ rows: Sequence[Mapping[str, Any]],
873
+ *,
874
+ bins: int,
875
+ ) -> Mapping[str, Any]:
876
+ ordered = sorted(
877
+ (
878
+ float(row["predicted_gain"]),
879
+ float(row["selector_gain"]),
880
+ )
881
+ for row in rows
882
+ )
883
+ bin_count = min(bins, len(ordered))
884
+ if bin_count < 2:
885
+ raise ArtifactError("selector gain calibration requires at least two records")
886
+ groups: list[list[tuple[float, float]]] = [[] for _ in range(bin_count)]
887
+ for index, item in enumerate(ordered):
888
+ groups[min(bin_count - 1, index * bin_count // len(ordered))].append(item)
889
+ payload: list[Mapping[str, Any]] = []
890
+ weighted_error = 0.0
891
+ for index, group in enumerate(groups):
892
+ predicted = fmean(item[0] for item in group)
893
+ realized = fmean(item[1] for item in group)
894
+ error = abs(predicted - realized)
895
+ weighted_error += len(group) * error
896
+ payload.append(
897
+ {
898
+ "bin": index,
899
+ "count": len(group),
900
+ "predicted_gain_mean": predicted,
901
+ "realized_gain_mean": realized,
902
+ "absolute_error": error,
903
+ }
904
+ )
905
+ return {
906
+ "bins": payload,
907
+ "expected_absolute_calibration_error": weighted_error / len(ordered),
908
+ "median_absolute_record_error": median(
909
+ abs(predicted - realized) for predicted, realized in ordered
910
+ ),
911
+ }
912
+
913
+
914
+ def _quantile_interval(values: Sequence[float]) -> tuple[float, float]:
915
+ ordered = sorted(values)
916
+ lower = ordered[max(0, math.floor(0.025 * (len(ordered) - 1)))]
917
+ upper = ordered[min(len(ordered) - 1, math.ceil(0.975 * (len(ordered) - 1)))]
918
+ return lower, upper
919
+
920
+
921
+ def _validate_resample_arguments(
922
+ *,
923
+ bootstrap_samples: int,
924
+ permutation_samples: int,
925
+ random_seed: int,
926
+ ) -> None:
927
+ for label, value, minimum in (
928
+ ("bootstrap_samples", bootstrap_samples, 100),
929
+ ("permutation_samples", permutation_samples, 1),
930
+ ("random_seed", random_seed, 0),
931
+ ):
932
+ if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
933
+ raise ValidationError(f"{label} must be an integer >= {minimum}")
934
+
935
+
936
+ def _required_record_text(
937
+ value: Mapping[str, Any],
938
+ key: str,
939
+ *,
940
+ label: str,
941
+ ) -> str:
942
+ result = value.get(key)
943
+ if not isinstance(result, str) or not result:
944
+ raise ArtifactError(f"{label} requires non-empty {key}")
945
+ return result
946
+
947
+
948
+ def _finite_record_number(value: Any, *, label: str) -> float:
949
+ if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
950
+ raise ArtifactError(f"{label} must be finite numeric")
951
+ return float(value)
952
+
953
+
954
+ def _is_sha256(value: str) -> bool:
955
+ return len(value) == 64 and all(character in "0123456789abcdef" for character in value)
956
+
957
+
958
+ def _manifest_hash(root: Path) -> str:
959
+ identity = _read_object(root / "run_identity.json", label="run identity")
960
+ raw = identity.get("identity")
961
+ if not isinstance(raw, Mapping) or not isinstance(raw.get("manifest_hash"), str):
962
+ raise ArtifactError("run identity lacks manifest_hash")
963
+ return raw["manifest_hash"]
964
+
965
+
966
+ def _read_object(path: Path, *, label: str) -> Mapping[str, Any]:
967
+ try:
968
+ value = json.loads(path.read_text(encoding="utf-8"))
969
+ except (OSError, UnicodeError, json.JSONDecodeError) as exc:
970
+ raise ArtifactError(f"{label} is missing or invalid") from exc
971
+ if not isinstance(value, Mapping):
972
+ raise ArtifactError(f"{label} must be a JSON object")
973
+ return value
974
+
975
+
976
+ __all__ = [
977
+ "analyze_confirmatory_interactions",
978
+ "analyze_selector_policy",
979
+ "load_verified_cell_results",
980
+ "summarize_paired_arm_metric",
981
+ ]