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,678 @@
1
+ """Bounded, secret-minimizing subprocess execution for worker protocols."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import math
7
+ import os
8
+ import signal
9
+ import subprocess
10
+ import threading
11
+ import time
12
+ from collections.abc import Mapping, Sequence
13
+ from contextlib import suppress
14
+ from dataclasses import dataclass, field
15
+ from typing import Any
16
+
17
+ from agentinputlab.contracts.errors import ExternalDependencyError, ValidationError
18
+ from agentinputlab.contracts.serialization import freeze_mapping
19
+
20
+ _INHERITED_ENVIRONMENT_KEYS = (
21
+ "LANG",
22
+ "LC_ALL",
23
+ "PATH",
24
+ "SYSTEMROOT",
25
+ "TEMP",
26
+ "TMP",
27
+ "TMPDIR",
28
+ "WINDIR",
29
+ )
30
+ _RESOURCE_SAMPLE_INTERVAL_SECONDS = 0.02
31
+
32
+
33
+ def _unavailable_measurement_identity(
34
+ reason: str = "runner_did_not_report",
35
+ ) -> dict[str, Any]:
36
+ return {
37
+ "process_tree": {
38
+ "status": "unavailable",
39
+ "reason": reason,
40
+ },
41
+ "accelerator": {
42
+ "status": "unavailable",
43
+ "reason": reason,
44
+ },
45
+ }
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class ProcessResourceObservation:
50
+ """Best-effort measurements from exactly one spawned process tree.
51
+
52
+ A missing value is not a measured zero. ``measurement_identity`` records
53
+ why each optional sampler was available or unavailable.
54
+ """
55
+
56
+ cpu_seconds: float | None = None
57
+ peak_rss_bytes: int | None = None
58
+ accelerator_seconds: float | None = None
59
+ accelerator_peak_memory_bytes: int | None = None
60
+ measurement_identity: Mapping[str, Any] = field(
61
+ default_factory=_unavailable_measurement_identity
62
+ )
63
+
64
+ def __post_init__(self) -> None:
65
+ for name in ("cpu_seconds", "accelerator_seconds"):
66
+ value = getattr(self, name)
67
+ if value is not None and (
68
+ isinstance(value, bool)
69
+ or not isinstance(value, (int, float))
70
+ or not math.isfinite(value)
71
+ or value < 0
72
+ ):
73
+ raise ValidationError(f"{name} must be finite and non-negative when provided")
74
+ if value is not None:
75
+ object.__setattr__(self, name, float(value))
76
+ for name in ("peak_rss_bytes", "accelerator_peak_memory_bytes"):
77
+ value = getattr(self, name)
78
+ if value is not None and (
79
+ isinstance(value, bool) or not isinstance(value, int) or value < 0
80
+ ):
81
+ raise ValidationError(f"{name} must be a non-negative integer when provided")
82
+ identity = self.measurement_identity or _unavailable_measurement_identity()
83
+ process_identity = identity.get("process_tree")
84
+ accelerator_identity = identity.get("accelerator")
85
+ if not isinstance(process_identity, Mapping) or not isinstance(
86
+ accelerator_identity,
87
+ Mapping,
88
+ ):
89
+ raise ValidationError(
90
+ "measurement_identity requires process_tree and accelerator objects"
91
+ )
92
+ if process_identity.get("status") not in {"available", "unavailable"}:
93
+ raise ValidationError("process-tree measurement status is invalid")
94
+ if accelerator_identity.get("status") not in {"available", "unavailable"}:
95
+ raise ValidationError("accelerator measurement status is invalid")
96
+ if (
97
+ self.cpu_seconds is not None or self.peak_rss_bytes is not None
98
+ ) and process_identity.get("status") == "unavailable":
99
+ raise ValidationError(
100
+ "unavailable process-tree measurements cannot contain measured values"
101
+ )
102
+ if (
103
+ self.accelerator_seconds is not None or self.accelerator_peak_memory_bytes is not None
104
+ ) and accelerator_identity.get("status") == "unavailable":
105
+ raise ValidationError(
106
+ "unavailable accelerator measurements cannot contain measured values"
107
+ )
108
+ object.__setattr__(self, "measurement_identity", freeze_mapping(identity))
109
+
110
+
111
+ @dataclass(frozen=True, slots=True)
112
+ class BoundedProcessResult:
113
+ returncode: int
114
+ stdout: str
115
+ stderr: str
116
+ cpu_seconds: float | None = None
117
+ peak_rss_bytes: int | None = None
118
+ accelerator_seconds: float | None = None
119
+ accelerator_peak_memory_bytes: int | None = None
120
+ measurement_identity: Mapping[str, Any] = field(
121
+ default_factory=_unavailable_measurement_identity
122
+ )
123
+
124
+ def __post_init__(self) -> None:
125
+ identity = self.measurement_identity
126
+ if not identity or identity == _unavailable_measurement_identity():
127
+ identity = _reported_measurement_identity(
128
+ has_process_measurement=(
129
+ self.cpu_seconds is not None or self.peak_rss_bytes is not None
130
+ ),
131
+ has_accelerator_measurement=(
132
+ self.accelerator_seconds is not None
133
+ or self.accelerator_peak_memory_bytes is not None
134
+ ),
135
+ )
136
+ observation = ProcessResourceObservation(
137
+ cpu_seconds=self.cpu_seconds,
138
+ peak_rss_bytes=self.peak_rss_bytes,
139
+ accelerator_seconds=self.accelerator_seconds,
140
+ accelerator_peak_memory_bytes=self.accelerator_peak_memory_bytes,
141
+ measurement_identity=identity,
142
+ )
143
+ object.__setattr__(self, "cpu_seconds", observation.cpu_seconds)
144
+ object.__setattr__(self, "peak_rss_bytes", observation.peak_rss_bytes)
145
+ object.__setattr__(self, "accelerator_seconds", observation.accelerator_seconds)
146
+ object.__setattr__(
147
+ self,
148
+ "accelerator_peak_memory_bytes",
149
+ observation.accelerator_peak_memory_bytes,
150
+ )
151
+ object.__setattr__(
152
+ self,
153
+ "measurement_identity",
154
+ observation.measurement_identity,
155
+ )
156
+
157
+ @property
158
+ def resource_observation(self) -> ProcessResourceObservation:
159
+ return ProcessResourceObservation(
160
+ cpu_seconds=self.cpu_seconds,
161
+ peak_rss_bytes=self.peak_rss_bytes,
162
+ accelerator_seconds=self.accelerator_seconds,
163
+ accelerator_peak_memory_bytes=self.accelerator_peak_memory_bytes,
164
+ measurement_identity=self.measurement_identity,
165
+ )
166
+
167
+
168
+ class BoundedProcessError(ExternalDependencyError):
169
+ """A bounded transport failure with any observation collected before exit."""
170
+
171
+ def __init__(
172
+ self,
173
+ message: str,
174
+ *,
175
+ resource_observation: ProcessResourceObservation | None = None,
176
+ ) -> None:
177
+ super().__init__(message)
178
+ self.resource_observation = resource_observation or ProcessResourceObservation()
179
+
180
+
181
+ class BoundedProcessTimeoutError(BoundedProcessError):
182
+ """A bounded process exceeded its hard wall-clock deadline."""
183
+
184
+
185
+ class BoundedProcessStartupError(BoundedProcessError):
186
+ """The requested executable could not be started."""
187
+
188
+
189
+ class BoundedProcessProtocolError(BoundedProcessError):
190
+ """The child process violated the bounded pipe transport."""
191
+
192
+
193
+ class BoundedProcessOutputLimitError(BoundedProcessError):
194
+ """A bounded process exceeded a configured pipe byte limit."""
195
+
196
+
197
+ class _ProcessTreeMonitor:
198
+ """Poll a spawned process tree with optional psutil and NVML dependencies."""
199
+
200
+ def __init__(self, pid: int) -> None:
201
+ self._pid = pid
202
+ self._stop = threading.Event()
203
+ self._thread: threading.Thread | None = None
204
+ self._psutil, psutil_reason = _optional_module("psutil")
205
+ self._known_processes: dict[int, Any] = {}
206
+ self._latest_cpu_by_pid: dict[int, float] = {}
207
+ self._peak_rss_bytes = 0
208
+ self._cpu_samples = 0
209
+ self._rss_samples = 0
210
+ self._process_errors = 0
211
+ self._psutil_reason = psutil_reason
212
+ if self._psutil is not None:
213
+ try:
214
+ root = self._psutil.Process(pid)
215
+ self._known_processes[int(root.pid)] = root
216
+ except Exception as exc: # optional telemetry must not break execution
217
+ self._psutil_reason = f"initialization_failed:{type(exc).__name__}"
218
+ self._psutil = None
219
+
220
+ self._nvml, nvml_reason = _optional_module("pynvml")
221
+ self._nvml_reason = nvml_reason
222
+ self._nvml_handles: tuple[Any, ...] = ()
223
+ self._nvml_initialized = False
224
+ self._nvml_failed = False
225
+ self._accelerator_samples = 0
226
+ self._accelerator_seconds = 0.0
227
+ self._accelerator_peak_memory_bytes = 0
228
+ self._last_accelerator_sample_at: float | None = None
229
+ self._last_active_device_count = 0
230
+ if self._psutil is None and self._nvml is not None:
231
+ self._nvml_reason = "psutil_required_for_process_attribution"
232
+ self._nvml = None
233
+ if self._nvml is not None:
234
+ try:
235
+ self._nvml.nvmlInit()
236
+ self._nvml_initialized = True
237
+ count = int(self._nvml.nvmlDeviceGetCount())
238
+ self._nvml_handles = tuple(
239
+ self._nvml.nvmlDeviceGetHandleByIndex(index) for index in range(count)
240
+ )
241
+ except Exception as exc: # driver/library absence is an expected optional state
242
+ self._nvml_reason = f"initialization_failed:{type(exc).__name__}"
243
+ if self._nvml_initialized:
244
+ with suppress(Exception):
245
+ self._nvml.nvmlShutdown()
246
+ self._nvml_initialized = False
247
+ self._nvml = None
248
+ self._nvml_handles = ()
249
+
250
+ def start(self) -> None:
251
+ if self._psutil is None:
252
+ return
253
+ self._sample()
254
+ self._thread = threading.Thread(
255
+ target=self._sample_loop,
256
+ name=f"agentinputlab-resource-monitor-{self._pid}",
257
+ daemon=True,
258
+ )
259
+ self._thread.start()
260
+
261
+ def stop(self) -> ProcessResourceObservation:
262
+ self._stop.set()
263
+ if self._thread is not None:
264
+ self._thread.join(timeout=max(1.0, _RESOURCE_SAMPLE_INTERVAL_SECONDS * 5))
265
+ if self._psutil is not None:
266
+ self._sample()
267
+ if self._nvml_initialized:
268
+ try:
269
+ self._nvml.nvmlShutdown()
270
+ except Exception as exc:
271
+ self._nvml_reason = f"shutdown_failed:{type(exc).__name__}"
272
+ self._nvml = None
273
+ finally:
274
+ self._nvml_initialized = False
275
+
276
+ process_available = self._psutil is not None
277
+ cpu_available = process_available and self._cpu_samples > 0
278
+ rss_available = process_available and self._rss_samples > 0
279
+ accelerator_available = (
280
+ self._nvml is not None and not self._nvml_failed and self._accelerator_samples > 0
281
+ )
282
+ cpu_reason = None if cpu_available else (self._psutil_reason or "no_successful_samples")
283
+ rss_reason = None if rss_available else (self._psutil_reason or "no_successful_samples")
284
+ accelerator_reason = (
285
+ None if accelerator_available else (self._nvml_reason or "no_successful_samples")
286
+ )
287
+ process_identity = {
288
+ "status": "available" if process_available else "unavailable",
289
+ "sampler": "psutil.process_tree.v1",
290
+ "scope": "spawned_process_tree",
291
+ "dependency_version": _module_version(self._psutil),
292
+ "sample_interval_seconds": _RESOURCE_SAMPLE_INTERVAL_SECONDS,
293
+ "samples": max(self._cpu_samples, self._rss_samples),
294
+ "sampling_errors": self._process_errors,
295
+ "cpu_seconds": {
296
+ "status": "available" if cpu_available else "unavailable",
297
+ "reason": cpu_reason,
298
+ },
299
+ "peak_rss_bytes": {
300
+ "status": "available" if rss_available else "unavailable",
301
+ "reason": rss_reason,
302
+ "semantics": "maximum sampled sum of resident bytes across the process tree",
303
+ },
304
+ }
305
+ accelerator_identity = {
306
+ "status": "available" if accelerator_available else "unavailable",
307
+ "sampler": "pynvml.process_tree_allocation.v1",
308
+ "scope": "spawned_process_tree",
309
+ "dependency_version": _module_version(self._nvml),
310
+ "reason": accelerator_reason,
311
+ "samples": self._accelerator_samples,
312
+ "seconds_semantics": "allocated device-seconds for matched process-tree PIDs",
313
+ "peak_memory_semantics": (
314
+ "maximum sampled sum of NVML process memory across matched devices"
315
+ ),
316
+ }
317
+ return ProcessResourceObservation(
318
+ cpu_seconds=(sum(self._latest_cpu_by_pid.values()) if cpu_available else None),
319
+ peak_rss_bytes=self._peak_rss_bytes if rss_available else None,
320
+ accelerator_seconds=(self._accelerator_seconds if accelerator_available else None),
321
+ accelerator_peak_memory_bytes=(
322
+ self._accelerator_peak_memory_bytes if accelerator_available else None
323
+ ),
324
+ measurement_identity={
325
+ "process_tree": process_identity,
326
+ "accelerator": accelerator_identity,
327
+ },
328
+ )
329
+
330
+ def _sample_loop(self) -> None:
331
+ while not self._stop.wait(_RESOURCE_SAMPLE_INTERVAL_SECONDS):
332
+ self._sample()
333
+
334
+ def _sample(self) -> None:
335
+ if self._psutil is None:
336
+ return
337
+ processes = list(self._known_processes.values())
338
+ for process in tuple(processes):
339
+ try:
340
+ children = process.children(recursive=True)
341
+ except Exception:
342
+ self._process_errors += 1
343
+ continue
344
+ for child in children:
345
+ pid = int(child.pid)
346
+ if pid not in self._known_processes:
347
+ self._known_processes[pid] = child
348
+ processes.append(child)
349
+
350
+ rss_total = 0
351
+ rss_observed = False
352
+ cpu_observed = False
353
+ active_pids: set[int] = set()
354
+ for process in processes:
355
+ pid = int(process.pid)
356
+ try:
357
+ cpu = process.cpu_times()
358
+ cpu_seconds = float(cpu.user) + float(cpu.system)
359
+ self._latest_cpu_by_pid[pid] = max(
360
+ cpu_seconds,
361
+ self._latest_cpu_by_pid.get(pid, 0.0),
362
+ )
363
+ cpu_observed = True
364
+ active_pids.add(pid)
365
+ except Exception:
366
+ self._process_errors += 1
367
+ try:
368
+ rss_total += int(process.memory_info().rss)
369
+ rss_observed = True
370
+ active_pids.add(pid)
371
+ except Exception:
372
+ self._process_errors += 1
373
+ if cpu_observed:
374
+ self._cpu_samples += 1
375
+ if rss_observed:
376
+ self._rss_samples += 1
377
+ self._peak_rss_bytes = max(self._peak_rss_bytes, rss_total)
378
+ self._sample_accelerators(active_pids)
379
+
380
+ def _sample_accelerators(self, active_pids: set[int]) -> None:
381
+ if self._nvml is None or self._nvml_failed:
382
+ return
383
+ now = time.monotonic()
384
+ if self._last_accelerator_sample_at is not None:
385
+ elapsed = max(0.0, now - self._last_accelerator_sample_at)
386
+ self._accelerator_seconds += elapsed * self._last_active_device_count
387
+ try:
388
+ active_devices = 0
389
+ memory_total = 0
390
+ for handle in self._nvml_handles:
391
+ matched = False
392
+ for process in _nvml_processes(self._nvml, handle):
393
+ try:
394
+ pid = int(process.pid)
395
+ except (AttributeError, TypeError, ValueError):
396
+ continue
397
+ if pid not in active_pids:
398
+ continue
399
+ matched = True
400
+ used_memory = getattr(process, "usedGpuMemory", None)
401
+ if (
402
+ isinstance(used_memory, int)
403
+ and not isinstance(used_memory, bool)
404
+ and 0 <= used_memory < (1 << 63)
405
+ ):
406
+ memory_total += used_memory
407
+ if matched:
408
+ active_devices += 1
409
+ self._last_accelerator_sample_at = now
410
+ self._last_active_device_count = active_devices
411
+ self._accelerator_peak_memory_bytes = max(
412
+ self._accelerator_peak_memory_bytes,
413
+ memory_total,
414
+ )
415
+ self._accelerator_samples += 1
416
+ except Exception as exc:
417
+ self._nvml_reason = f"sampling_failed:{type(exc).__name__}"
418
+ self._nvml_failed = True
419
+ self._last_active_device_count = 0
420
+
421
+
422
+ def _optional_module(name: str) -> tuple[Any | None, str | None]:
423
+ try:
424
+ return importlib.import_module(name), None
425
+ except ModuleNotFoundError:
426
+ return None, "dependency_missing"
427
+ except Exception as exc:
428
+ return None, f"import_failed:{type(exc).__name__}"
429
+
430
+
431
+ def _reported_measurement_identity(
432
+ *,
433
+ has_process_measurement: bool,
434
+ has_accelerator_measurement: bool,
435
+ ) -> dict[str, Any]:
436
+ return {
437
+ "process_tree": {
438
+ "status": "available" if has_process_measurement else "unavailable",
439
+ "reason": None if has_process_measurement else "runner_did_not_report",
440
+ "sampler": "injected_process_runner.v1",
441
+ "scope": "supervised_workload",
442
+ },
443
+ "accelerator": {
444
+ "status": "available" if has_accelerator_measurement else "unavailable",
445
+ "reason": None if has_accelerator_measurement else "runner_did_not_report",
446
+ "sampler": "injected_process_runner.v1",
447
+ "scope": "supervised_workload",
448
+ },
449
+ }
450
+
451
+
452
+ def _module_version(module: Any | None) -> str | None:
453
+ if module is None:
454
+ return None
455
+ value = getattr(module, "__version__", None)
456
+ return value if isinstance(value, str) and value else None
457
+
458
+
459
+ def _nvml_processes(module: Any, handle: Any) -> tuple[Any, ...]:
460
+ processes: dict[int, Any] = {}
461
+ for prefix in ("Compute", "Graphics"):
462
+ getter = None
463
+ for suffix in ("_v3", "_v2", ""):
464
+ candidate = getattr(
465
+ module,
466
+ f"nvmlDeviceGet{prefix}RunningProcesses{suffix}",
467
+ None,
468
+ )
469
+ if callable(candidate):
470
+ getter = candidate
471
+ break
472
+ if getter is None:
473
+ continue
474
+ try:
475
+ observed = getter(handle)
476
+ except Exception as exc:
477
+ not_supported = getattr(module, "NVMLError_NotSupported", ())
478
+ if isinstance(not_supported, type) and isinstance(exc, not_supported):
479
+ continue
480
+ raise
481
+ for process in observed or ():
482
+ pid = getattr(process, "pid", None)
483
+ if isinstance(pid, int) and not isinstance(pid, bool):
484
+ processes[pid] = process
485
+ return tuple(processes.values())
486
+
487
+
488
+ def minimal_worker_environment(
489
+ overrides: Mapping[str, str] | None = None,
490
+ ) -> dict[str, str]:
491
+ """Keep process bootstrap variables, but never inherit credentials."""
492
+
493
+ environment = {key: os.environ[key] for key in _INHERITED_ENVIRONMENT_KEYS if key in os.environ}
494
+ environment["PYTHONIOENCODING"] = "utf-8"
495
+ for key, value in (overrides or {}).items():
496
+ if not isinstance(key, str) or not isinstance(value, str):
497
+ raise ValidationError("worker environment keys and values must be strings")
498
+ if "\x00" in key or "\x00" in value or "=" in key:
499
+ raise ValidationError("worker environment contains an invalid key or value")
500
+ environment[key] = value
501
+ return environment
502
+
503
+
504
+ def run_bounded_process(
505
+ argv: Sequence[str],
506
+ *,
507
+ input_text: str,
508
+ timeout_seconds: float,
509
+ max_input_bytes: int,
510
+ max_stdout_bytes: int,
511
+ max_stderr_bytes: int,
512
+ cwd: str | None,
513
+ environment: Mapping[str, str] | None,
514
+ process_name: str,
515
+ ) -> BoundedProcessResult:
516
+ """Stream all pipes with hard in-memory bounds and a wall-clock timeout."""
517
+
518
+ if (
519
+ isinstance(timeout_seconds, bool)
520
+ or not isinstance(timeout_seconds, (int, float))
521
+ or not math.isfinite(timeout_seconds)
522
+ or timeout_seconds <= 0
523
+ ):
524
+ raise ValidationError("worker timeout_seconds must be finite and positive")
525
+ deadline = time.monotonic() + float(timeout_seconds)
526
+ encoded_input = input_text.encode("utf-8")
527
+ if len(encoded_input) > max_input_bytes:
528
+ raise BoundedProcessOutputLimitError(f"{process_name} request exceeds its input limit")
529
+ if min(max_input_bytes, max_stdout_bytes, max_stderr_bytes) < 1:
530
+ raise ValidationError("worker byte limits must be positive")
531
+ try:
532
+ process = subprocess.Popen(
533
+ tuple(argv),
534
+ stdin=subprocess.PIPE,
535
+ stdout=subprocess.PIPE,
536
+ stderr=subprocess.PIPE,
537
+ shell=False,
538
+ cwd=cwd,
539
+ env=minimal_worker_environment(environment),
540
+ start_new_session=os.name == "posix",
541
+ )
542
+ except FileNotFoundError as exc:
543
+ raise BoundedProcessStartupError(
544
+ f"{process_name} executable is unavailable: {argv[0]}"
545
+ ) from exc
546
+ except OSError as exc:
547
+ raise BoundedProcessStartupError(f"{process_name} could not be started") from exc
548
+
549
+ def kill_process_tree() -> None:
550
+ try:
551
+ if os.name == "posix":
552
+ os.killpg(process.pid, signal.SIGKILL)
553
+ elif process.poll() is None:
554
+ process.kill()
555
+ except ProcessLookupError:
556
+ pass
557
+
558
+ if process.stdin is None or process.stdout is None or process.stderr is None:
559
+ kill_process_tree()
560
+ raise ExternalDependencyError(f"{process_name} did not expose worker pipes")
561
+
562
+ monitor = _ProcessTreeMonitor(process.pid)
563
+ monitor.start()
564
+ stdout = bytearray()
565
+ stderr = bytearray()
566
+ overflows: list[str] = []
567
+ writer_errors: list[BaseException] = []
568
+ lock = threading.Lock()
569
+
570
+ def read_pipe(stream, output: bytearray, limit: int, label: str) -> None:
571
+ try:
572
+ while True:
573
+ remaining = limit + 1 - len(output)
574
+ chunk = stream.read(max(1, min(64 * 1024, remaining)))
575
+ if not chunk:
576
+ return
577
+ output.extend(chunk)
578
+ if len(output) > limit:
579
+ with lock:
580
+ overflows.append(label)
581
+ kill_process_tree()
582
+ return
583
+ finally:
584
+ stream.close()
585
+
586
+ def write_input() -> None:
587
+ try:
588
+ process.stdin.write(encoded_input)
589
+ process.stdin.flush()
590
+ except BrokenPipeError:
591
+ pass
592
+ except BaseException as exc: # captured and re-raised on the caller thread
593
+ writer_errors.append(exc)
594
+ kill_process_tree()
595
+ finally:
596
+ # The worker may exit between ``flush`` and ``close``. On Python
597
+ # 3.13 the buffered close performs a final flush and can surface
598
+ # the same expected EPIPE even when the explicit flush above was
599
+ # already handled. Keep that shutdown race on the writer thread
600
+ # from leaking as an unhandled-thread warning.
601
+ with suppress(BrokenPipeError):
602
+ process.stdin.close()
603
+
604
+ threads = (
605
+ threading.Thread(
606
+ target=read_pipe,
607
+ args=(process.stdout, stdout, max_stdout_bytes, "stdout"),
608
+ daemon=True,
609
+ ),
610
+ threading.Thread(
611
+ target=read_pipe,
612
+ args=(process.stderr, stderr, max_stderr_bytes, "stderr"),
613
+ daemon=True,
614
+ ),
615
+ threading.Thread(target=write_input, daemon=True),
616
+ )
617
+ for thread in threads:
618
+ thread.start()
619
+
620
+ timed_out = False
621
+ try:
622
+ remaining = deadline - time.monotonic()
623
+ if remaining <= 0:
624
+ process.wait(timeout=0)
625
+ else:
626
+ process.wait(timeout=remaining)
627
+ except subprocess.TimeoutExpired:
628
+ timed_out = True
629
+ kill_process_tree()
630
+ process.wait()
631
+ for thread in threads:
632
+ thread.join(timeout=1.0)
633
+ pipes_open = any(thread.is_alive() for thread in threads)
634
+ if pipes_open:
635
+ kill_process_tree()
636
+ for thread in threads:
637
+ thread.join(timeout=0.5)
638
+ observation = monitor.stop()
639
+
640
+ if timed_out:
641
+ raise BoundedProcessTimeoutError(
642
+ f"{process_name} exceeded {timeout_seconds}s",
643
+ resource_observation=observation,
644
+ )
645
+ if pipes_open:
646
+ raise BoundedProcessProtocolError(
647
+ f"{process_name} left worker pipes open",
648
+ resource_observation=observation,
649
+ )
650
+ if writer_errors:
651
+ raise BoundedProcessProtocolError(
652
+ f"{process_name} request pipe failed",
653
+ resource_observation=observation,
654
+ ) from writer_errors[0]
655
+ if overflows:
656
+ streams = ",".join(sorted(set(overflows)))
657
+ raise BoundedProcessOutputLimitError(
658
+ f"{process_name} exceeded its {streams} limit",
659
+ resource_observation=observation,
660
+ )
661
+ try:
662
+ stdout_text = bytes(stdout).decode("utf-8")
663
+ except UnicodeDecodeError as exc:
664
+ raise BoundedProcessProtocolError(
665
+ f"{process_name} emitted non-UTF-8 stdout",
666
+ resource_observation=observation,
667
+ ) from exc
668
+ stderr_text = bytes(stderr).decode("utf-8", errors="replace")
669
+ return BoundedProcessResult(
670
+ returncode=process.returncode,
671
+ stdout=stdout_text,
672
+ stderr=stderr_text,
673
+ cpu_seconds=observation.cpu_seconds,
674
+ peak_rss_bytes=observation.peak_rss_bytes,
675
+ accelerator_seconds=observation.accelerator_seconds,
676
+ accelerator_peak_memory_bytes=(observation.accelerator_peak_memory_bytes),
677
+ measurement_identity=observation.measurement_identity,
678
+ )