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,479 @@
1
+ """Generic fail-closed contracts for atomic external paired replay.
2
+
3
+ Stateful arenas cannot establish a fair pair by scoring two callbacks
4
+ sequentially. The external runtime must own one atomic operation that restores
5
+ the same protected origin twice, runs both arms in independent runtime
6
+ instances, invokes the locked scorer, and returns one complete receipt.
7
+
8
+ This module defines that transport-neutral boundary. It does not certify any
9
+ of the four interactive arenas as pairing-ready; each still needs a separately
10
+ verified upstream runtime bridge.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import math
16
+ import re
17
+ from collections.abc import Mapping
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Protocol, runtime_checkable
20
+
21
+ from agentinputlab.contracts.errors import ValidationError
22
+ from agentinputlab.contracts.serialization import deep_freeze_jsonable, freeze_mapping
23
+ from agentinputlab.observability import ResourceReceipt, ResourceUsage
24
+
25
+ ATOMIC_EXTERNAL_PAIR_CONTRACT = "agentinputlab.atomic_external_pair.v1"
26
+ _SHA256 = re.compile(r"^[0-9a-f]{64}$")
27
+ _ROLES = ("baseline", "treatment")
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class AtomicReplayArmRequest:
32
+ """One canonically serializable intervention artifact for external replay."""
33
+
34
+ role: str
35
+ arm_id: str
36
+ payload: Any = field(repr=False)
37
+
38
+ def __post_init__(self) -> None:
39
+ _require_role(self.role)
40
+ _require_text(self.arm_id, label="atomic replay arm_id")
41
+ try:
42
+ object.__setattr__(self, "payload", deep_freeze_jsonable(self.payload))
43
+ except (TypeError, ValueError) as exc:
44
+ raise ValidationError(
45
+ f"atomic replay arm payload must be canonically serializable: {exc}"
46
+ ) from exc
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class AtomicReplayRequest:
51
+ """Complete two-arm request bound to one protected checkpoint origin."""
52
+
53
+ benchmark_id: str
54
+ checkpoint_id: str
55
+ source_revision: str
56
+ origin_state_hash: str
57
+ replay_identity: str
58
+ seed: int
59
+ locked_dependency_hashes: Mapping[str, str]
60
+ arms: tuple[AtomicReplayArmRequest, ...]
61
+
62
+ def __post_init__(self) -> None:
63
+ for label in ("benchmark_id", "checkpoint_id", "source_revision"):
64
+ _require_text(getattr(self, label), label=f"atomic replay {label}")
65
+ _require_sha256(self.origin_state_hash, label="atomic replay origin_state_hash")
66
+ _require_sha256(self.replay_identity, label="atomic replay replay_identity")
67
+ _require_seed(self.seed)
68
+ dependencies = _validated_dependency_hashes(self.locked_dependency_hashes)
69
+ arms = tuple(self.arms)
70
+ _require_arm_types(
71
+ arms,
72
+ AtomicReplayArmRequest,
73
+ label="atomic replay request",
74
+ )
75
+ _require_complete_arms(arms, label="atomic replay request")
76
+ object.__setattr__(self, "locked_dependency_hashes", dependencies)
77
+ object.__setattr__(self, "arms", arms)
78
+
79
+
80
+ @dataclass(frozen=True, slots=True)
81
+ class AtomicReplayArmReceipt:
82
+ """Per-arm proof fields emitted by the external runtime."""
83
+
84
+ role: str
85
+ arm_id: str
86
+ origin_state_hash: str
87
+ replay_identity: str
88
+ seed: int
89
+ locked_dependency_hashes: Mapping[str, str]
90
+ runtime_instance_id: str
91
+ completed: bool
92
+
93
+ def __post_init__(self) -> None:
94
+ _require_role(self.role)
95
+ _require_text(self.arm_id, label="atomic replay receipt arm_id")
96
+ _require_sha256(
97
+ self.origin_state_hash,
98
+ label="atomic replay receipt origin_state_hash",
99
+ )
100
+ _require_sha256(
101
+ self.replay_identity,
102
+ label="atomic replay receipt replay_identity",
103
+ )
104
+ _require_seed(self.seed)
105
+ _require_sha256(
106
+ self.runtime_instance_id,
107
+ label="atomic replay receipt runtime_instance_id",
108
+ )
109
+ if self.completed is not True:
110
+ raise ValidationError("atomic replay receipt requires a completed arm")
111
+ object.__setattr__(
112
+ self,
113
+ "locked_dependency_hashes",
114
+ _validated_dependency_hashes(self.locked_dependency_hashes),
115
+ )
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class AtomicReplayReceipt:
120
+ """One indivisible receipt covering both restored runtime executions."""
121
+
122
+ benchmark_id: str
123
+ checkpoint_id: str
124
+ source_revision: str
125
+ bridge_id: str
126
+ bridge_revision: str
127
+ scorer_id: str
128
+ scorer_revision: str
129
+ origin_state_hash: str
130
+ replay_identity: str
131
+ seed: int
132
+ locked_dependency_hashes: Mapping[str, str]
133
+ arms: tuple[AtomicReplayArmReceipt, ...]
134
+
135
+ def __post_init__(self) -> None:
136
+ for label in (
137
+ "benchmark_id",
138
+ "checkpoint_id",
139
+ "source_revision",
140
+ "bridge_id",
141
+ "scorer_id",
142
+ ):
143
+ _require_text(getattr(self, label), label=f"atomic replay receipt {label}")
144
+ for label in ("bridge_revision", "scorer_revision", "origin_state_hash"):
145
+ _require_sha256(
146
+ getattr(self, label),
147
+ label=f"atomic replay receipt {label}",
148
+ )
149
+ _require_sha256(
150
+ self.replay_identity,
151
+ label="atomic replay receipt replay_identity",
152
+ )
153
+ _require_seed(self.seed)
154
+ dependencies = _validated_dependency_hashes(self.locked_dependency_hashes)
155
+ arms = tuple(self.arms)
156
+ _require_arm_types(
157
+ arms,
158
+ AtomicReplayArmReceipt,
159
+ label="atomic replay receipt",
160
+ )
161
+ _require_complete_arms(arms, label="atomic replay receipt")
162
+ if len({arm.runtime_instance_id for arm in arms}) != 2:
163
+ raise ValidationError(
164
+ "atomic replay arms must use different runtime instance identities"
165
+ )
166
+ for arm in arms:
167
+ if (
168
+ arm.origin_state_hash != self.origin_state_hash
169
+ or arm.replay_identity != self.replay_identity
170
+ or arm.seed != self.seed
171
+ or arm.locked_dependency_hashes != dependencies
172
+ ):
173
+ raise ValidationError(
174
+ "atomic replay arm receipt differs from the shared origin, seed, "
175
+ "replay identity, or locked dependencies"
176
+ )
177
+ object.__setattr__(self, "locked_dependency_hashes", dependencies)
178
+ object.__setattr__(self, "arms", arms)
179
+
180
+
181
+ @dataclass(frozen=True, slots=True)
182
+ class AtomicReplayArmResult:
183
+ """Official scorer output and execution accounting for one arm."""
184
+
185
+ role: str
186
+ arm_id: str
187
+ metrics: Mapping[str, float]
188
+ trace_hash: str
189
+ usage: ResourceUsage = field(default_factory=ResourceUsage)
190
+
191
+ def __post_init__(self) -> None:
192
+ _require_role(self.role)
193
+ _require_text(self.arm_id, label="atomic replay result arm_id")
194
+ _require_sha256(self.trace_hash, label="atomic replay result trace_hash")
195
+ if not isinstance(self.usage, ResourceUsage):
196
+ raise TypeError("atomic replay result usage must be ResourceUsage")
197
+ object.__setattr__(self, "metrics", _validated_metrics(self.metrics))
198
+
199
+
200
+ @dataclass(frozen=True, slots=True)
201
+ class AtomicReplayPairResult:
202
+ """Typed all-or-nothing result returned by an atomic bridge call."""
203
+
204
+ receipt: AtomicReplayReceipt
205
+ arms: tuple[AtomicReplayArmResult, ...]
206
+ resource_receipts: tuple[ResourceReceipt, ...] = ()
207
+
208
+ def __post_init__(self) -> None:
209
+ if not isinstance(self.receipt, AtomicReplayReceipt):
210
+ raise TypeError("atomic replay pair result requires AtomicReplayReceipt")
211
+ arms = tuple(self.arms)
212
+ _require_arm_types(
213
+ arms,
214
+ AtomicReplayArmResult,
215
+ label="atomic replay pair result",
216
+ )
217
+ _require_complete_arms(arms, label="atomic replay pair result")
218
+ expected = {(arm.role, arm.arm_id) for arm in self.receipt.arms}
219
+ actual = {(arm.role, arm.arm_id) for arm in arms}
220
+ if actual != expected:
221
+ raise ValidationError(
222
+ "atomic replay result arms differ from the completed receipt arms"
223
+ )
224
+ metric_keys = tuple(sorted(arms[0].metrics))
225
+ if tuple(sorted(arms[1].metrics)) != metric_keys:
226
+ raise ValidationError("atomic replay arm metric schemas differ")
227
+ resource_receipts = tuple(self.resource_receipts)
228
+ if any(not isinstance(item, ResourceReceipt) for item in resource_receipts):
229
+ raise TypeError(
230
+ "atomic replay pair resource_receipts must contain ResourceReceipt values"
231
+ )
232
+ object.__setattr__(self, "arms", arms)
233
+ object.__setattr__(self, "resource_receipts", resource_receipts)
234
+
235
+
236
+ @runtime_checkable
237
+ class AtomicExternalReplayBridge(Protocol):
238
+ """Distinct protocol that a legacy single-arm bridge cannot satisfy."""
239
+
240
+ atomic_replay_contract: str
241
+ benchmark_id: str
242
+ source_revision: str
243
+ bridge_id: str
244
+ bridge_revision: str
245
+ scorer_id: str
246
+ scorer_revision: str
247
+ locked_dependency_hashes: Mapping[str, str]
248
+
249
+ def evaluate_pair(self, request: AtomicReplayRequest) -> AtomicReplayPairResult: ...
250
+
251
+
252
+ @dataclass(frozen=True, slots=True)
253
+ class AtomicPairedReplayRecord:
254
+ """Validated paired result with evaluator-derived metric deltas."""
255
+
256
+ receipt: AtomicReplayReceipt
257
+ baseline: AtomicReplayArmResult
258
+ treatment: AtomicReplayArmResult
259
+ metric_deltas: Mapping[str, float]
260
+ resource_receipts: tuple[ResourceReceipt, ...] = ()
261
+
262
+ def __post_init__(self) -> None:
263
+ if not isinstance(self.receipt, AtomicReplayReceipt):
264
+ raise TypeError("atomic paired replay record requires AtomicReplayReceipt")
265
+ if not isinstance(self.baseline, AtomicReplayArmResult) or not isinstance(
266
+ self.treatment,
267
+ AtomicReplayArmResult,
268
+ ):
269
+ raise TypeError("atomic paired replay record requires typed arm results")
270
+ if self.baseline.role != "baseline" or self.treatment.role != "treatment":
271
+ raise ValidationError("atomic paired replay record has incorrect arm roles")
272
+ receipt_arms = {(arm.role, arm.arm_id) for arm in self.receipt.arms}
273
+ result_arms = {
274
+ (self.baseline.role, self.baseline.arm_id),
275
+ (self.treatment.role, self.treatment.arm_id),
276
+ }
277
+ if result_arms != receipt_arms:
278
+ raise ValidationError(
279
+ "atomic paired replay record arms differ from the completed receipt"
280
+ )
281
+ expected = tuple(sorted(self.baseline.metrics))
282
+ if tuple(sorted(self.treatment.metrics)) != expected:
283
+ raise ValidationError("atomic paired replay metric schemas differ")
284
+ deltas = _validated_metrics(self.metric_deltas)
285
+ if tuple(sorted(deltas)) != expected:
286
+ raise ValidationError("atomic paired replay delta schema differs from arm metrics")
287
+ for key in expected:
288
+ if deltas[key] != self.treatment.metrics[key] - self.baseline.metrics[key]:
289
+ raise ValidationError(f"atomic paired replay metric delta {key!r} is inconsistent")
290
+ resource_receipts = tuple(self.resource_receipts)
291
+ if any(not isinstance(item, ResourceReceipt) for item in resource_receipts):
292
+ raise TypeError(
293
+ "atomic paired replay resource_receipts must contain ResourceReceipt values"
294
+ )
295
+ object.__setattr__(self, "metric_deltas", deltas)
296
+ object.__setattr__(self, "resource_receipts", resource_receipts)
297
+
298
+
299
+ class AtomicExternalReplayRunner:
300
+ """Call a verified atomic bridge exactly once and validate its full receipt."""
301
+
302
+ def run(
303
+ self,
304
+ bridge: AtomicExternalReplayBridge,
305
+ request: AtomicReplayRequest,
306
+ ) -> AtomicPairedReplayRecord:
307
+ if not isinstance(request, AtomicReplayRequest):
308
+ raise TypeError("atomic replay request must be AtomicReplayRequest")
309
+ if not isinstance(bridge, AtomicExternalReplayBridge):
310
+ raise TypeError(
311
+ "bridge must implement AtomicExternalReplayBridge.evaluate_pair(); "
312
+ "single-arm evaluation bridges are not accepted"
313
+ )
314
+ bridge_dependencies = _validate_bridge(bridge)
315
+ if (
316
+ bridge.benchmark_id != request.benchmark_id
317
+ or bridge.source_revision != request.source_revision
318
+ or bridge_dependencies != request.locked_dependency_hashes
319
+ ):
320
+ raise ValidationError(
321
+ "atomic bridge identity or locked dependencies differ from the request"
322
+ )
323
+
324
+ result = bridge.evaluate_pair(request)
325
+ if not isinstance(result, AtomicReplayPairResult):
326
+ raise ValidationError(
327
+ "atomic external bridge must return AtomicReplayPairResult; "
328
+ "partial or bare metric results are not accepted"
329
+ )
330
+ _validate_receipt_against_request(result.receipt, request, bridge)
331
+ by_role = {arm.role: arm for arm in result.arms}
332
+ baseline = by_role["baseline"]
333
+ treatment = by_role["treatment"]
334
+ resource_receipts = result.resource_receipts
335
+ bridge_resource_receipt = getattr(bridge, "last_resource_receipt", None)
336
+ if bridge_resource_receipt is not None:
337
+ if not isinstance(bridge_resource_receipt, ResourceReceipt):
338
+ raise TypeError(
339
+ "atomic bridge last_resource_receipt must be a ResourceReceipt or null"
340
+ )
341
+ if bridge_resource_receipt not in resource_receipts:
342
+ resource_receipts = (*resource_receipts, bridge_resource_receipt)
343
+ return AtomicPairedReplayRecord(
344
+ receipt=result.receipt,
345
+ baseline=baseline,
346
+ treatment=treatment,
347
+ metric_deltas={
348
+ key: treatment.metrics[key] - baseline.metrics[key] for key in baseline.metrics
349
+ },
350
+ resource_receipts=resource_receipts,
351
+ )
352
+
353
+
354
+ def _validate_bridge(
355
+ bridge: AtomicExternalReplayBridge,
356
+ ) -> Mapping[str, str]:
357
+ if bridge.atomic_replay_contract != ATOMIC_EXTERNAL_PAIR_CONTRACT:
358
+ raise ValidationError("atomic external bridge contract version is unsupported")
359
+ for label in ("benchmark_id", "source_revision", "bridge_id", "scorer_id"):
360
+ _require_text(getattr(bridge, label), label=f"atomic bridge {label}")
361
+ _require_sha256(bridge.bridge_revision, label="atomic bridge bridge_revision")
362
+ _require_sha256(bridge.scorer_revision, label="atomic bridge scorer_revision")
363
+ return _validated_dependency_hashes(bridge.locked_dependency_hashes)
364
+
365
+
366
+ def _validate_receipt_against_request(
367
+ receipt: AtomicReplayReceipt,
368
+ request: AtomicReplayRequest,
369
+ bridge: AtomicExternalReplayBridge,
370
+ ) -> None:
371
+ expected = {
372
+ "benchmark_id": request.benchmark_id,
373
+ "checkpoint_id": request.checkpoint_id,
374
+ "source_revision": request.source_revision,
375
+ "bridge_id": bridge.bridge_id,
376
+ "bridge_revision": bridge.bridge_revision,
377
+ "scorer_id": bridge.scorer_id,
378
+ "scorer_revision": bridge.scorer_revision,
379
+ "origin_state_hash": request.origin_state_hash,
380
+ "replay_identity": request.replay_identity,
381
+ "seed": request.seed,
382
+ }
383
+ for field_name, value in expected.items():
384
+ if getattr(receipt, field_name) != value:
385
+ raise ValidationError(
386
+ f"atomic replay receipt {field_name} differs from its request or bridge"
387
+ )
388
+ if receipt.locked_dependency_hashes != request.locked_dependency_hashes:
389
+ raise ValidationError("atomic replay receipt locked dependencies differ from request")
390
+ requested_arms = {(arm.role, arm.arm_id) for arm in request.arms}
391
+ received_arms = {(arm.role, arm.arm_id) for arm in receipt.arms}
392
+ if requested_arms != received_arms:
393
+ raise ValidationError("atomic replay receipt arms differ from request arms")
394
+
395
+
396
+ def _require_complete_arms(arms: tuple[Any, ...], *, label: str) -> None:
397
+ if len(arms) != 2:
398
+ raise ValidationError(f"{label} requires exactly two arms")
399
+ roles = tuple(sorted(getattr(arm, "role", "") for arm in arms))
400
+ if roles != _ROLES:
401
+ raise ValidationError(f"{label} requires baseline and treatment roles")
402
+ identifiers = [getattr(arm, "arm_id", "") for arm in arms]
403
+ if len(set(identifiers)) != 2:
404
+ raise ValidationError(f"{label} requires unique arm IDs")
405
+
406
+
407
+ def _require_arm_types(
408
+ arms: tuple[Any, ...],
409
+ expected_type: type[Any],
410
+ *,
411
+ label: str,
412
+ ) -> None:
413
+ if not all(isinstance(arm, expected_type) for arm in arms):
414
+ raise TypeError(f"{label} requires {expected_type.__name__} instances")
415
+
416
+
417
+ def _require_role(value: Any) -> None:
418
+ if value not in _ROLES:
419
+ raise ValidationError("atomic replay arm role must be baseline or treatment")
420
+
421
+
422
+ def _validated_dependency_hashes(value: Mapping[str, str]) -> Mapping[str, str]:
423
+ if not isinstance(value, Mapping) or not value:
424
+ raise ValidationError("atomic replay locked_dependency_hashes must be non-empty")
425
+ dependencies: dict[str, str] = {}
426
+ for name, digest in value.items():
427
+ _require_text(name, label="atomic replay dependency name")
428
+ _require_sha256(digest, label=f"atomic replay dependency {name!r}")
429
+ dependencies[name] = digest
430
+ return freeze_mapping(dependencies)
431
+
432
+
433
+ def _validated_metrics(value: Mapping[str, float]) -> Mapping[str, float]:
434
+ if not isinstance(value, Mapping) or not value:
435
+ raise ValidationError("atomic replay metrics must be a non-empty mapping")
436
+ metrics: dict[str, float] = {}
437
+ for name, raw in value.items():
438
+ _require_text(name, label="atomic replay metric name")
439
+ if isinstance(raw, bool) or not isinstance(raw, (int, float)):
440
+ raise ValidationError(f"atomic replay metric {name!r} must be numeric")
441
+ numeric = float(raw)
442
+ if not math.isfinite(numeric):
443
+ raise ValidationError(f"atomic replay metric {name!r} must be finite")
444
+ metrics[name] = numeric
445
+ return freeze_mapping(metrics)
446
+
447
+
448
+ def _require_text(value: Any, *, label: str) -> None:
449
+ if (
450
+ not isinstance(value, str)
451
+ or not value.strip()
452
+ or value != value.strip()
453
+ or any(not character.isprintable() for character in value)
454
+ ):
455
+ raise ValidationError(f"{label} must be trimmed, non-empty printable text")
456
+
457
+
458
+ def _require_sha256(value: Any, *, label: str) -> None:
459
+ if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
460
+ raise ValidationError(f"{label} must be a lowercase SHA-256 digest")
461
+
462
+
463
+ def _require_seed(value: Any) -> None:
464
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
465
+ raise ValidationError("atomic replay seed must be a non-negative integer")
466
+
467
+
468
+ __all__ = [
469
+ "ATOMIC_EXTERNAL_PAIR_CONTRACT",
470
+ "AtomicExternalReplayBridge",
471
+ "AtomicExternalReplayRunner",
472
+ "AtomicPairedReplayRecord",
473
+ "AtomicReplayArmReceipt",
474
+ "AtomicReplayArmRequest",
475
+ "AtomicReplayArmResult",
476
+ "AtomicReplayPairResult",
477
+ "AtomicReplayReceipt",
478
+ "AtomicReplayRequest",
479
+ ]