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
workers/ace/driver.py ADDED
@@ -0,0 +1,1682 @@
1
+ #!/usr/bin/env python3
2
+ """Checksum-pinned JSONL driver for upstream ACE generic text tasks.
3
+
4
+ The worker deliberately keeps deployment configuration outside request payloads:
5
+ the operator supplies one verified checkout and one explicit LLM configuration at
6
+ process startup. Requests can contain examples and bounded scientific controls,
7
+ but never a module, callable, command, endpoint, or filesystem path.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import ast
14
+ import contextlib
15
+ import functools
16
+ import hashlib
17
+ import importlib
18
+ import io
19
+ import json
20
+ import math
21
+ import os
22
+ import random
23
+ import re
24
+ import subprocess
25
+ import sys
26
+ import tempfile
27
+ import threading
28
+ import urllib.parse
29
+ from collections.abc import Mapping, Sequence
30
+ from dataclasses import dataclass
31
+ from pathlib import Path, PurePosixPath
32
+ from typing import Any, TextIO
33
+
34
+ UPSTREAM_COMMIT = "bcb7cea0504afad6f55fec4845dd4864c9f9eee7"
35
+ PROTOCOL_VERSION = "agentinputlab.ace.offline.v1"
36
+ _HASH_RE = re.compile(r"[0-9a-f]{64}")
37
+ _COMMIT_RE = re.compile(r"[0-9a-f]{40}")
38
+ _REF_RE = re.compile(r"refs/[A-Za-z0-9._/-]+")
39
+ _ENV_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
40
+ _PLAYBOOK_LINE_RE = re.compile(r"\[([^\]]+)\]\s*helpful=(\d+)\s*harmful=(\d+)\s*::\s*(.*)")
41
+ _ROLE_NAMES = ("target", "reflector", "curator")
42
+ _PROVIDERS = frozenset(("openai", "sambanova", "together", "commonstack"))
43
+ _STANDARD_SECTIONS = (
44
+ "STRATEGIES & INSIGHTS",
45
+ "FORMULAS & CALCULATIONS",
46
+ "CODE SNIPPETS & TEMPLATES",
47
+ "COMMON MISTAKES TO AVOID",
48
+ "PROBLEM-SOLVING HEURISTICS",
49
+ "CONTEXT CLUES & INDICATORS",
50
+ "OTHERS",
51
+ )
52
+ _MAX_JSONL_BYTES = 8 * 1024 * 1024
53
+ _MAX_UPSTREAM_STDOUT_BYTES = 1024 * 1024
54
+ _UPSTREAM_MODULES = ("ace", "llm", "logger", "playbook_utils", "utils")
55
+ _OPENAI_CONSTRUCTOR_ENV_LOCK = threading.RLock()
56
+
57
+
58
+ class WorkerFailure(RuntimeError):
59
+ """Expected fail-closed worker error with a stable public code."""
60
+
61
+ def __init__(self, code: str, message: str) -> None:
62
+ super().__init__(message)
63
+ self.code = code
64
+
65
+
66
+ class BoundedHashSink(io.TextIOBase):
67
+ """Discard upstream text while retaining only bounded audit metadata."""
68
+
69
+ def __init__(self, max_bytes: int = _MAX_UPSTREAM_STDOUT_BYTES) -> None:
70
+ super().__init__()
71
+ self.max_bytes = max_bytes
72
+ self.byte_count = 0
73
+ self._digest = hashlib.sha256()
74
+
75
+ def writable(self) -> bool:
76
+ return True
77
+
78
+ def write(self, value: str) -> int:
79
+ if not isinstance(value, str):
80
+ raise TypeError("upstream stdout must be text")
81
+ encoded = value.encode("utf-8", errors="replace")
82
+ if self.byte_count + len(encoded) > self.max_bytes:
83
+ raise WorkerFailure(
84
+ "UPSTREAM_OUTPUT_LIMIT_EXCEEDED",
85
+ "ACE upstream stdout exceeded the bounded diagnostic limit",
86
+ )
87
+ self._digest.update(encoded)
88
+ self.byte_count += len(encoded)
89
+ return len(value)
90
+
91
+ def flush(self) -> None:
92
+ return None
93
+
94
+ @property
95
+ def sha256(self) -> str:
96
+ return self._digest.hexdigest()
97
+
98
+
99
+ @dataclass(frozen=True, slots=True)
100
+ class CompatibilityPolicy:
101
+ commit: str
102
+ license_path: str
103
+ license_spdx: str
104
+ license_sha256: str
105
+ source_hashes: Mapping[str, str]
106
+ require_clean_checkout: bool
107
+
108
+ @classmethod
109
+ def from_mapping(cls, value: Mapping[str, Any]) -> CompatibilityPolicy:
110
+ upstream = _require_mapping(value.get("upstream"), "compatibility upstream")
111
+ sources = _require_mapping(value.get("source_files"), "compatibility source_files")
112
+ commit = _require_text(upstream.get("commit"), "compatibility commit")
113
+ license_path = _require_text(upstream.get("license_path"), "compatibility license_path")
114
+ license_spdx = _require_text(upstream.get("license_spdx"), "compatibility license_spdx")
115
+ license_sha256 = _require_hash(
116
+ upstream.get("license_sha256"), "compatibility license_sha256"
117
+ )
118
+ require_clean_checkout = upstream.get("require_clean_checkout")
119
+ if not isinstance(require_clean_checkout, bool):
120
+ raise WorkerFailure(
121
+ "INVALID_COMPATIBILITY_MANIFEST",
122
+ "ACE compatibility policy must declare require_clean_checkout",
123
+ )
124
+ if commit != UPSTREAM_COMMIT or license_spdx != "Apache-2.0":
125
+ raise WorkerFailure(
126
+ "INVALID_COMPATIBILITY_MANIFEST",
127
+ "ACE compatibility policy must pin the approved commit and Apache-2.0",
128
+ )
129
+ source_hashes: dict[str, str] = {}
130
+ for raw_path, raw_hash in sources.items():
131
+ path = _safe_relative_path(raw_path, "compatibility source path")
132
+ source_hashes[path] = _require_hash(raw_hash, f"compatibility hash for {path}")
133
+ if not source_hashes:
134
+ raise WorkerFailure(
135
+ "INVALID_COMPATIBILITY_MANIFEST",
136
+ "ACE compatibility policy requires source hashes",
137
+ )
138
+ return cls(
139
+ commit=commit,
140
+ license_path=_safe_relative_path(license_path, "compatibility license path"),
141
+ license_spdx=license_spdx,
142
+ license_sha256=license_sha256,
143
+ source_hashes=source_hashes,
144
+ require_clean_checkout=require_clean_checkout,
145
+ )
146
+
147
+
148
+ @dataclass(frozen=True, slots=True)
149
+ class RoleLLMConfig:
150
+ model: str
151
+ fingerprint: Mapping[str, Any]
152
+ input_cost_per_million: float
153
+ output_cost_per_million: float
154
+
155
+
156
+ @dataclass(frozen=True, slots=True)
157
+ class LLMConfig:
158
+ api_provider: str
159
+ max_tokens_per_call: int
160
+ json_mode: bool
161
+ roles: Mapping[str, RoleLLMConfig]
162
+ base_url: str | None = None
163
+ api_key_env: str | None = None
164
+ extra_body: Mapping[str, Any] | None = None
165
+
166
+ @classmethod
167
+ def from_json(cls, raw: str) -> LLMConfig:
168
+ value = _loads_strict_json(raw)
169
+ root = _require_mapping(value, "LLM configuration")
170
+ required = {"api_provider", "max_tokens_per_call", "json_mode", "roles"}
171
+ allowed = required | {"base_url", "api_key_env", "extra_body"}
172
+ if not required.issubset(root) or not set(root).issubset(allowed):
173
+ raise WorkerFailure(
174
+ "INVALID_SCHEMA", "LLM configuration fields do not match the protocol"
175
+ )
176
+ provider = _require_text(root["api_provider"], "LLM api_provider")
177
+ if provider not in _PROVIDERS:
178
+ raise WorkerFailure(
179
+ "INVALID_LLM_CONFIG",
180
+ f"LLM api_provider must be one of {sorted(_PROVIDERS)}",
181
+ )
182
+ max_tokens = _require_positive_int(root["max_tokens_per_call"], "LLM max_tokens_per_call")
183
+ json_mode = root["json_mode"]
184
+ if not isinstance(json_mode, bool):
185
+ raise WorkerFailure("INVALID_LLM_CONFIG", "LLM json_mode must be boolean")
186
+ raw_roles = _require_mapping(root["roles"], "LLM roles")
187
+ if set(raw_roles) != set(_ROLE_NAMES):
188
+ raise WorkerFailure(
189
+ "INVALID_LLM_CONFIG",
190
+ "LLM roles must bind target, reflector, and curator exactly",
191
+ )
192
+ roles: dict[str, RoleLLMConfig] = {}
193
+ for role in _ROLE_NAMES:
194
+ role_value = _require_mapping(raw_roles[role], f"LLM role {role}")
195
+ _require_exact_keys(
196
+ role_value,
197
+ {
198
+ "model",
199
+ "fingerprint",
200
+ "input_cost_per_million",
201
+ "output_cost_per_million",
202
+ },
203
+ f"LLM role {role}",
204
+ )
205
+ fingerprint = _require_mapping(role_value["fingerprint"], f"LLM {role} fingerprint")
206
+ if not fingerprint:
207
+ raise WorkerFailure(
208
+ "INVALID_LLM_CONFIG", f"LLM {role} fingerprint must be non-empty"
209
+ )
210
+ _canonical_json(fingerprint)
211
+ roles[role] = RoleLLMConfig(
212
+ model=_require_text(role_value["model"], f"LLM {role} model"),
213
+ fingerprint=dict(fingerprint),
214
+ input_cost_per_million=_require_non_negative_float(
215
+ role_value["input_cost_per_million"],
216
+ f"LLM {role} input_cost_per_million",
217
+ ),
218
+ output_cost_per_million=_require_non_negative_float(
219
+ role_value["output_cost_per_million"],
220
+ f"LLM {role} output_cost_per_million",
221
+ ),
222
+ )
223
+ base_url_value = root.get("base_url")
224
+ api_key_env_value = root.get("api_key_env")
225
+ if (base_url_value is None) != (api_key_env_value is None):
226
+ raise WorkerFailure(
227
+ "INVALID_LLM_CONFIG",
228
+ "LLM base_url and api_key_env must be supplied together",
229
+ )
230
+ base_url: str | None = None
231
+ api_key_env: str | None = None
232
+ if base_url_value is not None:
233
+ if provider != "openai":
234
+ raise WorkerFailure(
235
+ "INVALID_LLM_CONFIG",
236
+ "custom OpenAI-compatible endpoints require api_provider=openai",
237
+ )
238
+ base_url = _require_http_url(base_url_value, "LLM base_url")
239
+ api_key_env = _require_env_name(api_key_env_value, "LLM api_key_env")
240
+ extra_body_value = root.get("extra_body")
241
+ extra_body: Mapping[str, Any] | None = None
242
+ if extra_body_value is not None:
243
+ if base_url is None:
244
+ raise WorkerFailure(
245
+ "INVALID_LLM_CONFIG",
246
+ "LLM extra_body is permitted only for an operator-bound endpoint",
247
+ )
248
+ extra_body = dict(_require_mapping(extra_body_value, "LLM extra_body"))
249
+ _canonical_json(extra_body)
250
+ return cls(
251
+ api_provider=provider,
252
+ max_tokens_per_call=max_tokens,
253
+ json_mode=json_mode,
254
+ roles=roles,
255
+ base_url=base_url,
256
+ api_key_env=api_key_env,
257
+ extra_body=extra_body,
258
+ )
259
+
260
+
261
+ @dataclass(frozen=True, slots=True)
262
+ class Controls:
263
+ max_rounds: int
264
+ max_model_calls: int
265
+ max_tokens: int
266
+ max_cost_usd: float
267
+ seed: int
268
+
269
+
270
+ @dataclass(frozen=True, slots=True)
271
+ class ValidatedRequest:
272
+ request_id: str
273
+ train_examples: tuple[Mapping[str, Any], ...]
274
+ validation_examples: tuple[Mapping[str, Any], ...]
275
+ initial_bullets: tuple[dict[str, Any], ...]
276
+ controls: Controls
277
+ split_audit_hash: str
278
+
279
+
280
+ class GenericTextProcessor:
281
+ """Minimal task-agnostic exact-match processor required by upstream ACE."""
282
+
283
+ @staticmethod
284
+ def answer_is_correct(predicted: str, ground_truth: str) -> bool:
285
+ return predicted.strip().casefold() == ground_truth.strip().casefold()
286
+
287
+ def evaluate_accuracy(self, predictions: Sequence[str], ground_truths: Sequence[str]) -> float:
288
+ if len(predictions) != len(ground_truths):
289
+ raise ValueError("predictions and ground truths must have equal length")
290
+ if not predictions:
291
+ return 0.0
292
+ correct = sum(
293
+ self.answer_is_correct(prediction, truth)
294
+ for prediction, truth in zip(predictions, ground_truths, strict=True)
295
+ )
296
+ return correct / len(predictions)
297
+
298
+
299
+ class ProviderAttemptCompletions:
300
+ """Transparent completion proxy that enforces the shared attempt budget."""
301
+
302
+ def __init__(self, delegate: Any, tracker: UsageTracker, role: str) -> None:
303
+ self._delegate = delegate
304
+ self._tracker = tracker
305
+ self._role = role
306
+
307
+ def create(self, *args: Any, **kwargs: Any) -> Any:
308
+ self._tracker.before_provider_attempt(self._role)
309
+ try:
310
+ result = self._delegate.create(*args, **kwargs)
311
+ except Exception:
312
+ self._tracker.failed_provider_attempt()
313
+ raise
314
+ self._tracker.after_provider_response(self._role, result)
315
+ return result
316
+
317
+ def __getattr__(self, name: str) -> Any:
318
+ return getattr(self._delegate, name)
319
+
320
+
321
+ class ProviderAttemptChat:
322
+ """Transparent chat proxy retaining all non-completion client behavior."""
323
+
324
+ def __init__(self, delegate: Any, tracker: UsageTracker, role: str) -> None:
325
+ self._delegate = delegate
326
+ self.completions = ProviderAttemptCompletions(delegate.completions, tracker, role)
327
+
328
+ def __getattr__(self, name: str) -> Any:
329
+ return getattr(self._delegate, name)
330
+
331
+
332
+ class ProviderAttemptClient:
333
+ """Transparent upstream client proxy with a counted completion seam."""
334
+
335
+ def __init__(self, delegate: Any, tracker: UsageTracker, role: str) -> None:
336
+ self._delegate = delegate
337
+ self.chat = ProviderAttemptChat(delegate.chat, tracker, role)
338
+
339
+ def __getattr__(self, name: str) -> Any:
340
+ return getattr(self._delegate, name)
341
+
342
+
343
+ class CompletionDefaults:
344
+ """Inject immutable deployment-only completion fields before observation."""
345
+
346
+ def __init__(self, delegate: Any, extra_body: Mapping[str, Any]) -> None:
347
+ self._delegate = delegate
348
+ self._extra_body = dict(extra_body)
349
+
350
+ def create(self, *args: Any, **kwargs: Any) -> Any:
351
+ if "extra_body" in kwargs:
352
+ raise WorkerFailure(
353
+ "UPSTREAM_INTERFACE_MISMATCH",
354
+ "upstream ACE unexpectedly supplied request-level extra_body",
355
+ )
356
+ if (
357
+ "seed" in self._extra_body
358
+ and "seed" in kwargs
359
+ and (type(kwargs["seed"]) is not int or kwargs["seed"] != self._extra_body["seed"])
360
+ ):
361
+ raise WorkerFailure(
362
+ "UPSTREAM_INTERFACE_MISMATCH", "upstream ACE seed differs from the fit controls"
363
+ )
364
+ return self._delegate.create(*args, extra_body=dict(self._extra_body), **kwargs)
365
+
366
+ def __getattr__(self, name: str) -> Any:
367
+ return getattr(self._delegate, name)
368
+
369
+
370
+ class CompletionDefaultChat:
371
+ def __init__(self, delegate: Any, extra_body: Mapping[str, Any]) -> None:
372
+ self._delegate = delegate
373
+ self.completions = CompletionDefaults(delegate.completions, extra_body)
374
+
375
+ def __getattr__(self, name: str) -> Any:
376
+ return getattr(self._delegate, name)
377
+
378
+
379
+ class CompletionDefaultClient:
380
+ def __init__(self, delegate: Any, extra_body: Mapping[str, Any]) -> None:
381
+ self._delegate = delegate
382
+ self.chat = CompletionDefaultChat(delegate.chat, extra_body)
383
+
384
+ def __getattr__(self, name: str) -> Any:
385
+ return getattr(self._delegate, name)
386
+
387
+
388
+ class UsageTracker:
389
+ """Observe upstream logical calls, provider attempts, and returned usage."""
390
+
391
+ def __init__(self, controls: Controls, llm_config: LLMConfig) -> None:
392
+ self.controls = controls
393
+ self.llm_config = llm_config
394
+ self.model_calls = 0
395
+ self.input_tokens = 0
396
+ self.output_tokens = 0
397
+ self.cost_usd = 0.0
398
+ self.call_sequence: list[str] = []
399
+ self.provider_attempt_sequence: list[str] = []
400
+ self.provider_input_tokens = 0
401
+ self.provider_output_tokens = 0
402
+ self.provider_cost_usd = 0.0
403
+ self.provider_usage_responses = 0
404
+ self.failed_provider_attempts = 0
405
+ self.first_component_failure: WorkerFailure | None = None
406
+ self._lock = threading.Lock()
407
+
408
+ def install(self, ace_system: Any) -> None:
409
+ self._install_component(ace_system.generator, "generate", "target")
410
+ self._install_component(ace_system.reflector, "reflect", "reflector")
411
+ self._install_component(ace_system.curator, "curate", "curator")
412
+
413
+ def _install_component(self, component: Any, method_name: str, role: str) -> None:
414
+ client = getattr(component, "api_client", None)
415
+ chat = getattr(client, "chat", None)
416
+ completions = getattr(chat, "completions", None)
417
+ if not callable(getattr(completions, "create", None)):
418
+ raise WorkerFailure(
419
+ "UPSTREAM_INTERFACE_MISMATCH",
420
+ f"upstream ACE {role} lacks api_client.chat.completions.create()",
421
+ )
422
+ component.api_client = ProviderAttemptClient(client, self, role)
423
+ self._wrap(component, method_name, role)
424
+
425
+ def _wrap(self, component: Any, method_name: str, role: str) -> None:
426
+ original = getattr(component, method_name, None)
427
+ if not callable(original):
428
+ raise WorkerFailure(
429
+ "UPSTREAM_INTERFACE_MISMATCH",
430
+ f"upstream ACE {role} does not expose {method_name}()",
431
+ )
432
+
433
+ @functools.wraps(original)
434
+ def observed(*args: Any, **kwargs: Any) -> Any:
435
+ self._before_component_call(role)
436
+ try:
437
+ result = original(*args, **kwargs)
438
+ self._after_call(role, result)
439
+ except WorkerFailure as exc:
440
+ # Native validation may catch this and then fail in its own
441
+ # empty-results branch. Keep the original guard failure/costs.
442
+ with self._lock:
443
+ if self.first_component_failure is None:
444
+ self.first_component_failure = exc
445
+ raise
446
+ return result
447
+
448
+ setattr(component, method_name, observed)
449
+
450
+ def _before_component_call(self, role: str) -> None:
451
+ with self._lock:
452
+ self.call_sequence.append(role)
453
+
454
+ def before_provider_attempt(self, role: str) -> None:
455
+ with self._lock:
456
+ if self.model_calls >= self.controls.max_model_calls:
457
+ raise WorkerFailure(
458
+ "MODEL_CALL_BUDGET_EXCEEDED",
459
+ "ACE reached max_model_calls before the next provider attempt",
460
+ )
461
+ self.model_calls += 1
462
+ self.provider_attempt_sequence.append(role)
463
+
464
+ def _after_call(self, role: str, result: Any) -> None:
465
+ if not isinstance(result, tuple) or not result:
466
+ raise WorkerFailure(
467
+ "UPSTREAM_USAGE_UNAVAILABLE",
468
+ f"upstream ACE {role} returned an unexpected result",
469
+ )
470
+ info = result[-1]
471
+ if not isinstance(info, Mapping):
472
+ raise WorkerFailure(
473
+ "UPSTREAM_USAGE_UNAVAILABLE",
474
+ f"upstream ACE {role} omitted call metadata",
475
+ )
476
+ input_tokens = _require_non_negative_int(
477
+ info.get("prompt_num_tokens"), f"upstream {role} prompt_num_tokens"
478
+ )
479
+ output_tokens = _require_non_negative_int(
480
+ info.get("response_num_tokens"), f"upstream {role} response_num_tokens"
481
+ )
482
+ expected_model = self.llm_config.roles[role].model
483
+ if info.get("model") != expected_model:
484
+ raise WorkerFailure(
485
+ "UPSTREAM_MODEL_MISMATCH",
486
+ f"upstream {role} reported a model other than the configured model",
487
+ )
488
+ pricing = self.llm_config.roles[role]
489
+ cost = (
490
+ input_tokens * pricing.input_cost_per_million
491
+ + output_tokens * pricing.output_cost_per_million
492
+ ) / 1_000_000
493
+ with self._lock:
494
+ self.input_tokens += input_tokens
495
+ self.output_tokens += output_tokens
496
+ self.cost_usd += cost
497
+ if self.input_tokens + self.output_tokens > self.controls.max_tokens:
498
+ raise WorkerFailure(
499
+ "TOKEN_BUDGET_EXCEEDED", "ACE exceeded the request token budget"
500
+ )
501
+ if self.cost_usd > self.controls.max_cost_usd:
502
+ raise WorkerFailure("COST_BUDGET_EXCEEDED", "ACE exceeded the request cost budget")
503
+
504
+ def failed_provider_attempt(self) -> None:
505
+ with self._lock:
506
+ self.failed_provider_attempts += 1
507
+
508
+ def after_provider_response(self, role: str, response: Any) -> None:
509
+ # Observe SDK usage before upstream JSON parsing can fail or retry.
510
+ # Older native test doubles may expose usage only in component metadata;
511
+ # that existing path remains available, without double-counting tokens.
512
+ usage = (
513
+ response.get("usage")
514
+ if isinstance(response, Mapping)
515
+ else getattr(response, "usage", None)
516
+ )
517
+ if usage is None:
518
+ return
519
+ read = usage.get if isinstance(usage, Mapping) else lambda key: getattr(usage, key, None)
520
+ incoming = _require_non_negative_int(read("prompt_tokens"), "provider prompt_tokens")
521
+ outgoing = _require_non_negative_int(
522
+ read("completion_tokens"), "provider completion_tokens"
523
+ )
524
+ pricing = self.llm_config.roles[role]
525
+ with self._lock:
526
+ self.provider_input_tokens += incoming
527
+ self.provider_output_tokens += outgoing
528
+ self.provider_usage_responses += 1
529
+ self.provider_cost_usd += (
530
+ incoming * pricing.input_cost_per_million
531
+ + outgoing * pricing.output_cost_per_million
532
+ ) / 1_000_000
533
+ if self.provider_input_tokens + self.provider_output_tokens > self.controls.max_tokens:
534
+ raise WorkerFailure(
535
+ "TOKEN_BUDGET_EXCEEDED", "ACE exceeded the request token budget"
536
+ )
537
+ if self.provider_cost_usd > self.controls.max_cost_usd:
538
+ raise WorkerFailure("COST_BUDGET_EXCEEDED", "ACE exceeded the request cost budget")
539
+
540
+ def require_upstream_chain(self) -> None:
541
+ self._require_sequence(self.call_sequence, "agent")
542
+ self._require_sequence(self.provider_attempt_sequence, "provider-attempt")
543
+
544
+ @staticmethod
545
+ def _require_sequence(sequence: Sequence[str], label: str) -> None:
546
+ try:
547
+ first_target = sequence.index("target")
548
+ first_reflector = sequence.index("reflector")
549
+ first_curator = sequence.index("curator")
550
+ except ValueError as exc:
551
+ raise WorkerFailure(
552
+ "UPSTREAM_CHAIN_INCOMPLETE",
553
+ f"ACE did not execute the complete {label} chain",
554
+ ) from exc
555
+ if not first_target < first_reflector < first_curator:
556
+ raise WorkerFailure(
557
+ "UPSTREAM_CHAIN_REORDERED",
558
+ f"ACE did not preserve Generator -> Reflector -> Curator {label} order",
559
+ )
560
+
561
+ def as_dict(self) -> dict[str, int | float]:
562
+ # Provider and component totals describe the same successful requests.
563
+ # Taking their maximum retains pre-parse costs without summing twice.
564
+ incoming = max(self.input_tokens, self.provider_input_tokens)
565
+ outgoing = max(self.output_tokens, self.provider_output_tokens)
566
+ return {
567
+ "model_calls": self.model_calls,
568
+ "input_tokens": incoming,
569
+ "output_tokens": outgoing,
570
+ "tokens": incoming + outgoing,
571
+ "cost_usd": max(self.cost_usd, self.provider_cost_usd),
572
+ "provider_usage_responses": self.provider_usage_responses,
573
+ "failed_provider_attempts_with_unknown_tokens": self.failed_provider_attempts,
574
+ }
575
+
576
+
577
+ class ACEWorkerRuntime:
578
+ """One fixed checkout and LLM configuration serving many JSONL requests."""
579
+
580
+ def __init__(
581
+ self,
582
+ checkout: Path,
583
+ llm_config: LLMConfig,
584
+ *,
585
+ policy: CompatibilityPolicy | None = None,
586
+ data_processor: Any | None = None,
587
+ ) -> None:
588
+ if data_processor is not None:
589
+ for name in ("answer_is_correct", "evaluate_accuracy", "fingerprint"):
590
+ if not callable(getattr(data_processor, name, None)):
591
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", f"task processor lacks {name}()")
592
+ fingerprint = data_processor.fingerprint()
593
+ if not isinstance(fingerprint, Mapping):
594
+ raise WorkerFailure(
595
+ "INVALID_TASK_PROCESSOR", "task processor fingerprint must be an object"
596
+ )
597
+ self.task_processor_fingerprint = json.loads(_canonical_json(fingerprint))
598
+ else:
599
+ self.task_processor_fingerprint = None
600
+ self.data_processor = (
601
+ data_processor if data_processor is not None else GenericTextProcessor()
602
+ )
603
+ self.policy = policy or load_compatibility_policy()
604
+ self.checkout = verify_checkout(checkout, self.policy)
605
+ self.llm_config = llm_config
606
+ self.ace_class, self.playbook_utils = load_upstream(self.checkout)
607
+ self.last_tracker: UsageTracker | None = None
608
+
609
+ @contextlib.contextmanager
610
+ def _bound_constructor_environment(self):
611
+ """Let native initialization use the explicitly configured provider.
612
+
613
+ Upstream initializes clients before the adapter can replace them. Its
614
+ constructor reads the default OpenAI environment names even when the
615
+ operator has chosen another key name. Bind those names for construction
616
+ only; restore the worker's environment on success and on failure.
617
+ """
618
+ with _OPENAI_CONSTRUCTOR_ENV_LOCK:
619
+ if self.llm_config.base_url is None:
620
+ yield
621
+ return
622
+ key_name = self.llm_config.api_key_env
623
+ api_key = os.environ.get(key_name, "") if key_name else ""
624
+ if not api_key:
625
+ raise WorkerFailure(
626
+ "MISSING_API_KEY",
627
+ f"ACE OpenAI-compatible runtime requires environment {key_name}",
628
+ )
629
+ values = {"OPENAI_API_KEY": api_key, "OPENAI_BASE_URL": self.llm_config.base_url}
630
+ previous = {name: os.environ.get(name) for name in values}
631
+ try:
632
+ os.environ.update(values)
633
+ yield
634
+ finally:
635
+ for name, old in previous.items():
636
+ if old is None:
637
+ os.environ.pop(name, None)
638
+ else:
639
+ os.environ[name] = old
640
+
641
+ def run(self, payload: Mapping[str, Any]) -> dict[str, Any]:
642
+ self.last_tracker = None
643
+ request = validate_request(payload, self.llm_config)
644
+ validator = getattr(self.data_processor, "validate_examples", None)
645
+ if callable(validator):
646
+ try:
647
+ validator(request.train_examples, request.validation_examples)
648
+ except (TypeError, ValueError) as exc:
649
+ raise WorkerFailure(
650
+ "INVALID_EXAMPLES", "task processor rejected the fit groups"
651
+ ) from exc
652
+ if (
653
+ self.task_processor_fingerprint is not None
654
+ and json.loads(_canonical_json(self.data_processor.fingerprint()))
655
+ != self.task_processor_fingerprint
656
+ ):
657
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", "task processor fingerprint changed")
658
+ initial_text = render_playbook(request.initial_bullets)
659
+ roles = self.llm_config.roles
660
+ diagnostics = BoundedHashSink()
661
+ with contextlib.redirect_stdout(diagnostics):
662
+ with self._bound_constructor_environment():
663
+ ace_system = self.ace_class(
664
+ api_provider=self.llm_config.api_provider,
665
+ generator_model=roles["target"].model,
666
+ reflector_model=roles["reflector"].model,
667
+ curator_model=roles["curator"].model,
668
+ max_tokens=self.llm_config.max_tokens_per_call,
669
+ initial_playbook=initial_text,
670
+ use_bulletpoint_analyzer=False,
671
+ )
672
+ if self.llm_config.base_url is not None:
673
+ self._bind_openai_compatible_clients(ace_system, seed=request.controls.seed)
674
+ get_next_id = getattr(self.playbook_utils, "get_next_global_id", None)
675
+ if not callable(get_next_id):
676
+ raise WorkerFailure(
677
+ "UPSTREAM_INTERFACE_MISMATCH",
678
+ "pinned playbook_utils lacks get_next_global_id()",
679
+ )
680
+ ace_system.next_global_id = get_next_id(initial_text)
681
+ tracker = UsageTracker(request.controls, self.llm_config)
682
+ self.last_tracker = tracker
683
+ tracker.install(ace_system)
684
+ train_samples = [adapt_example(item) for item in request.train_examples]
685
+ validation_samples = [adapt_example(item) for item in request.validation_examples]
686
+ controls = request.controls
687
+ if self.llm_config.max_tokens_per_call > controls.max_tokens:
688
+ raise WorkerFailure(
689
+ "INVALID_BUDGET",
690
+ "configured max_tokens_per_call exceeds the request token budget",
691
+ )
692
+ random_state = random.getstate()
693
+ try:
694
+ random.seed(controls.seed)
695
+ with tempfile.TemporaryDirectory(prefix="agentinputlab-ace-") as save_dir:
696
+ upstream_config = {
697
+ "num_epochs": 1,
698
+ "max_num_rounds": controls.max_rounds,
699
+ "curator_frequency": 1,
700
+ "eval_steps": len(train_samples),
701
+ "save_steps": len(train_samples) + 1,
702
+ "playbook_token_budget": controls.max_tokens,
703
+ "task_name": "agentinputlab_generic_text",
704
+ "json_mode": self.llm_config.json_mode,
705
+ "no_ground_truth": False,
706
+ "save_dir": save_dir,
707
+ "test_workers": 1,
708
+ "use_bulletpoint_analyzer": False,
709
+ }
710
+ with contextlib.redirect_stdout(diagnostics):
711
+ raw_result = ace_system.run(
712
+ mode="offline",
713
+ train_samples=train_samples,
714
+ val_samples=validation_samples,
715
+ test_samples=None,
716
+ data_processor=self.data_processor,
717
+ config=upstream_config,
718
+ )
719
+ except UnboundLocalError as exc:
720
+ final = exc.__traceback__
721
+ while final is not None and final.tb_next is not None:
722
+ final = final.tb_next
723
+ if (
724
+ final is not None
725
+ and final.tb_frame.f_code.co_name == "evaluate_test_set"
726
+ and Path(final.tb_frame.f_code.co_filename).resolve() == self.checkout / "utils.py"
727
+ and "final_results" in str(exc)
728
+ ):
729
+ if tracker.first_component_failure is not None:
730
+ raise tracker.first_component_failure from exc
731
+ raise WorkerFailure(
732
+ "UPSTREAM_VALIDATION_NO_RESULTS",
733
+ "pinned ACE validation returned no valid results; fit failed, usage retained",
734
+ ) from exc
735
+ raise
736
+ finally:
737
+ random.setstate(random_state)
738
+ tracker.require_upstream_chain()
739
+ score = extract_validation_score(raw_result)
740
+ best_playbook = getattr(ace_system, "best_playbook", None)
741
+ if not isinstance(best_playbook, str):
742
+ raise WorkerFailure("UPSTREAM_RESULT_INVALID", "upstream ACE omitted its best playbook")
743
+ candidate = parse_playbook(best_playbook, initial=request.initial_bullets)
744
+ operations = diff_playbooks(request.initial_bullets, candidate)
745
+ return {
746
+ "status": "ok",
747
+ "request_id": request.request_id,
748
+ "protocol_version": PROTOCOL_VERSION,
749
+ "upstream_commit": UPSTREAM_COMMIT,
750
+ "split_audit_hash": request.split_audit_hash,
751
+ "train_ids": [str(item["example_id"]) for item in request.train_examples],
752
+ "validation_ids": [str(item["example_id"]) for item in request.validation_examples],
753
+ "iterations": [
754
+ {
755
+ "index": 0,
756
+ "operations": operations,
757
+ "candidate_playbook_hash": playbook_hash(candidate),
758
+ "validation_score": score,
759
+ }
760
+ ],
761
+ "selected_iteration": 0,
762
+ "final_playbook": list(candidate),
763
+ "usage": tracker.as_dict(),
764
+ "diagnostics": {
765
+ **(
766
+ {"operator_task_processor": self.task_processor_fingerprint}
767
+ if self.task_processor_fingerprint is not None
768
+ else {}
769
+ ),
770
+ "upstream_stdout_bytes": diagnostics.byte_count,
771
+ "upstream_stdout_sha256": diagnostics.sha256,
772
+ "content_retained": False,
773
+ "provider_binding": (
774
+ "operator_openai_compatible"
775
+ if self.llm_config.base_url is not None
776
+ else "upstream_native"
777
+ ),
778
+ },
779
+ }
780
+
781
+ def _bind_openai_compatible_clients(self, ace_system: Any, *, seed: int) -> None:
782
+ """Replace only upstream client objects using operator-startup configuration."""
783
+
784
+ base_url = self.llm_config.base_url
785
+ api_key_env = self.llm_config.api_key_env
786
+ if base_url is None or api_key_env is None: # pragma: no cover - caller invariant
787
+ raise WorkerFailure("INVALID_LLM_CONFIG", "ACE endpoint binding is incomplete")
788
+ api_key = os.environ.get(api_key_env, "")
789
+ if not api_key:
790
+ raise WorkerFailure(
791
+ "MISSING_API_KEY",
792
+ f"ACE OpenAI-compatible runtime requires environment {api_key_env}",
793
+ )
794
+ try:
795
+ openai_module = importlib.import_module("openai")
796
+ client_class = openai_module.OpenAI
797
+ clients = tuple(
798
+ client_class(api_key=api_key, base_url=base_url, max_retries=0) for _ in _ROLE_NAMES
799
+ )
800
+ except (AttributeError, ImportError, TypeError, ValueError) as exc:
801
+ raise WorkerFailure(
802
+ "OPENAI_COMPATIBLE_CLIENT_UNAVAILABLE",
803
+ "ACE could not initialize its operator-bound OpenAI-compatible clients",
804
+ ) from exc
805
+ extra_body = dict(self.llm_config.extra_body or {})
806
+ if "seed" in extra_body and (
807
+ type(extra_body["seed"]) is not int or extra_body["seed"] != seed
808
+ ):
809
+ raise WorkerFailure(
810
+ "INVALID_LLM_CONFIG", "deployment seed differs from the fit controls"
811
+ )
812
+ extra_body["seed"] = seed
813
+ clients = tuple(CompletionDefaultClient(client, extra_body) for client in clients)
814
+ components = (
815
+ ace_system.generator,
816
+ ace_system.reflector,
817
+ ace_system.curator,
818
+ )
819
+ for component, client in zip(components, clients, strict=True):
820
+ component.api_client = client
821
+ ace_system.generator_client = clients[0]
822
+ ace_system.reflector_client = clients[1]
823
+ ace_system.curator_client = clients[2]
824
+
825
+
826
+ def load_compatibility_policy() -> CompatibilityPolicy:
827
+ path = Path(__file__).with_name("compatibility_manifest.json")
828
+ try:
829
+ raw = _loads_strict_json(path.read_text(encoding="utf-8"))
830
+ except OSError as exc:
831
+ raise WorkerFailure(
832
+ "COMPATIBILITY_MANIFEST_UNAVAILABLE", "ACE compatibility manifest is unavailable"
833
+ ) from exc
834
+ return CompatibilityPolicy.from_mapping(_require_mapping(raw, "compatibility manifest"))
835
+
836
+
837
+ def verify_checkout(checkout: Path, policy: CompatibilityPolicy) -> Path:
838
+ try:
839
+ root = checkout.expanduser().resolve(strict=True)
840
+ except OSError as exc:
841
+ raise WorkerFailure("CHECKOUT_UNAVAILABLE", "ACE checkout does not exist") from exc
842
+ if not root.is_dir():
843
+ raise WorkerFailure("CHECKOUT_UNAVAILABLE", "ACE checkout must be a directory")
844
+ if read_checkout_head(root) != policy.commit:
845
+ raise WorkerFailure(
846
+ "UPSTREAM_REVISION_MISMATCH", "ACE checkout is not at the pinned commit"
847
+ )
848
+ license_file = safe_checkout_file(root, policy.license_path)
849
+ if file_sha256(license_file) != policy.license_sha256:
850
+ raise WorkerFailure("UPSTREAM_LICENSE_MISMATCH", "ACE license hash does not match")
851
+ license_text = license_file.read_text(encoding="utf-8", errors="strict")[:500]
852
+ if "Apache License" not in license_text or "Version 2.0" not in license_text:
853
+ raise WorkerFailure(
854
+ "UPSTREAM_LICENSE_MISMATCH", "ACE checkout does not contain Apache-2.0 text"
855
+ )
856
+ source_paths: list[Path] = []
857
+ for relative, expected in policy.source_hashes.items():
858
+ source = safe_checkout_file(root, relative)
859
+ if file_sha256(source) != expected:
860
+ raise WorkerFailure("UPSTREAM_SOURCE_MISMATCH", f"ACE source hash mismatch: {relative}")
861
+ source_paths.append(source)
862
+ reject_appworld_imports(source_paths)
863
+ if policy.require_clean_checkout:
864
+ require_clean_git_checkout(root)
865
+ return root
866
+
867
+
868
+ def require_clean_git_checkout(root: Path) -> None:
869
+ """Reject tracked or untracked checkout changes with a fixed Git query."""
870
+
871
+ environment = {
872
+ "PATH": os.environ.get("PATH", ""),
873
+ "LC_ALL": "C",
874
+ "GIT_CONFIG_NOSYSTEM": "1",
875
+ "GIT_CONFIG_GLOBAL": os.devnull,
876
+ "GIT_OPTIONAL_LOCKS": "0",
877
+ }
878
+ try:
879
+ result = subprocess.run(
880
+ (
881
+ "git",
882
+ "-c",
883
+ "core.fsmonitor=false",
884
+ "-C",
885
+ str(root),
886
+ "status",
887
+ "--porcelain=v1",
888
+ "--untracked-files=all",
889
+ ),
890
+ check=False,
891
+ capture_output=True,
892
+ text=True,
893
+ encoding="utf-8",
894
+ errors="replace",
895
+ env=environment,
896
+ timeout=15,
897
+ )
898
+ except (OSError, subprocess.SubprocessError) as exc:
899
+ raise WorkerFailure(
900
+ "GIT_STATUS_FAILED", "ACE clean-checkout verification could not run"
901
+ ) from exc
902
+ if result.returncode != 0:
903
+ raise WorkerFailure("GIT_STATUS_FAILED", "ACE clean-checkout verification failed")
904
+ if result.stdout:
905
+ raise WorkerFailure("DIRTY_CHECKOUT", "ACE checkout contains tracked or untracked changes")
906
+
907
+
908
+ def read_checkout_head(root: Path) -> str:
909
+ git_entry = root / ".git"
910
+ if git_entry.is_dir():
911
+ git_dir = git_entry.resolve(strict=True)
912
+ elif git_entry.is_file():
913
+ raw = git_entry.read_text(encoding="utf-8").strip()
914
+ if not raw.startswith("gitdir: "):
915
+ raise WorkerFailure("GIT_METADATA_INVALID", "ACE .git file is malformed")
916
+ candidate = Path(raw.removeprefix("gitdir: "))
917
+ git_dir = (
918
+ (root / candidate).resolve(strict=True)
919
+ if not candidate.is_absolute()
920
+ else candidate.resolve(strict=True)
921
+ )
922
+ else:
923
+ raise WorkerFailure("GIT_METADATA_INVALID", "ACE checkout lacks Git metadata")
924
+ head = (git_dir / "HEAD").read_text(encoding="utf-8").strip()
925
+ if _COMMIT_RE.fullmatch(head):
926
+ return head
927
+ if not head.startswith("ref: "):
928
+ raise WorkerFailure("GIT_METADATA_INVALID", "ACE HEAD is malformed")
929
+ ref = head.removeprefix("ref: ")
930
+ if not _REF_RE.fullmatch(ref) or ".." in PurePosixPath(ref).parts:
931
+ raise WorkerFailure("GIT_METADATA_INVALID", "ACE HEAD reference is unsafe")
932
+ common_dir = git_dir
933
+ common_file = git_dir / "commondir"
934
+ if common_file.is_file():
935
+ common_candidate = Path(common_file.read_text(encoding="utf-8").strip())
936
+ common_dir = (
937
+ (git_dir / common_candidate).resolve(strict=True)
938
+ if not common_candidate.is_absolute()
939
+ else common_candidate.resolve(strict=True)
940
+ )
941
+ for base in (git_dir, common_dir):
942
+ loose = safe_child(base, ref)
943
+ if loose.is_file():
944
+ value = loose.read_text(encoding="utf-8").strip()
945
+ if _COMMIT_RE.fullmatch(value):
946
+ return value
947
+ packed = base / "packed-refs"
948
+ if packed.is_file():
949
+ for line in packed.read_text(encoding="utf-8").splitlines():
950
+ if not line or line.startswith(("#", "^")):
951
+ continue
952
+ value, separator, name = line.partition(" ")
953
+ if separator and name == ref and _COMMIT_RE.fullmatch(value):
954
+ return value
955
+ raise WorkerFailure("GIT_METADATA_INVALID", "ACE HEAD commit cannot be resolved")
956
+
957
+
958
+ def safe_checkout_file(root: Path, relative: str) -> Path:
959
+ path = safe_child(root, relative)
960
+ if not path.is_file() or path.is_symlink():
961
+ raise WorkerFailure(
962
+ "UPSTREAM_FILE_UNAVAILABLE", f"ACE pinned file is missing or unsafe: {relative}"
963
+ )
964
+ return path
965
+
966
+
967
+ def safe_child(root: Path, relative: str) -> Path:
968
+ candidate = (root / relative).resolve(strict=False)
969
+ if candidate != root and root not in candidate.parents:
970
+ raise WorkerFailure("UNSAFE_PATH", "path escapes its fixed root")
971
+ return candidate
972
+
973
+
974
+ def reject_appworld_imports(paths: Sequence[Path]) -> None:
975
+ for path in paths:
976
+ if path.suffix != ".py":
977
+ continue
978
+ try:
979
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=path.name)
980
+ except (OSError, SyntaxError, UnicodeError) as exc:
981
+ raise WorkerFailure(
982
+ "UPSTREAM_SOURCE_INVALID", "ACE pinned Python source cannot be parsed"
983
+ ) from exc
984
+ for node in ast.walk(tree):
985
+ names: tuple[str, ...] = ()
986
+ if isinstance(node, ast.Import):
987
+ names = tuple(alias.name for alias in node.names)
988
+ elif isinstance(node, ast.ImportFrom) and node.module:
989
+ names = (node.module,)
990
+ if any(_is_appworld_module(name) for name in names):
991
+ raise WorkerFailure("APPWORLD_FORBIDDEN", "ACE worker refuses to import AppWorld")
992
+
993
+
994
+ def load_upstream(checkout: Path) -> tuple[type[Any], Any]:
995
+ checkout = checkout.resolve(strict=True)
996
+ for name in tuple(sys.modules):
997
+ if any(name == root or name.startswith(f"{root}.") for root in _UPSTREAM_MODULES):
998
+ del sys.modules[name]
999
+ old_dont_write = sys.dont_write_bytecode
1000
+ had_dotenv_setting = "PYTHON_DOTENV_DISABLED" in os.environ
1001
+ old_dotenv_setting = os.environ.get("PYTHON_DOTENV_DISABLED")
1002
+ sys.dont_write_bytecode = True
1003
+ os.environ["PYTHON_DOTENV_DISABLED"] = "1"
1004
+ sys.path.insert(0, str(checkout))
1005
+ before = set(sys.modules)
1006
+ try:
1007
+ with contextlib.redirect_stdout(BoundedHashSink()):
1008
+ module = importlib.import_module("ace.ace")
1009
+ playbook_utils = importlib.import_module("playbook_utils")
1010
+ except ModuleNotFoundError as exc:
1011
+ if exc.name in {"numpy", "openai", "dotenv", "tiktoken"}:
1012
+ raise WorkerFailure(
1013
+ "UPSTREAM_DEPENDENCY_MISSING",
1014
+ "install agentinputlab[ace] in the isolated worker environment",
1015
+ ) from None
1016
+ raise WorkerFailure(
1017
+ "UPSTREAM_IMPORT_FAILED", "pinned ACE import requires its verified source dependencies"
1018
+ ) from None
1019
+ except Exception as exc:
1020
+ raise WorkerFailure(
1021
+ "UPSTREAM_IMPORT_FAILED",
1022
+ f"pinned ACE import failed: {type(exc).__name__}: {exc}",
1023
+ ) from exc
1024
+ finally:
1025
+ sys.path.remove(str(checkout))
1026
+ sys.dont_write_bytecode = old_dont_write
1027
+ if had_dotenv_setting:
1028
+ assert old_dotenv_setting is not None
1029
+ os.environ["PYTHON_DOTENV_DISABLED"] = old_dotenv_setting
1030
+ else:
1031
+ os.environ.pop("PYTHON_DOTENV_DISABLED", None)
1032
+ imported = set(sys.modules) - before
1033
+ if any(_is_appworld_module(name) for name in imported):
1034
+ raise WorkerFailure("APPWORLD_FORBIDDEN", "ACE worker imported AppWorld")
1035
+ for imported_module in (module, playbook_utils):
1036
+ origin = getattr(imported_module, "__file__", None)
1037
+ if not isinstance(origin, str):
1038
+ raise WorkerFailure(
1039
+ "UPSTREAM_IMPORT_MISMATCH", "ACE upstream module has no source origin"
1040
+ )
1041
+ resolved = Path(origin).resolve(strict=True)
1042
+ if checkout not in resolved.parents:
1043
+ raise WorkerFailure(
1044
+ "UPSTREAM_IMPORT_MISMATCH", "ACE upstream module came from another checkout"
1045
+ )
1046
+ ace_class = getattr(module, "ACE", None)
1047
+ if not isinstance(ace_class, type) or not callable(getattr(ace_class, "run", None)):
1048
+ raise WorkerFailure("UPSTREAM_INTERFACE_MISMATCH", "pinned ace.ace.ACE.run is unavailable")
1049
+ return ace_class, playbook_utils
1050
+
1051
+
1052
+ def validate_request(payload: Mapping[str, Any], llm_config: LLMConfig) -> ValidatedRequest:
1053
+ _require_exact_keys(
1054
+ payload,
1055
+ {
1056
+ "request_id",
1057
+ "train_examples",
1058
+ "validation_examples",
1059
+ "initial_playbook",
1060
+ "controls",
1061
+ "model_fingerprints",
1062
+ "split_audit_hash",
1063
+ "upstream_commit",
1064
+ "protocol_version",
1065
+ },
1066
+ "ACE request",
1067
+ )
1068
+ if payload["upstream_commit"] != UPSTREAM_COMMIT:
1069
+ raise WorkerFailure(
1070
+ "UPSTREAM_REVISION_MISMATCH", "ACE request targets another upstream commit"
1071
+ )
1072
+ if payload["protocol_version"] != PROTOCOL_VERSION:
1073
+ raise WorkerFailure("PROTOCOL_MISMATCH", "ACE request protocol version mismatch")
1074
+ request_id = _require_hash(payload["request_id"], "ACE request_id")
1075
+ split_audit_hash = _require_hash(payload["split_audit_hash"], "ACE split_audit_hash")
1076
+ train = validate_examples(payload["train_examples"], "train")
1077
+ validation = validate_examples(payload["validation_examples"], "validation")
1078
+ train_ids = tuple(str(item["example_id"]) for item in train)
1079
+ validation_ids = tuple(str(item["example_id"]) for item in validation)
1080
+ if set(train_ids) & set(validation_ids):
1081
+ raise WorkerFailure("SPLIT_OVERLAP", "ACE train and validation IDs overlap")
1082
+ initial = validate_initial_playbook(payload["initial_playbook"])
1083
+ controls = validate_controls(payload["controls"])
1084
+ if llm_config.base_url is not None and "seed" in (llm_config.extra_body or {}):
1085
+ declared_seed = llm_config.extra_body["seed"]
1086
+ if type(declared_seed) is not int or declared_seed != controls.seed:
1087
+ raise WorkerFailure(
1088
+ "INVALID_LLM_CONFIG", "deployment seed differs from the fit controls"
1089
+ )
1090
+ validate_model_fingerprints(payload["model_fingerprints"], llm_config)
1091
+ expected_split_hash = split_hash(train, validation)
1092
+ if split_audit_hash != expected_split_hash:
1093
+ raise WorkerFailure("SPLIT_AUDIT_MISMATCH", "ACE split audit hash is invalid")
1094
+ request_base = {
1095
+ "train_ids": train_ids,
1096
+ "validation_ids": validation_ids,
1097
+ "train_examples": train,
1098
+ "validation_examples": validation,
1099
+ "initial_playbook_hash": playbook_hash(initial),
1100
+ # Preserve the request's numeric JSON representation (for example,
1101
+ # ``1`` versus ``1.0``) because the recipe hashes the pre-normalized
1102
+ # control mapping when it constructs request_id.
1103
+ "controls": dict(_require_mapping(payload["controls"], "ACE controls")),
1104
+ "model_fingerprints": payload["model_fingerprints"],
1105
+ "split_audit_hash": split_audit_hash,
1106
+ "upstream_commit": UPSTREAM_COMMIT,
1107
+ }
1108
+ if request_id != canonical_hash(request_base):
1109
+ raise WorkerFailure("REQUEST_ID_MISMATCH", "ACE request_id is invalid")
1110
+ return ValidatedRequest(
1111
+ request_id=request_id,
1112
+ train_examples=train,
1113
+ validation_examples=validation,
1114
+ initial_bullets=initial,
1115
+ controls=controls,
1116
+ split_audit_hash=split_audit_hash,
1117
+ )
1118
+
1119
+
1120
+ def validate_examples(value: Any, split: str) -> tuple[Mapping[str, Any], ...]:
1121
+ if not isinstance(value, Sequence) or isinstance(value, (str, bytes)) or not value:
1122
+ raise WorkerFailure("INVALID_EXAMPLES", f"ACE {split} examples must be non-empty")
1123
+ examples: list[Mapping[str, Any]] = []
1124
+ ids: list[str] = []
1125
+ for index, raw in enumerate(value):
1126
+ item = _require_mapping(raw, f"ACE {split} example {index}")
1127
+ _require_exact_keys(
1128
+ item,
1129
+ {"example_id", "input", "expected_output", "split", "metadata"},
1130
+ f"ACE {split} example {index}",
1131
+ )
1132
+ example_id = _require_text(item["example_id"], f"ACE {split} example_id")
1133
+ if item["split"] != split:
1134
+ raise WorkerFailure("INVALID_EXAMPLES", f"ACE {split} example has wrong split")
1135
+ if item["expected_output"] is None:
1136
+ raise WorkerFailure("INVALID_EXAMPLES", f"ACE {split} example requires expected_output")
1137
+ _canonical_json(item["input"])
1138
+ _canonical_json(item["expected_output"])
1139
+ _canonical_json(_require_mapping(item["metadata"], "ACE example metadata"))
1140
+ examples.append(dict(item))
1141
+ ids.append(example_id)
1142
+ if len(ids) != len(set(ids)):
1143
+ raise WorkerFailure("INVALID_EXAMPLES", f"ACE {split} example IDs must be unique")
1144
+ return tuple(examples)
1145
+
1146
+
1147
+ def validate_initial_playbook(value: Any) -> tuple[dict[str, Any], ...]:
1148
+ playbook = _require_mapping(value, "ACE initial_playbook")
1149
+ _require_exact_keys(playbook, {"bullets"}, "ACE initial_playbook")
1150
+ raw_bullets = playbook["bullets"]
1151
+ if not isinstance(raw_bullets, Sequence) or isinstance(raw_bullets, (str, bytes)):
1152
+ raise WorkerFailure("INVALID_PLAYBOOK", "ACE initial bullets must be a sequence")
1153
+ bullets: list[dict[str, Any]] = []
1154
+ ids: list[str] = []
1155
+ for index, raw in enumerate(raw_bullets):
1156
+ item = _require_mapping(raw, f"ACE initial bullet {index}")
1157
+ _require_exact_keys(
1158
+ item,
1159
+ {"bullet_id", "content", "helpful", "harmful", "metadata"},
1160
+ f"ACE initial bullet {index}",
1161
+ )
1162
+ bullet_id = _require_text(item["bullet_id"], "ACE bullet_id")
1163
+ content = _require_text(item["content"], "ACE bullet content")
1164
+ if any(character in bullet_id for character in ("\n", "\r", "]")):
1165
+ raise WorkerFailure("INVALID_PLAYBOOK", "ACE bullet_id is not line-safe")
1166
+ if "\n" in content or "\r" in content:
1167
+ raise WorkerFailure("INVALID_PLAYBOOK", "ACE bullet content must be one line")
1168
+ helpful = _require_non_negative_int(item["helpful"], "ACE helpful counter")
1169
+ harmful = _require_non_negative_int(item["harmful"], "ACE harmful counter")
1170
+ metadata = _require_mapping(item["metadata"], "ACE bullet metadata")
1171
+ _canonical_json(metadata)
1172
+ bullets.append(
1173
+ {
1174
+ "bullet_id": bullet_id,
1175
+ "content": content,
1176
+ "helpful": helpful,
1177
+ "harmful": harmful,
1178
+ "metadata": dict(metadata),
1179
+ }
1180
+ )
1181
+ ids.append(bullet_id)
1182
+ if len(ids) != len(set(ids)):
1183
+ raise WorkerFailure("INVALID_PLAYBOOK", "ACE bullet IDs must be unique")
1184
+ return tuple(bullets)
1185
+
1186
+
1187
+ def validate_controls(value: Any) -> Controls:
1188
+ controls = _require_mapping(value, "ACE controls")
1189
+ _require_exact_keys(
1190
+ controls,
1191
+ {"max_rounds", "max_model_calls", "max_tokens", "max_cost_usd", "seed"},
1192
+ "ACE controls",
1193
+ )
1194
+ return Controls(
1195
+ max_rounds=_require_positive_int(controls["max_rounds"], "ACE max_rounds"),
1196
+ max_model_calls=_require_positive_int(controls["max_model_calls"], "ACE max_model_calls"),
1197
+ max_tokens=_require_positive_int(controls["max_tokens"], "ACE max_tokens"),
1198
+ max_cost_usd=_require_non_negative_float(controls["max_cost_usd"], "ACE max_cost_usd"),
1199
+ seed=_require_non_negative_int(controls["seed"], "ACE seed"),
1200
+ )
1201
+
1202
+
1203
+ def validate_model_fingerprints(value: Any, config: LLMConfig) -> None:
1204
+ fingerprints = _require_mapping(value, "ACE model_fingerprints")
1205
+ if set(fingerprints) != set(_ROLE_NAMES):
1206
+ raise WorkerFailure(
1207
+ "MODEL_FINGERPRINT_MISMATCH",
1208
+ "ACE request must bind target, reflector, and curator fingerprints",
1209
+ )
1210
+ for role in _ROLE_NAMES:
1211
+ supplied = _require_mapping(fingerprints[role], f"ACE {role} fingerprint")
1212
+ if _canonical_json(supplied) != _canonical_json(config.roles[role].fingerprint):
1213
+ raise WorkerFailure(
1214
+ "MODEL_FINGERPRINT_MISMATCH",
1215
+ f"ACE {role} fingerprint differs from worker startup configuration",
1216
+ )
1217
+
1218
+
1219
+ def split_hash(train: Sequence[Mapping[str, Any]], validation: Sequence[Mapping[str, Any]]) -> str:
1220
+ value = {
1221
+ "train_ids": [str(item["example_id"]) for item in train],
1222
+ "validation_ids": [str(item["example_id"]) for item in validation],
1223
+ "test_ids": [],
1224
+ "train_input_hashes": [canonical_hash(item["input"]) for item in train],
1225
+ "validation_input_hashes": [canonical_hash(item["input"]) for item in validation],
1226
+ "test_input_hashes": [],
1227
+ }
1228
+ return canonical_hash(value)
1229
+
1230
+
1231
+ def adapt_example(item: Mapping[str, Any]) -> dict[str, str]:
1232
+ raw_input = item["input"]
1233
+ context = ""
1234
+ if isinstance(raw_input, str):
1235
+ question = raw_input
1236
+ elif isinstance(raw_input, Mapping):
1237
+ aliases = [
1238
+ name
1239
+ for name in ("question", "query", "prompt", "instruction", "q")
1240
+ if name in raw_input
1241
+ ]
1242
+ if len(aliases) > 1:
1243
+ raise WorkerFailure(
1244
+ "AMBIGUOUS_TEXT_INPUT", "ACE text input contains multiple question fields"
1245
+ )
1246
+ question = render_text(raw_input[aliases[0]]) if aliases else render_text(raw_input)
1247
+ if "context" in raw_input:
1248
+ context = render_text(raw_input["context"])
1249
+ else:
1250
+ question = render_text(raw_input)
1251
+ if not question.strip():
1252
+ raise WorkerFailure("INVALID_TEXT_INPUT", "ACE text question must be non-empty")
1253
+ target = render_text(item["expected_output"])
1254
+ if not target.strip():
1255
+ raise WorkerFailure("INVALID_TEXT_INPUT", "ACE text target must be non-empty")
1256
+ return {
1257
+ "example_id": str(item["example_id"]),
1258
+ "context": context,
1259
+ "question": question,
1260
+ "target": target,
1261
+ }
1262
+
1263
+
1264
+ def render_text(value: Any) -> str:
1265
+ return value if isinstance(value, str) else _canonical_json(value)
1266
+
1267
+
1268
+ def render_playbook(bullets: Sequence[Mapping[str, Any]]) -> str:
1269
+ grouped: dict[str, list[str]] = {section: [] for section in _STANDARD_SECTIONS}
1270
+ for bullet in bullets:
1271
+ metadata = _require_mapping(bullet.get("metadata", {}), "ACE bullet metadata")
1272
+ raw_section = metadata.get("section", "OTHERS")
1273
+ section = normalize_section(raw_section)
1274
+ grouped[section].append(
1275
+ f"[{bullet['bullet_id']}] helpful={bullet['helpful']} "
1276
+ f"harmful={bullet['harmful']} :: {bullet['content']}"
1277
+ )
1278
+ lines: list[str] = []
1279
+ for section in _STANDARD_SECTIONS:
1280
+ if lines:
1281
+ lines.append("")
1282
+ lines.append(f"## {section}")
1283
+ lines.extend(grouped[section])
1284
+ return "\n".join(lines)
1285
+
1286
+
1287
+ def normalize_section(value: Any) -> str:
1288
+ if not isinstance(value, str):
1289
+ raise WorkerFailure("INVALID_PLAYBOOK", "ACE bullet section must be text")
1290
+ normalized = value.strip().upper().replace("_", " ")
1291
+ normalized = re.sub(r"\s+", " ", normalized)
1292
+ aliases = {
1293
+ "STRATEGIES AND INSIGHTS": "STRATEGIES & INSIGHTS",
1294
+ "FORMULAS AND CALCULATIONS": "FORMULAS & CALCULATIONS",
1295
+ "CODE SNIPPETS AND TEMPLATES": "CODE SNIPPETS & TEMPLATES",
1296
+ }
1297
+ normalized = aliases.get(normalized, normalized)
1298
+ return normalized if normalized in _STANDARD_SECTIONS else "OTHERS"
1299
+
1300
+
1301
+ def parse_playbook(
1302
+ text: str, *, initial: Sequence[Mapping[str, Any]]
1303
+ ) -> tuple[dict[str, Any], ...]:
1304
+ initial_by_id = {str(item["bullet_id"]): item for item in initial}
1305
+ parsed_by_id: dict[str, dict[str, Any]] = {}
1306
+ parsed_order: list[str] = []
1307
+ section = "OTHERS"
1308
+ for raw_line in text.splitlines():
1309
+ line = raw_line.strip()
1310
+ if not line:
1311
+ continue
1312
+ if line.startswith("##"):
1313
+ section = line.removeprefix("##").strip() or "OTHERS"
1314
+ continue
1315
+ match = _PLAYBOOK_LINE_RE.fullmatch(line)
1316
+ if match is None:
1317
+ raise WorkerFailure(
1318
+ "UPSTREAM_PLAYBOOK_INVALID", "upstream ACE emitted an unparseable playbook line"
1319
+ )
1320
+ bullet_id, helpful, harmful, content = match.groups()
1321
+ if not content.strip():
1322
+ raise WorkerFailure("UPSTREAM_PLAYBOOK_INVALID", "upstream ACE emitted an empty bullet")
1323
+ if bullet_id in parsed_by_id:
1324
+ raise WorkerFailure(
1325
+ "UPSTREAM_PLAYBOOK_INVALID", "upstream ACE emitted duplicate bullet IDs"
1326
+ )
1327
+ old = initial_by_id.get(bullet_id)
1328
+ metadata = dict(old.get("metadata", {})) if old is not None else {"section": section}
1329
+ parsed_by_id[bullet_id] = {
1330
+ "bullet_id": bullet_id,
1331
+ "content": content,
1332
+ "helpful": int(helpful),
1333
+ "harmful": int(harmful),
1334
+ "metadata": metadata,
1335
+ }
1336
+ parsed_order.append(bullet_id)
1337
+ stable_order = [
1338
+ str(item["bullet_id"]) for item in initial if str(item["bullet_id"]) in parsed_by_id
1339
+ ]
1340
+ stable_order.extend(item for item in parsed_order if item not in initial_by_id)
1341
+ return tuple(parsed_by_id[bullet_id] for bullet_id in stable_order)
1342
+
1343
+
1344
+ def diff_playbooks(
1345
+ initial: Sequence[Mapping[str, Any]], candidate: Sequence[Mapping[str, Any]]
1346
+ ) -> list[dict[str, Any]]:
1347
+ initial_by_id = {str(item["bullet_id"]): item for item in initial}
1348
+ candidate_by_id = {str(item["bullet_id"]): item for item in candidate}
1349
+ operations: list[dict[str, Any]] = []
1350
+ for item in initial:
1351
+ bullet_id = str(item["bullet_id"])
1352
+ replacement = candidate_by_id.get(bullet_id)
1353
+ if replacement is None:
1354
+ operations.append({"operation": "delete", "bullet_id": bullet_id})
1355
+ elif any(
1356
+ item[key] != replacement[key] for key in ("content", "helpful", "harmful", "metadata")
1357
+ ):
1358
+ operations.append(
1359
+ {
1360
+ "operation": "edit",
1361
+ "bullet_id": bullet_id,
1362
+ "content": replacement["content"],
1363
+ "helpful": replacement["helpful"],
1364
+ "harmful": replacement["harmful"],
1365
+ "metadata": replacement["metadata"],
1366
+ }
1367
+ )
1368
+ for item in candidate:
1369
+ bullet_id = str(item["bullet_id"])
1370
+ if bullet_id not in initial_by_id:
1371
+ operations.append(
1372
+ {
1373
+ "operation": "add",
1374
+ "bullet_id": bullet_id,
1375
+ "content": item["content"],
1376
+ "helpful": item["helpful"],
1377
+ "harmful": item["harmful"],
1378
+ "metadata": item["metadata"],
1379
+ }
1380
+ )
1381
+ return operations
1382
+
1383
+
1384
+ def playbook_hash(bullets: Sequence[Mapping[str, Any]]) -> str:
1385
+ return canonical_hash({"bullets": list(bullets)})
1386
+
1387
+
1388
+ def extract_validation_score(value: Any) -> float:
1389
+ root = _require_mapping(value, "upstream ACE result")
1390
+ training = _require_mapping(root.get("training_results"), "upstream training_results")
1391
+ score = _require_non_negative_float(
1392
+ training.get("best_validation_accuracy"), "upstream validation accuracy"
1393
+ )
1394
+ if score > 1:
1395
+ raise WorkerFailure("UPSTREAM_RESULT_INVALID", "upstream validation accuracy exceeds one")
1396
+ return score
1397
+
1398
+
1399
+ def serve(runtime: ACEWorkerRuntime, input_stream: TextIO, output_stream: TextIO) -> None:
1400
+ for raw_line in input_stream:
1401
+ if not raw_line.strip():
1402
+ continue
1403
+ request_id = ""
1404
+ runtime.last_tracker = None
1405
+ try:
1406
+ if len(raw_line.encode("utf-8")) > _MAX_JSONL_BYTES:
1407
+ raise WorkerFailure("REQUEST_TOO_LARGE", "ACE JSONL request is too large")
1408
+ value = _loads_strict_json(raw_line)
1409
+ payload = _require_mapping(value, "ACE JSONL request")
1410
+ if isinstance(payload.get("request_id"), str):
1411
+ request_id = payload["request_id"][:128]
1412
+ response = runtime.run(payload)
1413
+ except WorkerFailure as exc:
1414
+ response = failure_response(request_id, exc.code, str(exc))
1415
+ except Exception as exc: # pragma: no cover - final process boundary
1416
+ response = failure_response(
1417
+ request_id,
1418
+ "INTERNAL_WORKER_ERROR",
1419
+ f"ACE worker failed with {type(exc).__name__}",
1420
+ )
1421
+ if response.get("status") == "error" and runtime.last_tracker is not None:
1422
+ response["usage"] = runtime.last_tracker.as_dict()
1423
+ response["consumed"] = runtime.last_tracker.model_calls > 0
1424
+ response["failed_attempt_tokens_unknown"] = (
1425
+ runtime.last_tracker.failed_provider_attempts > 0
1426
+ )
1427
+ output_stream.write(_canonical_json(response) + "\n")
1428
+ output_stream.flush()
1429
+
1430
+
1431
+ def failure_response(request_id: str, code: str, message: str) -> dict[str, Any]:
1432
+ return {
1433
+ "status": "error",
1434
+ "request_id": request_id,
1435
+ "protocol_version": PROTOCOL_VERSION,
1436
+ "upstream_commit": UPSTREAM_COMMIT,
1437
+ "failure_code": code,
1438
+ "message": message,
1439
+ }
1440
+
1441
+
1442
+ def file_sha256(path: Path) -> str:
1443
+ digest = hashlib.sha256()
1444
+ with path.open("rb") as handle:
1445
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
1446
+ digest.update(chunk)
1447
+ return digest.hexdigest()
1448
+
1449
+
1450
+ def canonical_hash(value: Any) -> str:
1451
+ return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest()
1452
+
1453
+
1454
+ def _canonical_json(value: Any) -> str:
1455
+ try:
1456
+ return json.dumps(
1457
+ value,
1458
+ ensure_ascii=False,
1459
+ sort_keys=True,
1460
+ separators=(",", ":"),
1461
+ allow_nan=False,
1462
+ )
1463
+ except (TypeError, ValueError) as exc:
1464
+ raise WorkerFailure("NON_CANONICAL_JSON", "value is not canonical JSON") from exc
1465
+
1466
+
1467
+ def _loads_strict_json(raw: str) -> Any:
1468
+ def reject_constant(value: str) -> None:
1469
+ raise ValueError(f"non-finite JSON constant: {value}")
1470
+
1471
+ def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
1472
+ result: dict[str, Any] = {}
1473
+ for key, value in pairs:
1474
+ if key in result:
1475
+ raise ValueError(f"duplicate JSON key: {key}")
1476
+ result[key] = value
1477
+ return result
1478
+
1479
+ try:
1480
+ return json.loads(
1481
+ raw,
1482
+ parse_constant=reject_constant,
1483
+ object_pairs_hook=reject_duplicates,
1484
+ )
1485
+ except (TypeError, ValueError, json.JSONDecodeError) as exc:
1486
+ raise WorkerFailure("INVALID_JSON", "input is not strict JSON") from exc
1487
+
1488
+
1489
+ def _require_mapping(value: Any, label: str) -> Mapping[str, Any]:
1490
+ if not isinstance(value, Mapping):
1491
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be a mapping")
1492
+ if any(not isinstance(key, str) for key in value):
1493
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} keys must be strings")
1494
+ return value
1495
+
1496
+
1497
+ def _require_exact_keys(value: Mapping[str, Any], expected: set[str], label: str) -> None:
1498
+ if set(value) != expected:
1499
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} fields do not match the protocol")
1500
+
1501
+
1502
+ def _require_text(value: Any, label: str) -> str:
1503
+ if not isinstance(value, str) or not value.strip():
1504
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be non-empty text")
1505
+ return value
1506
+
1507
+
1508
+ def _require_env_name(value: Any, label: str) -> str:
1509
+ text = _require_text(value, label)
1510
+ if _ENV_RE.fullmatch(text) is None:
1511
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be an environment identifier")
1512
+ return text
1513
+
1514
+
1515
+ def _require_http_url(value: Any, label: str) -> str:
1516
+ text = _require_text(value, label).rstrip("/")
1517
+ parsed = urllib.parse.urlsplit(text)
1518
+ if (
1519
+ parsed.scheme not in {"http", "https"}
1520
+ or not parsed.hostname
1521
+ or parsed.username is not None
1522
+ or parsed.password is not None
1523
+ or parsed.query
1524
+ or parsed.fragment
1525
+ ):
1526
+ raise WorkerFailure(
1527
+ "INVALID_SCHEMA",
1528
+ f"{label} must be an HTTP(S) URL without credentials, query, or fragment",
1529
+ )
1530
+ return text
1531
+
1532
+
1533
+ def _require_hash(value: Any, label: str) -> str:
1534
+ if not isinstance(value, str) or not _HASH_RE.fullmatch(value):
1535
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be a SHA-256 hex digest")
1536
+ return value
1537
+
1538
+
1539
+ def _require_positive_int(value: Any, label: str) -> int:
1540
+ result = _require_non_negative_int(value, label)
1541
+ if result == 0:
1542
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be positive")
1543
+ return result
1544
+
1545
+
1546
+ def _require_non_negative_int(value: Any, label: str) -> int:
1547
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
1548
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be a non-negative integer")
1549
+ return value
1550
+
1551
+
1552
+ def _require_non_negative_float(value: Any, label: str) -> float:
1553
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
1554
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be numeric")
1555
+ result = float(value)
1556
+ if not math.isfinite(result) or result < 0:
1557
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be finite and non-negative")
1558
+ return result
1559
+
1560
+
1561
+ def _safe_relative_path(value: Any, label: str) -> str:
1562
+ text = _require_text(value, label)
1563
+ path = PurePosixPath(text)
1564
+ if path.is_absolute() or ".." in path.parts or "." in path.parts:
1565
+ raise WorkerFailure("INVALID_SCHEMA", f"{label} must be a safe relative path")
1566
+ return path.as_posix()
1567
+
1568
+
1569
+ def _is_appworld_module(name: str) -> bool:
1570
+ lowered = name.casefold().replace("-", "_")
1571
+ return lowered == "appworld" or lowered.startswith("appworld.") or "ace_appworld" in lowered
1572
+
1573
+
1574
+ def load_operator_data_processor(factory: str, raw_config: str = "{}") -> Any:
1575
+ """Load a caller-selected feedback factory at process startup, never from JSONL."""
1576
+ if not isinstance(factory, str) or not re.fullmatch(
1577
+ r"[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*:[A-Za-z_]\w*", factory
1578
+ ):
1579
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", "factory must be module:function")
1580
+ config = _require_mapping(_loads_strict_json(raw_config), "task processor configuration")
1581
+ module_name, symbol = factory.split(":")
1582
+ diagnostics = BoundedHashSink()
1583
+ with contextlib.redirect_stdout(diagnostics):
1584
+ try:
1585
+ module = importlib.import_module(module_name)
1586
+ except ImportError as exc:
1587
+ raise WorkerFailure(
1588
+ "INVALID_TASK_PROCESSOR", "cannot import configured feedback factory"
1589
+ ) from exc
1590
+ source = getattr(module, "__file__", None)
1591
+ if source is None or not Path(source).is_file():
1592
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", "factory module requires a source identity")
1593
+ create = getattr(module, symbol, None)
1594
+ if not callable(create):
1595
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", "factory symbol must be callable")
1596
+ with contextlib.redirect_stdout(diagnostics):
1597
+ delegate = create(**dict(config))
1598
+ for name in ("answer_is_correct", "evaluate_accuracy", "fingerprint"):
1599
+ if not callable(getattr(delegate, name, None)):
1600
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", f"factory result lacks {name}()")
1601
+ binding = dict(
1602
+ factory=factory,
1603
+ factory_module_sha256=file_sha256(Path(source)),
1604
+ operator_configuration_sha256=canonical_hash(config),
1605
+ factory_stdout_sha256=diagnostics.sha256,
1606
+ factory_stdout_bytes=diagnostics.byte_count,
1607
+ )
1608
+
1609
+ class BoundProcessor:
1610
+ def fingerprint(self):
1611
+ return dict(operator_binding=binding, task_processor=delegate.fingerprint())
1612
+
1613
+ def __getattr__(self, name):
1614
+ return getattr(delegate, name)
1615
+
1616
+ return BoundProcessor()
1617
+
1618
+
1619
+ def build_parser() -> argparse.ArgumentParser:
1620
+ parser = argparse.ArgumentParser(description="Pinned AgentInputLab ACE JSONL worker")
1621
+ parser.add_argument("--checkout", required=True, type=Path)
1622
+ parser.add_argument(
1623
+ "--llm-config-json",
1624
+ help="Explicit provider/model/fingerprint/pricing JSON; required unless --verify-only",
1625
+ )
1626
+ parser.add_argument("--verify-only", action="store_true")
1627
+ parser.add_argument(
1628
+ "--task-processor-factory",
1629
+ help="Operator-bound module:function feedback factory; never accepted in JSONL requests",
1630
+ )
1631
+ parser.add_argument(
1632
+ "--task-processor-config-json",
1633
+ help="Startup-only keyword JSON for the feedback factory (no credentials)",
1634
+ )
1635
+ return parser
1636
+
1637
+
1638
+ def main(argv: Sequence[str] | None = None) -> int:
1639
+ args = build_parser().parse_args(argv)
1640
+ policy = load_compatibility_policy()
1641
+ checkout = verify_checkout(args.checkout, policy)
1642
+ if args.verify_only:
1643
+ load_upstream(checkout)
1644
+ sys.stdout.write(
1645
+ _canonical_json(
1646
+ {
1647
+ "status": "ok",
1648
+ "upstream_commit": UPSTREAM_COMMIT,
1649
+ "license_spdx": policy.license_spdx,
1650
+ "import": "ace.ace.ACE.run",
1651
+ }
1652
+ )
1653
+ + "\n"
1654
+ )
1655
+ return 0
1656
+ if args.llm_config_json is None:
1657
+ raise WorkerFailure("INVALID_LLM_CONFIG", "--llm-config-json is required in serve mode")
1658
+ if args.task_processor_config_json is not None and args.task_processor_factory is None:
1659
+ raise WorkerFailure("INVALID_TASK_PROCESSOR", "processor configuration requires a factory")
1660
+ processor = (
1661
+ load_operator_data_processor(
1662
+ args.task_processor_factory, args.task_processor_config_json or "{}"
1663
+ )
1664
+ if args.task_processor_factory is not None
1665
+ else None
1666
+ )
1667
+ runtime = ACEWorkerRuntime(
1668
+ checkout,
1669
+ LLMConfig.from_json(args.llm_config_json),
1670
+ policy=policy,
1671
+ data_processor=processor,
1672
+ )
1673
+ serve(runtime, sys.stdin, sys.stdout)
1674
+ return 0
1675
+
1676
+
1677
+ if __name__ == "__main__": # pragma: no cover - exercised through JSONL smoke tests
1678
+ try:
1679
+ raise SystemExit(main())
1680
+ except WorkerFailure as error:
1681
+ sys.stderr.write(f"{error.code}: {error}\n")
1682
+ raise SystemExit(2) from error