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,687 @@
1
+ """Content-bound, resumable experiment artifact storage.
2
+
3
+ The store writes only after an operator has explicitly selected execution. A
4
+ resume request is accepted only when the frozen manifest, plan, and driver
5
+ fingerprint match the existing run exactly.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ import os
13
+ import tempfile
14
+ from collections.abc import Mapping
15
+ from dataclasses import dataclass
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+ from threading import RLock
19
+ from typing import Any
20
+
21
+ from agentinputlab.contracts.errors import ArtifactError, ValidationError
22
+ from agentinputlab.contracts.serialization import (
23
+ canonical_json,
24
+ freeze_mapping,
25
+ to_jsonable,
26
+ )
27
+
28
+ from .plan import ExperimentCell, ExperimentPlan, validate_experiment_plan
29
+ from .schema import ExperimentManifest, require_sha256
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class ExperimentArtifactIdentity:
34
+ study_id: str
35
+ manifest_hash: str
36
+ plan_hash: str
37
+ driver_hash: str
38
+
39
+ def __post_init__(self) -> None:
40
+ if not isinstance(self.study_id, str) or not self.study_id.strip():
41
+ raise ValidationError("experiment artifact study_id must be non-empty")
42
+ for label in ("manifest_hash", "plan_hash", "driver_hash"):
43
+ require_sha256(getattr(self, label), label=label)
44
+
45
+ @property
46
+ def identity_hash(self) -> str:
47
+ from agentinputlab.contracts.serialization import content_hash
48
+
49
+ return content_hash(self)
50
+
51
+
52
+ class ExperimentArtifactStore:
53
+ """Append-only cell results under one immutable run identity."""
54
+
55
+ def __init__(
56
+ self,
57
+ root: str | Path,
58
+ *,
59
+ manifest: ExperimentManifest,
60
+ plan: ExperimentPlan,
61
+ driver_fingerprint: Mapping[str, Any],
62
+ ) -> None:
63
+ from agentinputlab.contracts.serialization import content_hash
64
+
65
+ if plan.manifest_hash != manifest.manifest_hash:
66
+ raise ValidationError("experiment plan does not belong to manifest")
67
+ validate_experiment_plan(manifest, plan)
68
+ try:
69
+ fingerprint = freeze_mapping(driver_fingerprint)
70
+ driver_hash = content_hash(fingerprint)
71
+ except (TypeError, ValueError) as exc:
72
+ raise ValidationError("driver fingerprint must be canonical JSON") from exc
73
+ self.manifest = manifest
74
+ self.plan = plan
75
+ self.driver_fingerprint = fingerprint
76
+ self.identity = ExperimentArtifactIdentity(
77
+ study_id=manifest.study_id,
78
+ manifest_hash=manifest.manifest_hash,
79
+ plan_hash=plan.plan_hash,
80
+ driver_hash=driver_hash,
81
+ )
82
+ configured_root = Path(root).expanduser()
83
+ if not configured_root.is_absolute():
84
+ configured_root = Path(os.path.abspath(configured_root))
85
+ if configured_root.is_symlink():
86
+ raise ArtifactError("experiment output root must not be a symbolic link")
87
+ self.root = configured_root.resolve()
88
+ self.run_dir = (
89
+ self.root / manifest.study_id / manifest.manifest_hash / plan.plan_hash / driver_hash
90
+ )
91
+ self.results_dir = self.run_dir / "results"
92
+ self._lock = RLock()
93
+
94
+ def initialize(self, *, resume: bool) -> Path:
95
+ """Create a new run or verify the exact identity of an existing one."""
96
+
97
+ self._assert_run_path_confined()
98
+ identity_path = self.run_dir / "run_identity.json"
99
+ if self.run_dir.exists():
100
+ if not resume:
101
+ raise ArtifactError(
102
+ "experiment run already exists; use --resume only for the same frozen run"
103
+ )
104
+ self._verify_control_file(
105
+ identity_path,
106
+ self._identity_payload(),
107
+ label="run identity",
108
+ )
109
+ self._verify_control_file(
110
+ self.run_dir / "manifest.json",
111
+ self.manifest.to_dict(),
112
+ label="experiment manifest",
113
+ )
114
+ self._verify_control_file(
115
+ self.run_dir / "plan.json",
116
+ self.plan.to_dict(),
117
+ label="experiment plan",
118
+ )
119
+ self.verify()
120
+ return self.run_dir
121
+
122
+ if resume:
123
+ raise ArtifactError("resume run identity is missing or invalid")
124
+ self.results_dir.mkdir(parents=True, exist_ok=False)
125
+ self._assert_run_path_confined()
126
+ self._write_new(identity_path, self._identity_payload())
127
+ self._write_new(self.run_dir / "manifest.json", self.manifest.to_dict())
128
+ self._write_new(self.run_dir / "plan.json", self.plan.to_dict())
129
+ self._write_new(self.run_dir / "result_index.json", {"schema_version": 1, "results": {}})
130
+ return self.run_dir
131
+
132
+ def completed_cell_ids(self) -> frozenset[str]:
133
+ self._assert_run_path_confined()
134
+ index = self._load_index()
135
+ completed: set[str] = set()
136
+ for cell_id, expected_hash in index.items():
137
+ path = self.results_dir / f"{cell_id}.json"
138
+ if path.is_symlink():
139
+ raise ArtifactError(f"result path must not be a symbolic link: {cell_id}")
140
+ if not path.is_file():
141
+ raise ArtifactError(f"result index references a missing cell: {cell_id}")
142
+ actual_hash = _sha256_bytes(path.read_bytes())
143
+ if actual_hash != expected_hash:
144
+ raise ArtifactError(f"result checksum mismatch for cell {cell_id}")
145
+ completed.add(cell_id)
146
+ return frozenset(completed)
147
+
148
+ def result_status_counts(self) -> Mapping[str, int]:
149
+ """Return terminal status counts across all append-only cell records."""
150
+
151
+ counts = {"succeeded": 0, "failed": 0}
152
+ for cell_id in self.completed_cell_ids():
153
+ record = _read_json_object(
154
+ self.results_dir / f"{cell_id}.json",
155
+ label=f"result {cell_id}",
156
+ )
157
+ result = record.get("result")
158
+ status = result.get("status") if isinstance(result, Mapping) else None
159
+ if not isinstance(status, str) or status not in counts:
160
+ raise ArtifactError(f"result {cell_id} has an invalid terminal status")
161
+ counts[status] += 1
162
+ return freeze_mapping(counts)
163
+
164
+ def write_cell_result(self, cell_id: str, payload: Mapping[str, Any]) -> Path:
165
+ if cell_id not in {cell.cell_id for cell in self.plan.cells}:
166
+ raise ArtifactError("refusing result for a cell outside the frozen plan")
167
+ try:
168
+ serialized = (canonical_json(payload) + "\n").encode("utf-8")
169
+ except (TypeError, ValueError) as exc:
170
+ raise ArtifactError("cell result must be canonical JSON") from exc
171
+ destination = self.results_dir / f"{cell_id}.json"
172
+ with self._lock:
173
+ self._assert_run_path_confined()
174
+ index = self._load_index()
175
+ digest = _sha256_bytes(serialized)
176
+ if cell_id in index:
177
+ if index[cell_id] != digest or destination.read_bytes() != serialized:
178
+ raise ArtifactError(
179
+ "append-only result store refuses different content for a completed cell"
180
+ )
181
+ return destination
182
+ _atomic_write(destination, serialized)
183
+ index[cell_id] = digest
184
+ _atomic_write(
185
+ self.run_dir / "result_index.json",
186
+ (canonical_json({"schema_version": 1, "results": index}) + "\n").encode("utf-8"),
187
+ )
188
+ return destination
189
+
190
+ def append_event(self, payload: Mapping[str, Any]) -> None:
191
+ try:
192
+ line = (canonical_json(payload) + "\n").encode("utf-8")
193
+ except (TypeError, ValueError) as exc:
194
+ raise ArtifactError("experiment event must be canonical JSON") from exc
195
+ path = self.run_dir / "events.jsonl"
196
+ with self._lock:
197
+ self._assert_run_path_confined()
198
+ _append_no_follow(path, line)
199
+
200
+ def verify(self) -> Mapping[str, Any]:
201
+ verified = verify_experiment_run_directory(self.run_dir)
202
+ if (
203
+ verified["identity_hash"] != self.identity.identity_hash
204
+ or verified["manifest_hash"] != self.manifest.manifest_hash
205
+ or verified["plan_hash"] != self.plan.plan_hash
206
+ or verified["driver_hash"] != self.identity.driver_hash
207
+ ):
208
+ raise ArtifactError("verified run differs from the active artifact store")
209
+ return verified
210
+
211
+ def _identity_payload(self) -> Mapping[str, Any]:
212
+ return {
213
+ "schema_version": 1,
214
+ "identity": to_jsonable(self.identity),
215
+ "identity_hash": self.identity.identity_hash,
216
+ "driver_fingerprint": self.driver_fingerprint,
217
+ }
218
+
219
+ def _assert_run_path_confined(self) -> None:
220
+ """Reject existing symlinks or resolved paths outside the output root."""
221
+
222
+ try:
223
+ relative = self.run_dir.relative_to(self.root)
224
+ except ValueError as exc:
225
+ raise ArtifactError("experiment run path escapes the output root") from exc
226
+ current = self.root
227
+ if current.is_symlink():
228
+ raise ArtifactError("experiment output root must not be a symbolic link")
229
+ for part in relative.parts:
230
+ current /= part
231
+ if current.is_symlink():
232
+ raise ArtifactError(
233
+ f"experiment run path must not contain a symbolic link: {current}"
234
+ )
235
+ for label, path in (
236
+ ("run", self.run_dir),
237
+ ("results", self.results_dir),
238
+ ):
239
+ try:
240
+ path.resolve().relative_to(self.root)
241
+ except (OSError, ValueError) as exc:
242
+ raise ArtifactError(f"experiment {label} path escapes the output root") from exc
243
+
244
+ def _load_index(self) -> dict[str, str]:
245
+ path = self.run_dir / "result_index.json"
246
+ try:
247
+ raw = _read_json_object(path, label="experiment result index")
248
+ except ArtifactError as exc:
249
+ raise ArtifactError("experiment result index is invalid") from exc
250
+ if (
251
+ not isinstance(raw, dict)
252
+ or set(raw) != {"schema_version", "results"}
253
+ or raw["schema_version"] != 1
254
+ or not isinstance(raw["results"], dict)
255
+ ):
256
+ raise ArtifactError("experiment result index schema is invalid")
257
+ result: dict[str, str] = {}
258
+ for cell_id, digest in raw["results"].items():
259
+ if not isinstance(cell_id, str) or not cell_id or not isinstance(digest, str):
260
+ raise ArtifactError("experiment result index entry is invalid")
261
+ require_sha256(digest, label="result checksum")
262
+ result[cell_id] = digest
263
+ return result
264
+
265
+ def _write_new(self, path: Path, payload: Mapping[str, Any]) -> None:
266
+ if path.exists() or path.is_symlink():
267
+ raise ArtifactError(f"refusing to overwrite experiment control file: {path.name}")
268
+ _atomic_write(path, (canonical_json(payload) + "\n").encode("utf-8"))
269
+
270
+ @staticmethod
271
+ def _verify_control_file(
272
+ path: Path,
273
+ expected: Mapping[str, Any],
274
+ *,
275
+ label: str,
276
+ ) -> None:
277
+ try:
278
+ actual = _read_json_object(path, label=label)
279
+ except ArtifactError as exc:
280
+ raise ArtifactError(f"{label} is missing or invalid") from exc
281
+ if canonical_json(actual) != canonical_json(expected):
282
+ raise ArtifactError(f"{label} differs from the frozen run")
283
+
284
+
285
+ def _atomic_write(path: Path, content: bytes) -> None:
286
+ path.parent.mkdir(parents=True, exist_ok=True)
287
+ descriptor, temporary = tempfile.mkstemp(prefix=".partial-", dir=path.parent)
288
+ try:
289
+ with os.fdopen(descriptor, "wb") as handle:
290
+ handle.write(content)
291
+ handle.flush()
292
+ os.fsync(handle.fileno())
293
+ os.replace(temporary, path)
294
+ finally:
295
+ if os.path.exists(temporary):
296
+ os.unlink(temporary)
297
+
298
+
299
+ def _append_no_follow(path: Path, content: bytes) -> None:
300
+ if path.is_symlink():
301
+ raise ArtifactError("experiment event path must not be a symbolic link")
302
+ if path.exists() and not path.is_file():
303
+ raise ArtifactError("experiment event path must be a regular file")
304
+ flags = os.O_APPEND | os.O_CREAT | os.O_WRONLY
305
+ if hasattr(os, "O_NOFOLLOW"):
306
+ flags |= os.O_NOFOLLOW
307
+ try:
308
+ descriptor = os.open(path, flags, 0o600)
309
+ except OSError as exc:
310
+ raise ArtifactError("experiment event path cannot be opened safely") from exc
311
+ try:
312
+ handle = os.fdopen(descriptor, "ab")
313
+ except Exception:
314
+ os.close(descriptor)
315
+ raise
316
+ with handle:
317
+ handle.write(content)
318
+ handle.flush()
319
+ os.fsync(handle.fileno())
320
+
321
+
322
+ def _sha256_bytes(content: bytes) -> str:
323
+ return hashlib.sha256(content).hexdigest()
324
+
325
+
326
+ def verify_experiment_run_directory(path: str | Path) -> Mapping[str, Any]:
327
+ """Verify run-artifact integrity without claiming execution authenticity.
328
+
329
+ This validates paths, schemas, hashes, deterministic plan closure, and
330
+ result-record identities without importing the original driver. A valid
331
+ directory is checksum-consistent evidence, not proof that an external
332
+ worker, model, service, or official scorer actually executed as claimed.
333
+ """
334
+
335
+ from agentinputlab.contracts.serialization import content_hash
336
+
337
+ supplied_root = Path(path).expanduser()
338
+ if not supplied_root.is_absolute():
339
+ supplied_root = Path(os.path.abspath(supplied_root))
340
+ if supplied_root.is_symlink():
341
+ raise ArtifactError("experiment run directory must not be a symbolic link")
342
+ root = supplied_root.resolve()
343
+ if not root.is_dir():
344
+ raise ArtifactError("experiment run directory is missing or invalid")
345
+ identity_path = _require_internal_file(
346
+ root,
347
+ Path("run_identity.json"),
348
+ label="run identity",
349
+ )
350
+ manifest_path = _require_internal_file(
351
+ root,
352
+ Path("manifest.json"),
353
+ label="experiment manifest",
354
+ )
355
+ plan_path = _require_internal_file(
356
+ root,
357
+ Path("plan.json"),
358
+ label="experiment plan",
359
+ )
360
+ results_root = _require_internal_directory(
361
+ root,
362
+ Path("results"),
363
+ label="experiment results directory",
364
+ )
365
+ index_path = _require_internal_file(
366
+ root,
367
+ Path("result_index.json"),
368
+ label="result index",
369
+ )
370
+ identity_raw = _read_json_object(identity_path, label="run identity")
371
+ manifest_raw = _read_json_object(manifest_path, label="experiment manifest")
372
+ plan_raw = _read_json_object(plan_path, label="experiment plan")
373
+ manifest = ExperimentManifest.from_mapping(manifest_raw)
374
+ if set(plan_raw) != {
375
+ "study_id",
376
+ "manifest_hash",
377
+ "cells",
378
+ "unit_inventories",
379
+ "unit_inventory_hashes",
380
+ "parent_plan_hash",
381
+ "parent_cell_ids",
382
+ "shard_index",
383
+ "shard_count",
384
+ "plan_hash",
385
+ "cell_count",
386
+ }:
387
+ raise ArtifactError("experiment plan control schema is invalid")
388
+ raw_cells = plan_raw["cells"]
389
+ if not isinstance(raw_cells, list):
390
+ raise ArtifactError("experiment plan cells must be an array")
391
+ cells: list[ExperimentCell] = []
392
+ for raw in raw_cells:
393
+ if not isinstance(raw, Mapping) or set(raw) != {
394
+ "study_id",
395
+ "benchmark_id",
396
+ "split",
397
+ "unit_id",
398
+ "model_slot",
399
+ "seed",
400
+ "arm_ids",
401
+ "manifest_hash",
402
+ "cell_id",
403
+ }:
404
+ raise ArtifactError("experiment plan cell schema is invalid")
405
+ cell = ExperimentCell(
406
+ study_id=raw["study_id"],
407
+ benchmark_id=raw["benchmark_id"],
408
+ split=raw["split"],
409
+ unit_id=raw["unit_id"],
410
+ model_slot=raw["model_slot"],
411
+ seed=raw["seed"],
412
+ arm_ids=tuple(raw["arm_ids"]),
413
+ manifest_hash=raw["manifest_hash"],
414
+ )
415
+ if raw["cell_id"] != cell.cell_id:
416
+ raise ArtifactError("experiment plan cell hash mismatch")
417
+ cells.append(cell)
418
+ raw_inventories = plan_raw["unit_inventories"]
419
+ inventory_hashes = plan_raw["unit_inventory_hashes"]
420
+ if not isinstance(raw_inventories, Mapping):
421
+ raise ArtifactError("experiment plan unit inventories must be an object")
422
+ if not isinstance(inventory_hashes, Mapping):
423
+ raise ArtifactError("experiment plan inventory hashes must be an object")
424
+ inventories: dict[str, tuple[str, ...]] = {}
425
+ for key, raw_units in raw_inventories.items():
426
+ if not isinstance(key, str) or not key.strip() or not isinstance(raw_units, list):
427
+ raise ArtifactError("experiment plan unit inventory entry is invalid")
428
+ inventories[key] = tuple(raw_units)
429
+ parent_cell_ids = plan_raw["parent_cell_ids"]
430
+ if not isinstance(parent_cell_ids, list):
431
+ raise ArtifactError("experiment plan parent_cell_ids must be an array")
432
+ plan = ExperimentPlan(
433
+ study_id=plan_raw["study_id"],
434
+ manifest_hash=plan_raw["manifest_hash"],
435
+ cells=tuple(cells),
436
+ unit_inventories=inventories,
437
+ unit_inventory_hashes=inventory_hashes,
438
+ parent_plan_hash=plan_raw["parent_plan_hash"],
439
+ parent_cell_ids=tuple(parent_cell_ids),
440
+ shard_index=plan_raw["shard_index"],
441
+ shard_count=plan_raw["shard_count"],
442
+ )
443
+ if (
444
+ plan.study_id != manifest.study_id
445
+ or plan.manifest_hash != manifest.manifest_hash
446
+ or plan_raw["plan_hash"] != plan.plan_hash
447
+ or plan_raw["cell_count"] != len(plan.cells)
448
+ ):
449
+ raise ArtifactError("experiment plan identity differs from its manifest")
450
+ for target in manifest.benchmarks:
451
+ key = f"{target.cell_id}:{target.split}"
452
+ if target.unit_ids and tuple(sorted(target.unit_ids)) != tuple(
453
+ plan.unit_inventories.get(key, ())
454
+ ):
455
+ raise ArtifactError(
456
+ f"experiment plan unit inventory differs from inline manifest units for {key}"
457
+ )
458
+ try:
459
+ validate_experiment_plan(manifest, plan)
460
+ except (TypeError, ValidationError) as exc:
461
+ raise ArtifactError(f"experiment plan verification failed: {exc}") from exc
462
+
463
+ if (
464
+ set(identity_raw)
465
+ != {
466
+ "schema_version",
467
+ "identity",
468
+ "identity_hash",
469
+ "driver_fingerprint",
470
+ }
471
+ or identity_raw["schema_version"] != 1
472
+ ):
473
+ raise ArtifactError("run identity schema is invalid")
474
+ raw_identity = identity_raw["identity"]
475
+ fingerprint = identity_raw["driver_fingerprint"]
476
+ if not isinstance(raw_identity, Mapping) or not isinstance(fingerprint, Mapping):
477
+ raise ArtifactError("run identity payload is invalid")
478
+ if set(raw_identity) != {
479
+ "study_id",
480
+ "manifest_hash",
481
+ "plan_hash",
482
+ "driver_hash",
483
+ }:
484
+ raise ArtifactError("run identity fields are invalid")
485
+ identity = ExperimentArtifactIdentity(
486
+ study_id=raw_identity.get("study_id"),
487
+ manifest_hash=raw_identity.get("manifest_hash"),
488
+ plan_hash=raw_identity.get("plan_hash"),
489
+ driver_hash=raw_identity.get("driver_hash"),
490
+ )
491
+ if (
492
+ identity.manifest_hash != manifest.manifest_hash
493
+ or identity.study_id != manifest.study_id
494
+ or identity.plan_hash != plan.plan_hash
495
+ or identity.driver_hash != content_hash(fingerprint)
496
+ or identity_raw["identity_hash"] != identity.identity_hash
497
+ ):
498
+ raise ArtifactError("run identity hash mismatch")
499
+
500
+ index_raw = _read_json_object(index_path, label="result index")
501
+ if (
502
+ set(index_raw) != {"schema_version", "results"}
503
+ or index_raw["schema_version"] != 1
504
+ or not isinstance(index_raw["results"], Mapping)
505
+ ):
506
+ raise ArtifactError("result index schema is invalid")
507
+ planned_by_id = {cell.cell_id: cell for cell in plan.cells}
508
+ planned = set(planned_by_id)
509
+ completed: set[str] = set()
510
+ status_counts = {"succeeded": 0, "failed": 0}
511
+ for cell_id, expected in index_raw["results"].items():
512
+ if cell_id not in planned or not isinstance(expected, str):
513
+ raise ArtifactError("result index references an unplanned cell")
514
+ require_sha256(expected, label="result checksum")
515
+ result_path = _require_internal_file(
516
+ root,
517
+ Path("results") / f"{cell_id}.json",
518
+ label=f"result {cell_id}",
519
+ )
520
+ try:
521
+ result_path.relative_to(results_root)
522
+ except ValueError as exc:
523
+ raise ArtifactError(f"result {cell_id} escapes the results directory") from exc
524
+ try:
525
+ content = result_path.read_bytes()
526
+ except OSError as exc:
527
+ raise ArtifactError(f"result index references missing cell {cell_id}") from exc
528
+ if _sha256_bytes(content) != expected:
529
+ raise ArtifactError(f"result checksum mismatch for cell {cell_id}")
530
+ result_record = _decode_json_object(content, label=f"result {cell_id}")
531
+ if set(result_record) != {
532
+ "schema_version",
533
+ "study_id",
534
+ "manifest_hash",
535
+ "plan_hash",
536
+ "cell",
537
+ "result",
538
+ "finished_at_utc",
539
+ "claims",
540
+ }:
541
+ raise ArtifactError(f"result {cell_id} record schema is invalid")
542
+ if (
543
+ result_record.get("schema_version") != 1
544
+ or result_record.get("study_id") != manifest.study_id
545
+ or result_record.get("manifest_hash") != manifest.manifest_hash
546
+ or result_record.get("plan_hash") != plan.plan_hash
547
+ ):
548
+ raise ArtifactError(f"result {cell_id} control identity mismatch")
549
+ raw_cell = result_record.get("cell")
550
+ expected_cell = {
551
+ **to_jsonable(planned_by_id[cell_id]),
552
+ "cell_id": cell_id,
553
+ }
554
+ if not isinstance(raw_cell, Mapping) or canonical_json(raw_cell) != canonical_json(
555
+ expected_cell
556
+ ):
557
+ raise ArtifactError(f"result {cell_id} frozen cell identity mismatch")
558
+ _require_utc_timestamp(
559
+ result_record.get("finished_at_utc"),
560
+ label=f"result {cell_id} finished_at_utc",
561
+ )
562
+ try:
563
+ from .executor import parse_experiment_cell_result
564
+
565
+ result = parse_experiment_cell_result(
566
+ planned_by_id[cell_id],
567
+ result_record.get("result"),
568
+ )
569
+ except (TypeError, ValidationError) as exc:
570
+ raise ArtifactError(f"result {cell_id} payload is invalid: {exc}") from exc
571
+ status = result.status
572
+ claims = result_record.get("claims")
573
+ expected_claims = {
574
+ "official_parity_verified": False,
575
+ "confirmatory_candidate_record": (
576
+ manifest.stage.value == "confirmatory" and status == "succeeded"
577
+ ),
578
+ "paper_claim_authorized": False,
579
+ "resource_receipt_count": len(result.resource_receipts),
580
+ }
581
+ if not isinstance(claims, Mapping) or canonical_json(claims) != canonical_json(
582
+ expected_claims
583
+ ):
584
+ raise ArtifactError(f"result {cell_id} claim metadata is invalid")
585
+ status_counts[status] += 1
586
+ completed.add(cell_id)
587
+ return freeze_mapping(
588
+ {
589
+ "study_id": manifest.study_id,
590
+ "identity_hash": identity.identity_hash,
591
+ "manifest_hash": manifest.manifest_hash,
592
+ "plan_hash": plan.plan_hash,
593
+ "driver_hash": identity.driver_hash,
594
+ "profile_kind": manifest.estimand_profile_kind.value,
595
+ "comparison_profile_hash": content_hash(
596
+ {
597
+ "schema_version": 1,
598
+ "manifest_hash": manifest.manifest_hash,
599
+ "driver_hash": identity.driver_hash,
600
+ "profile_kind": manifest.estimand_profile_kind.value,
601
+ }
602
+ ),
603
+ "planned_cells": len(planned),
604
+ "completed_cells": len(completed),
605
+ "complete": completed == planned,
606
+ "succeeded_cells": status_counts["succeeded"],
607
+ "failed_cells": status_counts["failed"],
608
+ "all_succeeded": completed == planned and status_counts["failed"] == 0,
609
+ "is_shard": plan.parent_plan_hash is not None,
610
+ "parent_plan_hash": plan.parent_plan_hash,
611
+ "shard_index": plan.shard_index,
612
+ "shard_count": plan.shard_count,
613
+ }
614
+ )
615
+
616
+
617
+ def _require_internal_path(
618
+ root: Path,
619
+ relative: Path,
620
+ *,
621
+ label: str,
622
+ ) -> Path:
623
+ if relative.is_absolute() or ".." in relative.parts:
624
+ raise ArtifactError(f"{label} path must remain under the experiment run directory")
625
+ current = root
626
+ for part in relative.parts:
627
+ current /= part
628
+ if current.is_symlink():
629
+ raise ArtifactError(f"{label} path must not contain a symbolic link")
630
+ resolved = current.resolve()
631
+ try:
632
+ resolved.relative_to(root)
633
+ except ValueError as exc:
634
+ raise ArtifactError(f"{label} path escapes the experiment run directory") from exc
635
+ return resolved
636
+
637
+
638
+ def _require_internal_file(root: Path, relative: Path, *, label: str) -> Path:
639
+ path = _require_internal_path(root, relative, label=label)
640
+ if not path.is_file():
641
+ raise ArtifactError(f"{label} is missing or invalid")
642
+ return path
643
+
644
+
645
+ def _require_internal_directory(root: Path, relative: Path, *, label: str) -> Path:
646
+ path = _require_internal_path(root, relative, label=label)
647
+ if not path.is_dir():
648
+ raise ArtifactError(f"{label} is missing or invalid")
649
+ return path
650
+
651
+
652
+ def _decode_json_object(content: bytes, *, label: str) -> Mapping[str, Any]:
653
+ try:
654
+ value = json.loads(content)
655
+ except (UnicodeError, json.JSONDecodeError) as exc:
656
+ raise ArtifactError(f"{label} is missing or invalid") from exc
657
+ if not isinstance(value, Mapping):
658
+ raise ArtifactError(f"{label} must be a JSON object")
659
+ return value
660
+
661
+
662
+ def _read_json_object(path: Path, *, label: str) -> Mapping[str, Any]:
663
+ if path.is_symlink():
664
+ raise ArtifactError(f"{label} path must not be a symbolic link")
665
+ try:
666
+ content = path.read_bytes()
667
+ except OSError as exc:
668
+ raise ArtifactError(f"{label} is missing or invalid") from exc
669
+ return _decode_json_object(content, label=label)
670
+
671
+
672
+ def _require_utc_timestamp(value: Any, *, label: str) -> None:
673
+ if not isinstance(value, str) or not value.endswith("Z"):
674
+ raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp")
675
+ try:
676
+ parsed = datetime.fromisoformat(value[:-1] + "+00:00")
677
+ except ValueError as exc:
678
+ raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp") from exc
679
+ if parsed.utcoffset() is None or parsed.utcoffset().total_seconds() != 0:
680
+ raise ArtifactError(f"{label} must be an ISO-8601 UTC timestamp")
681
+
682
+
683
+ __all__ = [
684
+ "ExperimentArtifactIdentity",
685
+ "ExperimentArtifactStore",
686
+ "verify_experiment_run_directory",
687
+ ]