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,514 @@
1
+ """Strict, execution-neutral schemas for reproducible experiment manifests.
2
+
3
+ The schemas in this module describe work without performing it. In
4
+ particular, loading a manifest never imports an operator driver, downloads an
5
+ asset, opens a model endpoint, or creates a result directory.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from collections.abc import Mapping, Sequence
12
+ from dataclasses import dataclass, field
13
+ from typing import Any
14
+
15
+ from agentinputlab.contracts.enums import ProfileKind, SemanticFamily, StrEnum
16
+ from agentinputlab.contracts.errors import ValidationError
17
+ from agentinputlab.contracts.serialization import content_hash, freeze_mapping, to_jsonable
18
+
19
+ _IDENTIFIER = re.compile(r"^[a-z0-9][a-z0-9_.:-]*$")
20
+ _ENVIRONMENT_NAME = re.compile(r"^[A-Z_][A-Z0-9_]*$")
21
+ _ENTRYPOINT = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*:[A-Za-z_][A-Za-z0-9_.]*$")
22
+ _SHA256 = re.compile(r"^[0-9a-f]{64}$")
23
+
24
+
25
+ class ExperimentStage(StrEnum):
26
+ PARITY = "parity"
27
+ SMOKE = "smoke"
28
+ DISCOVERY = "discovery"
29
+ CONFIRMATORY = "confirmatory"
30
+ SELECTOR_CONFIRMATION = "selector_confirmation"
31
+ REPLICATION = "replication"
32
+ TRANSFER = "transfer"
33
+
34
+
35
+ class PrerequisiteKind(StrEnum):
36
+ """Kinds that can be checked without executing an experiment."""
37
+
38
+ ENV = "env"
39
+ FILE_ENV = "file_env"
40
+ DIRECTORY_ENV = "directory_env"
41
+ EXECUTABLE = "executable"
42
+
43
+
44
+ class ExperimentArmKind(StrEnum):
45
+ """Whether an arm binds one release or an already-frozen policy."""
46
+
47
+ RELEASE = "release"
48
+ POLICY = "policy"
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class ExperimentPrerequisite:
53
+ kind: PrerequisiteKind
54
+ name: str
55
+ description: str
56
+ sha256_env: str | None = None
57
+
58
+ def __post_init__(self) -> None:
59
+ if not isinstance(self.kind, PrerequisiteKind):
60
+ raise ValidationError("experiment prerequisite kind must be typed")
61
+ if not isinstance(self.name, str) or not self.name.strip():
62
+ raise ValidationError("experiment prerequisite name must be non-empty")
63
+ if not isinstance(self.description, str) or not self.description.strip():
64
+ raise ValidationError("experiment prerequisite description must be non-empty")
65
+ if self.kind is not PrerequisiteKind.EXECUTABLE and not _ENVIRONMENT_NAME.fullmatch(
66
+ self.name
67
+ ):
68
+ raise ValidationError("environment prerequisite names must be uppercase identifiers")
69
+ if self.sha256_env is not None:
70
+ if self.kind is not PrerequisiteKind.FILE_ENV:
71
+ raise ValidationError("sha256_env is valid only for file_env prerequisites")
72
+ if not _ENVIRONMENT_NAME.fullmatch(self.sha256_env):
73
+ raise ValidationError("sha256_env must be an uppercase environment identifier")
74
+
75
+ @classmethod
76
+ def from_mapping(cls, value: Any, *, path: str) -> ExperimentPrerequisite:
77
+ data = _strict_mapping(
78
+ value,
79
+ path=path,
80
+ keys={"kind", "name", "description", "sha256_env"},
81
+ required={"kind", "name", "description"},
82
+ )
83
+ return cls(
84
+ kind=_enum(PrerequisiteKind, data["kind"], f"{path}.kind"),
85
+ name=_text(data["name"], f"{path}.name"),
86
+ description=_text(data["description"], f"{path}.description"),
87
+ sha256_env=_optional_text(data.get("sha256_env"), f"{path}.sha256_env"),
88
+ )
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class ExperimentBenchmarkTarget:
93
+ """One benchmark split plus an exact or operator-supplied unit inventory."""
94
+
95
+ cell_id: str
96
+ split: str
97
+ unit_ids: tuple[str, ...] = ()
98
+ unit_inventory_env: str | None = None
99
+ unit_inventory_sha256_env: str | None = None
100
+ external: bool = False
101
+ source_revision: str | None = None
102
+ arm_ids: tuple[str, ...] = ()
103
+
104
+ def __post_init__(self) -> None:
105
+ _identity(self.cell_id, label="benchmark cell_id")
106
+ _identity(self.split, label="benchmark split")
107
+ units = tuple(self.unit_ids)
108
+ if any(not isinstance(item, str) or not item.strip() for item in units):
109
+ raise ValidationError("benchmark unit IDs must be non-empty strings")
110
+ if len(units) != len(set(units)):
111
+ raise ValidationError("benchmark unit IDs must be unique")
112
+ if bool(units) == bool(self.unit_inventory_env):
113
+ raise ValidationError(
114
+ "benchmark target requires exactly one of unit_ids or unit_inventory_env"
115
+ )
116
+ if self.unit_inventory_env is not None and not _ENVIRONMENT_NAME.fullmatch(
117
+ self.unit_inventory_env
118
+ ):
119
+ raise ValidationError("unit_inventory_env must be an uppercase identifier")
120
+ if self.unit_inventory_sha256_env is not None:
121
+ if self.unit_inventory_env is None:
122
+ raise ValidationError("unit_inventory_sha256_env requires unit_inventory_env")
123
+ if not _ENVIRONMENT_NAME.fullmatch(self.unit_inventory_sha256_env):
124
+ raise ValidationError("unit_inventory_sha256_env must be an uppercase identifier")
125
+ if not isinstance(self.external, bool):
126
+ raise ValidationError("benchmark external flag must be boolean")
127
+ if self.external and (
128
+ not isinstance(self.source_revision, str) or not self.source_revision.strip()
129
+ ):
130
+ raise ValidationError("external benchmark targets require source_revision")
131
+ if not self.external and self.source_revision is not None:
132
+ raise ValidationError("registered benchmark revisions come from the benchmark registry")
133
+ arm_ids = tuple(self.arm_ids)
134
+ if arm_ids:
135
+ if len(arm_ids) < 2:
136
+ raise ValidationError(
137
+ "benchmark-specific arm_ids require at least two comparison arms"
138
+ )
139
+ if len(arm_ids) != len(set(arm_ids)):
140
+ raise ValidationError("benchmark-specific arm_ids must be unique")
141
+ for arm_id in arm_ids:
142
+ _identity(arm_id, label="benchmark-specific arm_id")
143
+ object.__setattr__(self, "unit_ids", units)
144
+ object.__setattr__(self, "arm_ids", arm_ids)
145
+
146
+ @classmethod
147
+ def from_mapping(cls, value: Any, *, path: str) -> ExperimentBenchmarkTarget:
148
+ data = _strict_mapping(
149
+ value,
150
+ path=path,
151
+ keys={
152
+ "cell_id",
153
+ "split",
154
+ "unit_ids",
155
+ "unit_inventory_env",
156
+ "unit_inventory_sha256_env",
157
+ "external",
158
+ "source_revision",
159
+ "arm_ids",
160
+ },
161
+ required={"cell_id", "split"},
162
+ )
163
+ return cls(
164
+ cell_id=_text(data["cell_id"], f"{path}.cell_id"),
165
+ split=_text(data["split"], f"{path}.split"),
166
+ unit_ids=_string_tuple(data.get("unit_ids", []), f"{path}.unit_ids"),
167
+ unit_inventory_env=_optional_text(
168
+ data.get("unit_inventory_env"),
169
+ f"{path}.unit_inventory_env",
170
+ ),
171
+ unit_inventory_sha256_env=_optional_text(
172
+ data.get("unit_inventory_sha256_env"),
173
+ f"{path}.unit_inventory_sha256_env",
174
+ ),
175
+ external=_boolean(data.get("external", False), f"{path}.external"),
176
+ source_revision=_optional_text(
177
+ data.get("source_revision"),
178
+ f"{path}.source_revision",
179
+ ),
180
+ arm_ids=_string_tuple(data.get("arm_ids", []), f"{path}.arm_ids"),
181
+ )
182
+
183
+
184
+ @dataclass(frozen=True, slots=True)
185
+ class ExperimentArmSpec:
186
+ arm_id: str
187
+ release_id: str
188
+ family: SemanticFamily
189
+ profile_kind: ProfileKind
190
+ arm_kind: ExperimentArmKind = ExperimentArmKind.RELEASE
191
+ parameters: Mapping[str, Any] = field(default_factory=dict)
192
+
193
+ def __post_init__(self) -> None:
194
+ _identity(self.arm_id, label="experiment arm_id")
195
+ _identity(self.release_id, label="experiment release_id")
196
+ if not isinstance(self.family, SemanticFamily):
197
+ raise ValidationError("experiment arm family must be typed")
198
+ if not isinstance(self.profile_kind, ProfileKind):
199
+ raise ValidationError("experiment arm profile_kind must be typed")
200
+ if not isinstance(self.arm_kind, ExperimentArmKind):
201
+ raise ValidationError("experiment arm_kind must be typed")
202
+ if self.arm_kind is ExperimentArmKind.POLICY:
203
+ if self.family is not SemanticFamily.COMPOSITE:
204
+ raise ValidationError("experiment policy arms must use family=composite")
205
+ if not self.release_id.startswith("policy."):
206
+ raise ValidationError("experiment policy arm identity must start with policy.")
207
+ elif self.release_id.startswith("policy."):
208
+ raise ValidationError("policy.* identities require arm_kind=policy")
209
+ try:
210
+ frozen = freeze_mapping(self.parameters)
211
+ except (TypeError, ValueError) as exc:
212
+ raise ValidationError("experiment arm parameters must be canonical JSON") from exc
213
+ object.__setattr__(self, "parameters", frozen)
214
+
215
+ @classmethod
216
+ def from_mapping(cls, value: Any, *, path: str) -> ExperimentArmSpec:
217
+ data = _strict_mapping(
218
+ value,
219
+ path=path,
220
+ keys={
221
+ "arm_id",
222
+ "release_id",
223
+ "family",
224
+ "profile_kind",
225
+ "arm_kind",
226
+ "parameters",
227
+ },
228
+ required={"arm_id", "release_id", "family", "profile_kind"},
229
+ )
230
+ parameters = data.get("parameters", {})
231
+ if not isinstance(parameters, Mapping):
232
+ raise ValidationError(f"{path}.parameters must be an object")
233
+ return cls(
234
+ arm_id=_text(data["arm_id"], f"{path}.arm_id"),
235
+ release_id=_text(data["release_id"], f"{path}.release_id"),
236
+ family=_enum(SemanticFamily, data["family"], f"{path}.family"),
237
+ profile_kind=_enum(ProfileKind, data["profile_kind"], f"{path}.profile_kind"),
238
+ arm_kind=_enum(
239
+ ExperimentArmKind,
240
+ data.get("arm_kind", ExperimentArmKind.RELEASE.value),
241
+ f"{path}.arm_kind",
242
+ ),
243
+ parameters=parameters,
244
+ )
245
+
246
+
247
+ @dataclass(frozen=True, slots=True)
248
+ class ExperimentManifest:
249
+ """Frozen operator intent for one experiment family."""
250
+
251
+ schema_version: int
252
+ study_id: str
253
+ stage: ExperimentStage
254
+ estimand_profile_kind: ProfileKind
255
+ benchmarks: tuple[ExperimentBenchmarkTarget, ...]
256
+ arms: tuple[ExperimentArmSpec, ...]
257
+ seeds: tuple[int, ...]
258
+ model_slots: tuple[str, ...] = ("default",)
259
+ driver_entrypoint: str | None = None
260
+ prerequisites: tuple[ExperimentPrerequisite, ...] = ()
261
+ metadata: Mapping[str, Any] = field(default_factory=dict)
262
+
263
+ def __post_init__(self) -> None:
264
+ if self.schema_version != 1:
265
+ raise ValidationError("unsupported experiment manifest schema_version")
266
+ _identity(self.study_id, label="experiment study_id")
267
+ if not isinstance(self.stage, ExperimentStage):
268
+ raise ValidationError("experiment stage must be typed")
269
+ if not isinstance(self.estimand_profile_kind, ProfileKind):
270
+ raise ValidationError("estimand_profile_kind must be typed")
271
+ benchmarks = tuple(self.benchmarks)
272
+ arms = tuple(self.arms)
273
+ seeds = tuple(self.seeds)
274
+ model_slots = tuple(self.model_slots)
275
+ prerequisites = tuple(self.prerequisites)
276
+ if not benchmarks or not all(
277
+ isinstance(item, ExperimentBenchmarkTarget) for item in benchmarks
278
+ ):
279
+ raise ValidationError("experiment manifest requires benchmark targets")
280
+ if len({(item.cell_id, item.split) for item in benchmarks}) != len(benchmarks):
281
+ raise ValidationError("experiment benchmark target identities must be unique")
282
+ if len(arms) < 2 or not all(isinstance(item, ExperimentArmSpec) for item in arms):
283
+ raise ValidationError("experiment manifest requires at least two arms")
284
+ if len({item.arm_id for item in arms}) != len(arms):
285
+ raise ValidationError("experiment arm IDs must be unique")
286
+ declared_arm_ids = {item.arm_id for item in arms}
287
+ for benchmark in benchmarks:
288
+ unknown_arm_ids = set(benchmark.arm_ids) - declared_arm_ids
289
+ if unknown_arm_ids:
290
+ raise ValidationError(
291
+ f"benchmark {benchmark.cell_id} references unknown arm IDs: "
292
+ + ", ".join(sorted(unknown_arm_ids))
293
+ )
294
+ mixed = {item.profile_kind for item in arms}
295
+ if mixed != {self.estimand_profile_kind}:
296
+ raise ValidationError(
297
+ "one experiment manifest cannot mix official_native, controlled_adapter, "
298
+ "or factorial_adapter estimands"
299
+ )
300
+ if not seeds or any(
301
+ isinstance(seed, bool) or not isinstance(seed, int) or seed < 0 for seed in seeds
302
+ ):
303
+ raise ValidationError("experiment seeds must be non-negative integers")
304
+ if len(seeds) != len(set(seeds)):
305
+ raise ValidationError("experiment seeds must be unique")
306
+ if (
307
+ not model_slots
308
+ or len(model_slots) != len(set(model_slots))
309
+ or any(
310
+ not isinstance(model_slot, str) or not model_slot.strip()
311
+ for model_slot in model_slots
312
+ )
313
+ ):
314
+ raise ValidationError("experiment model_slots must be unique non-empty identifiers")
315
+ for model_slot in model_slots:
316
+ _identity(model_slot, label="experiment model_slot")
317
+ if self.driver_entrypoint is not None and not _ENTRYPOINT.fullmatch(self.driver_entrypoint):
318
+ raise ValidationError("driver_entrypoint must use module:attribute syntax")
319
+ if not all(isinstance(item, ExperimentPrerequisite) for item in prerequisites):
320
+ raise ValidationError("experiment prerequisites must be typed")
321
+ if len({(item.kind, item.name) for item in prerequisites}) != len(prerequisites):
322
+ raise ValidationError("experiment prerequisites must be unique")
323
+ try:
324
+ metadata = freeze_mapping(self.metadata)
325
+ except (TypeError, ValueError) as exc:
326
+ raise ValidationError("experiment metadata must be canonical JSON") from exc
327
+ object.__setattr__(self, "benchmarks", benchmarks)
328
+ object.__setattr__(self, "arms", arms)
329
+ object.__setattr__(self, "seeds", seeds)
330
+ object.__setattr__(self, "model_slots", model_slots)
331
+ object.__setattr__(self, "prerequisites", prerequisites)
332
+ object.__setattr__(self, "metadata", metadata)
333
+
334
+ @property
335
+ def manifest_hash(self) -> str:
336
+ return content_hash(self)
337
+
338
+ def to_dict(self) -> dict[str, Any]:
339
+ return to_jsonable(self)
340
+
341
+ @classmethod
342
+ def from_mapping(cls, value: Any) -> ExperimentManifest:
343
+ path = "manifest"
344
+ data = _strict_mapping(
345
+ value,
346
+ path=path,
347
+ keys={
348
+ "schema_version",
349
+ "study_id",
350
+ "stage",
351
+ "estimand_profile_kind",
352
+ "benchmarks",
353
+ "arms",
354
+ "seeds",
355
+ "model_slots",
356
+ "driver_entrypoint",
357
+ "prerequisites",
358
+ "metadata",
359
+ },
360
+ required={
361
+ "schema_version",
362
+ "study_id",
363
+ "stage",
364
+ "estimand_profile_kind",
365
+ "benchmarks",
366
+ "arms",
367
+ "seeds",
368
+ },
369
+ )
370
+ schema_version = data["schema_version"]
371
+ if isinstance(schema_version, bool) or not isinstance(schema_version, int):
372
+ raise ValidationError("manifest.schema_version must be an integer")
373
+ benchmarks = _sequence(data["benchmarks"], "manifest.benchmarks")
374
+ arms = _sequence(data["arms"], "manifest.arms")
375
+ seeds = _sequence(data["seeds"], "manifest.seeds")
376
+ model_slots = _sequence(
377
+ data.get("model_slots", ["default"]),
378
+ "manifest.model_slots",
379
+ )
380
+ prerequisites = _sequence(
381
+ data.get("prerequisites", []),
382
+ "manifest.prerequisites",
383
+ )
384
+ metadata = data.get("metadata", {})
385
+ if not isinstance(metadata, Mapping):
386
+ raise ValidationError("manifest.metadata must be an object")
387
+ return cls(
388
+ schema_version=schema_version,
389
+ study_id=_text(data["study_id"], "manifest.study_id"),
390
+ stage=_enum(ExperimentStage, data["stage"], "manifest.stage"),
391
+ estimand_profile_kind=_enum(
392
+ ProfileKind,
393
+ data["estimand_profile_kind"],
394
+ "manifest.estimand_profile_kind",
395
+ ),
396
+ benchmarks=tuple(
397
+ ExperimentBenchmarkTarget.from_mapping(
398
+ item,
399
+ path=f"manifest.benchmarks[{index}]",
400
+ )
401
+ for index, item in enumerate(benchmarks)
402
+ ),
403
+ arms=tuple(
404
+ ExperimentArmSpec.from_mapping(
405
+ item,
406
+ path=f"manifest.arms[{index}]",
407
+ )
408
+ for index, item in enumerate(arms)
409
+ ),
410
+ seeds=tuple(
411
+ _integer(item, f"manifest.seeds[{index}]") for index, item in enumerate(seeds)
412
+ ),
413
+ model_slots=tuple(
414
+ _text(item, f"manifest.model_slots[{index}]")
415
+ for index, item in enumerate(model_slots)
416
+ ),
417
+ driver_entrypoint=_optional_text(
418
+ data.get("driver_entrypoint"),
419
+ "manifest.driver_entrypoint",
420
+ ),
421
+ prerequisites=tuple(
422
+ ExperimentPrerequisite.from_mapping(
423
+ item,
424
+ path=f"manifest.prerequisites[{index}]",
425
+ )
426
+ for index, item in enumerate(prerequisites)
427
+ ),
428
+ metadata=metadata,
429
+ )
430
+
431
+
432
+ def _strict_mapping(
433
+ value: Any,
434
+ *,
435
+ path: str,
436
+ keys: set[str],
437
+ required: set[str],
438
+ ) -> Mapping[str, Any]:
439
+ if not isinstance(value, Mapping):
440
+ raise ValidationError(f"{path} must be an object")
441
+ unknown = set(value) - keys
442
+ missing = required - set(value)
443
+ if unknown:
444
+ raise ValidationError(f"{path} contains unknown fields: {', '.join(sorted(unknown))}")
445
+ if missing:
446
+ raise ValidationError(f"{path} is missing fields: {', '.join(sorted(missing))}")
447
+ return value
448
+
449
+
450
+ def _sequence(value: Any, path: str) -> Sequence[Any]:
451
+ if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
452
+ raise ValidationError(f"{path} must be an array")
453
+ return value
454
+
455
+
456
+ def _string_tuple(value: Any, path: str) -> tuple[str, ...]:
457
+ return tuple(
458
+ _text(item, f"{path}[{index}]") for index, item in enumerate(_sequence(value, path))
459
+ )
460
+
461
+
462
+ def _text(value: Any, path: str) -> str:
463
+ if not isinstance(value, str) or not value.strip() or value != value.strip():
464
+ raise ValidationError(f"{path} must be a trimmed non-empty string")
465
+ return value
466
+
467
+
468
+ def _optional_text(value: Any, path: str) -> str | None:
469
+ return None if value is None else _text(value, path)
470
+
471
+
472
+ def _integer(value: Any, path: str) -> int:
473
+ if isinstance(value, bool) or not isinstance(value, int):
474
+ raise ValidationError(f"{path} must be an integer")
475
+ return value
476
+
477
+
478
+ def _boolean(value: Any, path: str) -> bool:
479
+ if not isinstance(value, bool):
480
+ raise ValidationError(f"{path} must be a boolean")
481
+ return value
482
+
483
+
484
+ def _enum(enum_type: type[Any], value: Any, path: str) -> Any:
485
+ if not isinstance(value, str):
486
+ raise ValidationError(f"{path} must be a string")
487
+ try:
488
+ return enum_type(value)
489
+ except ValueError as exc:
490
+ allowed = ", ".join(item.value for item in enum_type)
491
+ raise ValidationError(f"{path} must be one of: {allowed}") from exc
492
+
493
+
494
+ def _identity(value: str, *, label: str) -> None:
495
+ if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
496
+ raise ValidationError(f"{label} contains unsupported characters")
497
+
498
+
499
+ def require_sha256(value: str, *, label: str) -> str:
500
+ if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
501
+ raise ValidationError(f"{label} must be a lowercase SHA-256")
502
+ return value
503
+
504
+
505
+ __all__ = [
506
+ "ExperimentArmKind",
507
+ "ExperimentArmSpec",
508
+ "ExperimentBenchmarkTarget",
509
+ "ExperimentManifest",
510
+ "ExperimentPrerequisite",
511
+ "ExperimentStage",
512
+ "PrerequisiteKind",
513
+ "require_sha256",
514
+ ]
@@ -0,0 +1,177 @@
1
+ """Fail-closed secret screening for persisted experiment artifacts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+ from agentinputlab.contracts.errors import ArtifactError
10
+
11
+ _SECRET_VALUE_PATTERNS = (
12
+ re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]{8,}"),
13
+ re.compile(r"\bsk-[A-Za-z0-9_-]{8,}"),
14
+ re.compile(r"\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b"),
15
+ re.compile(
16
+ r"(?i)([?&](?:api[_-]?key|access[_-]?token|token|password|client[_-]?secret)"
17
+ r"=)[^&#\s]+"
18
+ ),
19
+ re.compile(
20
+ r"(?i)(\b(?:api[_-]?key|x[_-]?api[_-]?key|authorization|access[_-]?token|"
21
+ r"refresh[_-]?token|password|passwd|client[_-]?secret|secret[_-]?key)"
22
+ r"\b\s*[:=]\s*)['\"]?(?:bearer\s+)?[^\s,;'\"]{4,}"
23
+ ),
24
+ )
25
+ _SECRET_KEYS = frozenset(
26
+ {
27
+ "api_key",
28
+ "apikey",
29
+ "authorization",
30
+ "access_token",
31
+ "refresh_token",
32
+ "token",
33
+ "password",
34
+ "passwd",
35
+ "client_secret",
36
+ "secret",
37
+ "secret_key",
38
+ }
39
+ )
40
+ _SECRET_ENVIRONMENT_NAME = re.compile(
41
+ r"(?i)(?:^|_)(?:api_?key|authorization|credential|password|passwd|"
42
+ r"secret|token)(?:$|_)"
43
+ )
44
+
45
+
46
+ def redact_secret_text(
47
+ value: str,
48
+ *,
49
+ secret_values: Sequence[str] = (),
50
+ limit: int = 2048,
51
+ ) -> str:
52
+ """Redact known values and common credential forms before persistence.
53
+
54
+ Pattern matching is only a fallback: opaque credentials do not necessarily
55
+ have a recognizable prefix. Process drivers therefore pass the exact
56
+ values of their explicitly allowlisted environment variables here. Empty
57
+ values are ignored. Short values are retained by
58
+ :func:`environment_secret_values` only for secret-bearing variable names.
59
+ """
60
+
61
+ result = value
62
+ known_values = {item for item in secret_values if isinstance(item, str) and item}
63
+ for secret in sorted(known_values, key=len, reverse=True):
64
+ result = result.replace(secret, "<redacted>")
65
+ for pattern in _SECRET_VALUE_PATTERNS:
66
+ result = pattern.sub(_redacted_match, result)
67
+ return result[:limit]
68
+
69
+
70
+ def environment_secret_values(
71
+ environment: Mapping[str, str],
72
+ names: Sequence[str],
73
+ ) -> tuple[str, ...]:
74
+ """Return opaque values for only the explicitly passed worker variables."""
75
+
76
+ return tuple(
77
+ dict.fromkeys(
78
+ value
79
+ for name in names
80
+ if isinstance(name, str)
81
+ and isinstance((value := environment.get(name)), str)
82
+ and value
83
+ and (len(value) >= 4 or _SECRET_ENVIRONMENT_NAME.search(name))
84
+ )
85
+ )
86
+
87
+
88
+ def require_secret_free(value: Any, *, label: str) -> None:
89
+ """Reject credentials before a fingerprint or result reaches disk."""
90
+
91
+ _walk(value, path=label)
92
+
93
+
94
+ def require_secret_values_absent(
95
+ value: Any,
96
+ *,
97
+ secret_values: Sequence[str],
98
+ label: str,
99
+ ) -> None:
100
+ """Reject opaque passed-environment values before accepting worker output."""
101
+
102
+ known_values = tuple(
103
+ sorted(
104
+ {item for item in secret_values if isinstance(item, str) and item},
105
+ key=len,
106
+ reverse=True,
107
+ )
108
+ )
109
+ if known_values:
110
+ _walk_known_values(
111
+ value,
112
+ path=label,
113
+ known_values=known_values,
114
+ )
115
+
116
+
117
+ def _walk(value: Any, *, path: str) -> None:
118
+ if isinstance(value, Mapping):
119
+ for key, item in value.items():
120
+ if not isinstance(key, str):
121
+ continue
122
+ normalized = key.casefold().replace("-", "_")
123
+ if normalized in _SECRET_KEYS:
124
+ raise ArtifactError(f"{path} contains forbidden secret field {key!r}")
125
+ _walk(item, path=f"{path}.{key}")
126
+ return
127
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
128
+ for index, item in enumerate(value):
129
+ _walk(item, path=f"{path}[{index}]")
130
+ return
131
+ if isinstance(value, str) and redact_secret_text(value, limit=len(value)) != value:
132
+ raise ArtifactError(f"{path} contains credential-shaped text")
133
+
134
+
135
+ def _walk_known_values(
136
+ value: Any,
137
+ *,
138
+ path: str,
139
+ known_values: tuple[str, ...],
140
+ ) -> None:
141
+ if isinstance(value, Mapping):
142
+ for key, item in value.items():
143
+ if isinstance(key, str):
144
+ _walk_known_values(
145
+ key,
146
+ path=f"{path}.<key>",
147
+ known_values=known_values,
148
+ )
149
+ _walk_known_values(
150
+ item,
151
+ path=f"{path}.{key}",
152
+ known_values=known_values,
153
+ )
154
+ return
155
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
156
+ for index, item in enumerate(value):
157
+ _walk_known_values(
158
+ item,
159
+ path=f"{path}[{index}]",
160
+ known_values=known_values,
161
+ )
162
+ return
163
+ if isinstance(value, str) and any(secret in value for secret in known_values):
164
+ raise ArtifactError(f"{path} contains a value copied from the worker environment")
165
+
166
+
167
+ def _redacted_match(match: re.Match[str]) -> str:
168
+ prefix = match.group(1) if match.lastindex else ""
169
+ return f"{prefix}<redacted>"
170
+
171
+
172
+ __all__ = [
173
+ "environment_secret_values",
174
+ "redact_secret_text",
175
+ "require_secret_free",
176
+ "require_secret_values_absent",
177
+ ]