synth-ai 0.2.9.dev0__py3-none-any.whl → 0.2.23.dev3__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 (890) hide show
  1. examples/README.md +1 -0
  2. examples/__init__.py +16 -0
  3. examples/analyze_semantic_words.sh +17 -0
  4. examples/baseline/banking77_baseline.py +243 -0
  5. examples/baseline/banking77_pipeline_baseline.py +294 -0
  6. examples/baseline/crafter_baseline.py +407 -0
  7. examples/baseline/pokemon_red_baseline.py +326 -0
  8. examples/baseline/simple_baseline.py +56 -0
  9. examples/baseline/warming_up_to_rl_baseline.py +239 -0
  10. examples/blog_posts/gepa/README.md +355 -0
  11. examples/blog_posts/gepa/configs/banking77_gepa_local.toml +95 -0
  12. examples/blog_posts/gepa/configs/banking77_gepa_test.toml +80 -0
  13. examples/blog_posts/gepa/configs/banking77_mipro_local.toml +50 -0
  14. examples/blog_posts/gepa/configs/banking77_pipeline_gepa_local.toml +101 -0
  15. examples/blog_posts/gepa/configs/banking77_pipeline_gepa_test.toml +96 -0
  16. examples/blog_posts/gepa/configs/hotpotqa_gepa_local.toml +57 -0
  17. examples/blog_posts/gepa/configs/hotpotqa_gepa_qwen.toml +35 -0
  18. examples/blog_posts/gepa/configs/hotpotqa_mipro_local.toml +51 -0
  19. examples/blog_posts/gepa/configs/hover_gepa_local.toml +57 -0
  20. examples/blog_posts/gepa/configs/hover_gepa_qwen.toml +35 -0
  21. examples/blog_posts/gepa/configs/hover_mipro_local.toml +51 -0
  22. examples/blog_posts/gepa/configs/ifbench_gepa_local.toml +57 -0
  23. examples/blog_posts/gepa/configs/ifbench_gepa_qwen.toml +35 -0
  24. examples/blog_posts/gepa/configs/ifbench_mipro_local.toml +51 -0
  25. examples/blog_posts/gepa/configs/pupa_gepa_local.toml +58 -0
  26. examples/blog_posts/gepa/configs/pupa_mipro_local.toml +52 -0
  27. examples/blog_posts/gepa/deploy_banking77_task_app.sh +54 -0
  28. examples/blog_posts/gepa/gepa_baseline.py +204 -0
  29. examples/blog_posts/gepa/query_prompts_example.py +97 -0
  30. examples/blog_posts/gepa/run_gepa_banking77.sh +112 -0
  31. examples/blog_posts/gepa/run_gepa_banking77_pipeline.sh +163 -0
  32. examples/blog_posts/gepa/task_apps.py +105 -0
  33. examples/blog_posts/gepa/test_gepa_local.sh +67 -0
  34. examples/blog_posts/gepa/verify_banking77_setup.sh +123 -0
  35. examples/blog_posts/mipro/README.md +415 -0
  36. examples/blog_posts/mipro/configs/banking77_mipro_local.toml +91 -0
  37. examples/blog_posts/mipro/configs/banking77_mipro_test.toml +87 -0
  38. examples/blog_posts/mipro/configs/banking77_pipeline_mipro_gemini_flash_lite_local.toml +98 -0
  39. examples/blog_posts/mipro/configs/banking77_pipeline_mipro_gpt41mini_local.toml +96 -0
  40. examples/blog_posts/mipro/configs/banking77_pipeline_mipro_local.toml +94 -0
  41. examples/blog_posts/mipro/configs/banking77_pipeline_mipro_test.toml +170 -0
  42. examples/blog_posts/mipro/deploy_banking77_pipeline_task_app.sh +59 -0
  43. examples/blog_posts/mipro/deploy_banking77_task_app.sh +41 -0
  44. examples/blog_posts/mipro/multi_step.md +79 -0
  45. examples/blog_posts/mipro/run_mipro_banking77.sh +191 -0
  46. examples/blog_posts/mipro/run_mipro_banking77_pipeline.sh +171 -0
  47. examples/blog_posts/mipro/run_mipro_banking77_pipeline_gemini_flash_lite.sh +177 -0
  48. examples/blog_posts/mipro/run_mipro_banking77_pipeline_gpt41mini.sh +173 -0
  49. examples/blog_posts/mipro/verify_banking77_setup.sh +117 -0
  50. examples/blog_posts/pokemon_vl/README.md +98 -0
  51. examples/blog_posts/pokemon_vl/configs/eval_gpt5nano.toml +26 -0
  52. examples/blog_posts/pokemon_vl/configs/eval_qwen3_vl.toml +27 -0
  53. examples/blog_posts/pokemon_vl/configs/eval_rl_final.toml +24 -0
  54. examples/blog_posts/pokemon_vl/configs/filter_high_reward.toml +10 -0
  55. examples/blog_posts/pokemon_vl/configs/train_rl_from_sft.toml +43 -0
  56. examples/blog_posts/pokemon_vl/configs/train_sft_qwen4b_vl.toml +40 -0
  57. examples/blog_posts/pokemon_vl/extract_images.py +239 -0
  58. examples/blog_posts/pokemon_vl/pokemon_vl_baseline.py +326 -0
  59. examples/blog_posts/pokemon_vl/run_eval_extract_images.py +209 -0
  60. examples/blog_posts/pokemon_vl/run_qwen_eval_extract_images.py +212 -0
  61. examples/blog_posts/pokemon_vl/text_box_analysis.md +106 -0
  62. examples/blog_posts/warming_up_to_rl/ARCHITECTURE.md +195 -0
  63. examples/blog_posts/warming_up_to_rl/FINAL_TEST_RESULTS.md +127 -0
  64. examples/blog_posts/warming_up_to_rl/INFERENCE_SUCCESS.md +132 -0
  65. examples/blog_posts/warming_up_to_rl/README.md +158 -0
  66. examples/blog_posts/warming_up_to_rl/SMOKE_TESTING.md +164 -0
  67. examples/blog_posts/warming_up_to_rl/SMOKE_TEST_COMPLETE.md +253 -0
  68. examples/blog_posts/warming_up_to_rl/configs/eval_baseline_qwen32b_10x20.toml +25 -0
  69. examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b.toml +25 -0
  70. examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b_10x20.toml +26 -0
  71. examples/blog_posts/warming_up_to_rl/configs/eval_groq_qwen32b.toml +25 -0
  72. examples/blog_posts/warming_up_to_rl/configs/eval_openai_gpt_oss_120b.toml +29 -0
  73. examples/blog_posts/warming_up_to_rl/configs/filter_high_reward_dataset.toml +10 -0
  74. examples/blog_posts/warming_up_to_rl/configs/smoke_test.toml +75 -0
  75. examples/blog_posts/warming_up_to_rl/configs/train_rl_from_sft.toml +91 -0
  76. examples/blog_posts/warming_up_to_rl/configs/train_sft_qwen4b.toml +40 -0
  77. examples/blog_posts/warming_up_to_rl/warming_up_to_rl_baseline.py +187 -0
  78. examples/crafter_debug_render.py +186 -0
  79. examples/dev/qwen3_32b_qlora_4xh100.toml +45 -0
  80. examples/gepa/banking77_pipeline_gepa.toml +96 -0
  81. examples/gepa/multi_stage_gepa_example.toml +84 -0
  82. examples/gepa/run_gepa_banking77_pipeline.sh +157 -0
  83. examples/multi_step/SFT_README.md +147 -0
  84. examples/multi_step/configs/README_verilog_rl.md +77 -0
  85. examples/multi_step/configs/VERILOG_REWARDS.md +103 -0
  86. examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +196 -0
  87. examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +35 -0
  88. examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +36 -0
  89. examples/multi_step/configs/crafter_rl_outcome.toml +75 -0
  90. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +145 -0
  91. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +84 -0
  92. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +79 -0
  93. examples/multi_step/configs/crafter_rl_stepwise_simple_NEW_FORMAT.toml +105 -0
  94. examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
  95. examples/multi_step/configs/crafter_synth_backend.md +40 -0
  96. examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +31 -0
  97. examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +33 -0
  98. examples/multi_step/configs/verilog_rl_lora.toml +147 -0
  99. examples/multi_step/convert_traces_to_sft.py +84 -0
  100. examples/multi_step/crafter_rl_lora.md +70 -0
  101. examples/multi_step/judges/crafter_backend_judge.py +220 -0
  102. examples/multi_step/judges/verilog_backend_judge.py +234 -0
  103. examples/multi_step/readme.md +48 -0
  104. examples/multi_step/run_sft_qwen30b.sh +45 -0
  105. examples/multi_step/sse_metrics_streaming_notes.md +357 -0
  106. examples/multi_step/task_app_config_notes.md +494 -0
  107. examples/multi_step/verilog_rl_lora.md +218 -0
  108. examples/qwen_coder/README.md +102 -0
  109. examples/qwen_coder/_shared.py +113 -0
  110. examples/qwen_coder/configs/coder_lora_30b.toml +60 -0
  111. examples/qwen_coder/configs/coder_lora_4b.toml +61 -0
  112. examples/qwen_coder/configs/coder_lora_small.toml +57 -0
  113. examples/qwen_coder/generate_dataset.py +98 -0
  114. examples/qwen_coder/infer_ft_smoke.py +65 -0
  115. examples/qwen_coder/infer_prod_proxy.py +73 -0
  116. examples/qwen_coder/infer_via_synth.py +87 -0
  117. examples/qwen_coder/scripts/infer_coder.sh +19 -0
  118. examples/qwen_coder/scripts/train_coder_30b.sh +22 -0
  119. examples/qwen_coder/sft_full_17b.py +103 -0
  120. examples/qwen_coder/sft_lora_30b.py +110 -0
  121. examples/qwen_coder/subset_jsonl.py +39 -0
  122. examples/qwen_coder/todos.md +38 -0
  123. examples/qwen_coder/validate_jsonl.py +60 -0
  124. examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
  125. examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
  126. examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
  127. examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
  128. examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
  129. examples/qwen_vl/QUICKSTART.md +327 -0
  130. examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
  131. examples/qwen_vl/README.md +152 -0
  132. examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
  133. examples/qwen_vl/RL_VISION_TESTING.md +333 -0
  134. examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
  135. examples/qwen_vl/SETUP_COMPLETE.md +274 -0
  136. examples/qwen_vl/VISION_TESTS_COMPLETE.md +489 -0
  137. examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
  138. examples/qwen_vl/__init__.py +2 -0
  139. examples/qwen_vl/collect_data_via_cli.md +415 -0
  140. examples/qwen_vl/collect_vision_traces.py +368 -0
  141. examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +110 -0
  142. examples/qwen_vl/configs/crafter_vlm_sft_example.toml +59 -0
  143. examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +26 -0
  144. examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
  145. examples/qwen_vl/configs/eval_gpt5nano_vision.toml +26 -0
  146. examples/qwen_vl/configs/eval_qwen3vl_vision.toml +26 -0
  147. examples/qwen_vl/configs/filter_qwen3vl_sft.toml +49 -0
  148. examples/qwen_vl/configs/filter_vision_sft.toml +52 -0
  149. examples/qwen_vl/configs/filter_vision_test.toml +8 -0
  150. examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
  151. examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
  152. examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
  153. examples/qwen_vl/run_vision_comparison.sh +61 -0
  154. examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
  155. examples/qwen_vl/test_image_validation.py +201 -0
  156. examples/qwen_vl/test_sft_vision_data.py +110 -0
  157. examples/rl/README.md +169 -0
  158. examples/rl/configs/eval_base_qwen.toml +17 -0
  159. examples/rl/configs/eval_rl_qwen.toml +13 -0
  160. examples/rl/configs/rl_from_base_qwen.toml +62 -0
  161. examples/rl/configs/rl_from_base_qwen17.toml +80 -0
  162. examples/rl/configs/rl_from_ft_qwen.toml +37 -0
  163. examples/rl/download_dataset.py +80 -0
  164. examples/rl/run_eval.py +436 -0
  165. examples/rl/run_rl_and_save.py +111 -0
  166. examples/rl/task_app/README.md +21 -0
  167. {synth_ai/task/apps → examples/rl/task_app}/math_single_step.py +188 -50
  168. examples/rl/task_app/math_task_app.py +111 -0
  169. examples/run_crafter_demo.sh +10 -0
  170. examples/sdk_prompt_learning_example.py +55 -0
  171. examples/sft/README.md +139 -0
  172. examples/sft/configs/crafter_fft_qwen0p6b.toml +49 -0
  173. examples/sft/configs/crafter_lora_qwen0p6b.toml +49 -0
  174. examples/sft/evaluate.py +117 -0
  175. examples/sft/export_dataset.py +120 -0
  176. examples/sft/generate_traces.py +164 -0
  177. examples/swe/__init__.py +12 -0
  178. examples/swe/task_app/README.md +135 -0
  179. examples/swe/task_app/__init__.py +2 -0
  180. examples/swe/task_app/grpo_swe_mini.py +604 -0
  181. examples/swe/task_app/grpo_swe_mini_task_app.py +124 -0
  182. examples/swe/task_app/hosted/README.md +173 -0
  183. examples/swe/task_app/hosted/__init__.py +5 -0
  184. examples/swe/task_app/hosted/branching.py +143 -0
  185. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  186. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  187. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  188. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  189. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  190. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  191. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  192. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  193. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  194. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  195. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1191 -0
  196. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  197. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  198. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  199. examples/swe/task_app/hosted/hosted_app.py +204 -0
  200. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  201. examples/swe/task_app/hosted/inference/openai_client.py +584 -0
  202. examples/swe/task_app/hosted/main.py +100 -0
  203. examples/swe/task_app/hosted/policy_routes.py +1094 -0
  204. examples/swe/task_app/hosted/registry.py +195 -0
  205. examples/swe/task_app/hosted/rollout.py +1905 -0
  206. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  207. examples/swe/task_app/hosted/storage/volume.py +211 -0
  208. examples/swe/task_app/hosted/test_agents.py +161 -0
  209. examples/swe/task_app/hosted/test_service.py +136 -0
  210. examples/swe/task_app/hosted/utils.py +62 -0
  211. examples/swe/task_app/morph_backend.py +178 -0
  212. examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +258 -0
  213. examples/task_apps/TESTING.md +275 -0
  214. examples/task_apps/banking77/__init__.py +6 -0
  215. examples/task_apps/banking77/banking77_task_app.py +912 -0
  216. examples/task_apps/banking77/deploy_wrapper.py +46 -0
  217. examples/task_apps/banking77_pipeline/__init__.py +6 -0
  218. examples/task_apps/banking77_pipeline/banking77_pipeline_task_app.py +489 -0
  219. examples/task_apps/banking77_pipeline/deploy_wrapper.py +50 -0
  220. examples/task_apps/crafter/CREATE_SFT_DATASET.md +286 -0
  221. examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +152 -0
  222. examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +187 -0
  223. examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +281 -0
  224. examples/task_apps/crafter/QUERY_EXAMPLES.md +203 -0
  225. examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +316 -0
  226. examples/task_apps/crafter/eval_image_only_gpt4o.toml +28 -0
  227. examples/task_apps/crafter/eval_text_only_groq_llama.toml +36 -0
  228. examples/task_apps/crafter/filter_sft_dataset.toml +16 -0
  229. examples/task_apps/crafter/task_app/README.md +42 -0
  230. examples/task_apps/crafter/task_app/__init__.py +5 -0
  231. examples/task_apps/crafter/task_app/grpo_crafter.py +1055 -0
  232. examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +146 -0
  233. examples/task_apps/crafter/task_app/synth_envs_hosted/README.md +173 -0
  234. examples/task_apps/crafter/task_app/synth_envs_hosted/__init__.py +5 -0
  235. examples/task_apps/crafter/task_app/synth_envs_hosted/branching.py +143 -0
  236. examples/task_apps/crafter/task_app/synth_envs_hosted/environment_routes.py +1226 -0
  237. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/__init__.py +1 -0
  238. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
  239. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
  240. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +532 -0
  241. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +583 -0
  242. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +122 -0
  243. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
  244. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
  245. examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +253 -0
  246. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/__init__.py +5 -0
  247. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +999 -0
  248. examples/task_apps/crafter/task_app/synth_envs_hosted/main.py +100 -0
  249. examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +1252 -0
  250. examples/task_apps/crafter/task_app/synth_envs_hosted/registry.py +195 -0
  251. examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +2233 -0
  252. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/__init__.py +5 -0
  253. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/volume.py +211 -0
  254. examples/task_apps/crafter/task_app/synth_envs_hosted/test_agents.py +161 -0
  255. examples/task_apps/crafter/task_app/synth_envs_hosted/test_service.py +136 -0
  256. examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +411 -0
  257. examples/task_apps/dev/pokemon_emerald/__init__.py +2 -0
  258. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +811 -0
  259. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +120 -0
  260. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +160 -0
  261. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +155 -0
  262. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +69 -0
  263. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +96 -0
  264. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +1502 -0
  265. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +4 -0
  266. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +68 -0
  267. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +216 -0
  268. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +35 -0
  269. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +631 -0
  270. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +1544 -0
  271. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +1428 -0
  272. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +4848 -0
  273. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +41 -0
  274. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +298 -0
  275. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +95 -0
  276. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +204 -0
  277. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +2152 -0
  278. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +429 -0
  279. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +155 -0
  280. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +78 -0
  281. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +122 -0
  282. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +76 -0
  283. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +413 -0
  284. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +204 -0
  285. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +133 -0
  286. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +229 -0
  287. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +300 -0
  288. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +205 -0
  289. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +200 -0
  290. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +284 -0
  291. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +468 -0
  292. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +575 -0
  293. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +311 -0
  294. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +259 -0
  295. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +372 -0
  296. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +296 -0
  297. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +275 -0
  298. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +22 -0
  299. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +44 -0
  300. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +514 -0
  301. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +415 -0
  302. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +1763 -0
  303. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +33 -0
  304. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +106 -0
  305. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +334 -0
  306. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +1020 -0
  307. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +188 -0
  308. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +1481 -0
  309. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +862 -0
  310. examples/task_apps/dev/pokemon_emerald/modal_app.py +114 -0
  311. examples/task_apps/dev/pokemon_emerald/task_app/README.md +81 -0
  312. examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +6 -0
  313. examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +685 -0
  314. examples/task_apps/enron/__init__.py +2 -0
  315. examples/task_apps/enron/eval_groq_qwen32.toml +16 -0
  316. examples/task_apps/enron/filter_sft.toml +5 -0
  317. examples/task_apps/enron/task_app/README.md +14 -0
  318. examples/task_apps/enron/task_app/__init__.py +1 -0
  319. examples/task_apps/enron/task_app/grpo_enron.py +906 -0
  320. examples/task_apps/enron/task_app/grpo_enron_task_app.py +146 -0
  321. examples/task_apps/enron/tests/__init__.py +4 -0
  322. examples/task_apps/enron/tests/conftest.py +115 -0
  323. examples/task_apps/enron/tests/integration/__init__.py +4 -0
  324. examples/task_apps/enron/tests/integration/test_enron_eval.py +179 -0
  325. examples/task_apps/enron/tests/integration/test_enron_rollout.py +135 -0
  326. examples/task_apps/enron/tests/unit/__init__.py +4 -0
  327. examples/task_apps/enron/tests/unit/test_enron_environment.py +126 -0
  328. examples/task_apps/gepa_benchmarks/__init__.py +7 -0
  329. examples/task_apps/gepa_benchmarks/common.py +260 -0
  330. examples/task_apps/gepa_benchmarks/hotpotqa_task_app.py +507 -0
  331. examples/task_apps/gepa_benchmarks/hover_task_app.py +436 -0
  332. examples/task_apps/gepa_benchmarks/ifbench_task_app.py +563 -0
  333. examples/task_apps/gepa_benchmarks/pupa_task_app.py +460 -0
  334. examples/task_apps/math/README.md +21 -0
  335. examples/task_apps/math/math_single_step.py +1000 -0
  336. examples/task_apps/math/math_task_app.py +115 -0
  337. examples/task_apps/pokemon_battle/__init__.py +2 -0
  338. examples/task_apps/pokemon_battle/modal_app.py +104 -0
  339. examples/task_apps/pokemon_battle/task_app/README.md +68 -0
  340. examples/task_apps/pokemon_battle/task_app/__init__.py +6 -0
  341. examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +932 -0
  342. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +283 -0
  343. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +155 -0
  344. examples/task_apps/pokemon_red/README.md +356 -0
  345. examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +428 -0
  346. examples/task_apps/pokemon_red/__init__.py +3 -0
  347. examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +30 -0
  348. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +224 -0
  349. examples/task_apps/pokemon_red/pallet_town_rl_config.toml +75 -0
  350. examples/task_apps/pokemon_red/task_app.py +1048 -0
  351. examples/task_apps/pokemon_red/test_pallet_town_rewards.py +193 -0
  352. examples/task_apps/sokoban/README.md +306 -0
  353. examples/task_apps/sokoban/__init__.py +3 -0
  354. examples/task_apps/sokoban/eval_groq_qwen32.toml +16 -0
  355. examples/task_apps/sokoban/eval_openai_gpt5.toml +16 -0
  356. examples/task_apps/sokoban/filter_sft.toml +5 -0
  357. examples/task_apps/sokoban/task_app.py +1058 -0
  358. examples/task_apps/sokoban/tests/__init__.py +4 -0
  359. examples/task_apps/sokoban/tests/conftest.py +113 -0
  360. examples/task_apps/sokoban/tests/integration/__init__.py +4 -0
  361. examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +57 -0
  362. examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +198 -0
  363. examples/task_apps/sokoban/tests/unit/__init__.py +4 -0
  364. examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +114 -0
  365. examples/task_apps/verilog/__init__.py +1 -0
  366. examples/task_apps/verilog/eval_groq_qwen32b.toml +22 -0
  367. examples/task_apps/verilog/filter_sft.toml +5 -0
  368. examples/task_apps/verilog/task_app/README.md +12 -0
  369. examples/task_apps/verilog/task_app/__init__.py +1 -0
  370. examples/task_apps/verilog/task_app/grpo_verilog.py +1166 -0
  371. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +145 -0
  372. examples/task_apps/verilog/tests/__init__.py +4 -0
  373. examples/task_apps/verilog/tests/conftest.py +115 -0
  374. examples/task_apps/verilog/tests/integration/__init__.py +4 -0
  375. examples/task_apps/verilog/tests/integration/test_verilog_eval.py +181 -0
  376. examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +55 -0
  377. examples/task_apps/verilog/tests/unit/__init__.py +4 -0
  378. examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +118 -0
  379. examples/tunnel_gepa_banking77/README.md +106 -0
  380. examples/tunnel_gepa_banking77/banking77_gepa_tunnel.toml +95 -0
  381. examples/tunnel_gepa_banking77/keep_tunnel_running.py +60 -0
  382. examples/tunnel_gepa_banking77/run_gepa_with_tunnel.sh +226 -0
  383. examples/vlm/PROPOSAL.md +53 -0
  384. examples/vlm/README.md +68 -0
  385. examples/vlm/configs/crafter_vlm_gpt4o.toml +49 -0
  386. examples/vlm/crafter_image_only_agent.py +207 -0
  387. examples/vlm/crafter_openai_vlm_agent.py +275 -0
  388. examples/vlm/filter_image_rows.py +63 -0
  389. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  390. examples/warming_up_to_rl/_utils.py +92 -0
  391. examples/warming_up_to_rl/analyze_trace_db.py +422 -0
  392. examples/warming_up_to_rl/configs/crafter_fft.toml +53 -0
  393. examples/warming_up_to_rl/configs/crafter_fft_4b.toml +54 -0
  394. examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +22 -0
  395. examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +15 -0
  396. examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +24 -0
  397. examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +35 -0
  398. examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +26 -0
  399. examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +36 -0
  400. examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +32 -0
  401. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +85 -0
  402. examples/warming_up_to_rl/configs/rl_from_ft.toml +58 -0
  403. examples/warming_up_to_rl/export_trace_sft.py +837 -0
  404. examples/warming_up_to_rl/groq_test.py +97 -0
  405. examples/warming_up_to_rl/manage_secrets.py +131 -0
  406. examples/warming_up_to_rl/old/event_rewards.md +234 -0
  407. examples/warming_up_to_rl/old/notes.md +73 -0
  408. examples/warming_up_to_rl/readme.md +110 -0
  409. examples/warming_up_to_rl/run_eval.py +736 -0
  410. examples/warming_up_to_rl/run_fft_and_save.py +380 -0
  411. examples/warming_up_to_rl/run_local_rollout.py +239 -0
  412. examples/warming_up_to_rl/run_local_rollout_modal.py +248 -0
  413. examples/warming_up_to_rl/run_local_rollout_parallel.py +405 -0
  414. examples/warming_up_to_rl/run_local_rollout_traced.py +477 -0
  415. examples/warming_up_to_rl/run_rl_and_save.py +124 -0
  416. examples/warming_up_to_rl/run_rollout_remote.py +156 -0
  417. examples/warming_up_to_rl/task_app/README.md +42 -0
  418. examples/warming_up_to_rl/task_app/grpo_crafter.py +876 -0
  419. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
  420. examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
  421. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
  422. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
  423. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
  424. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
  425. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
  426. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
  427. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
  428. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +454 -0
  429. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
  430. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
  431. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
  432. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +253 -0
  433. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
  434. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +729 -0
  435. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
  436. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1114 -0
  437. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
  438. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1891 -0
  439. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
  440. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
  441. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
  442. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
  443. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +129 -0
  444. examples/workflows/math_rl/configs/eval_base_qwen.toml +15 -0
  445. examples/workflows/math_rl/configs/eval_rl_qwen.toml +11 -0
  446. examples/workflows/math_rl/configs/rl_from_base_qwen.toml +62 -0
  447. examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +80 -0
  448. examples/workflows/math_rl/configs/rl_from_ft_qwen.toml +35 -0
  449. examples/workflows/math_rl/download_dataset.py +80 -0
  450. examples/workflows/math_rl/run_eval.py +436 -0
  451. examples/workflows/math_rl/run_rl_and_save.py +111 -0
  452. synth_ai/__init__.py +47 -23
  453. synth_ai/_utils/__init__.py +47 -0
  454. synth_ai/_utils/base_url.py +10 -0
  455. synth_ai/_utils/http.py +10 -0
  456. synth_ai/_utils/prompts.py +10 -0
  457. synth_ai/_utils/task_app_state.py +12 -0
  458. synth_ai/_utils/user_config.py +10 -0
  459. synth_ai/api/models/supported.py +514 -0
  460. synth_ai/api/train/__init__.py +60 -2
  461. synth_ai/api/train/builders.py +347 -39
  462. synth_ai/api/train/cli.py +895 -160
  463. synth_ai/api/train/config_finder.py +103 -25
  464. synth_ai/api/train/configs/__init__.py +65 -0
  465. synth_ai/api/train/configs/prompt_learning.py +496 -0
  466. synth_ai/api/train/configs/rl.py +188 -0
  467. synth_ai/api/train/configs/sft.py +99 -0
  468. synth_ai/api/train/configs/shared.py +81 -0
  469. synth_ai/api/train/env_resolver.py +70 -20
  470. synth_ai/api/train/pollers.py +29 -4
  471. synth_ai/api/train/prompt_learning.py +425 -0
  472. synth_ai/api/train/sft.py +390 -0
  473. synth_ai/api/train/supported_algos.py +147 -0
  474. synth_ai/api/train/task_app.py +6 -4
  475. synth_ai/api/train/utils.py +64 -52
  476. synth_ai/api/train/validators.py +1117 -0
  477. synth_ai/api/tunnel.py +49 -0
  478. synth_ai/auth/credentials.py +94 -0
  479. synth_ai/baseline/__init__.py +25 -0
  480. synth_ai/baseline/config.py +209 -0
  481. synth_ai/baseline/discovery.py +214 -0
  482. synth_ai/baseline/execution.py +146 -0
  483. synth_ai/cfgs.py +227 -0
  484. synth_ai/cli/__init__.py +85 -63
  485. synth_ai/cli/_modal_wrapper.py +31 -0
  486. synth_ai/cli/_storage.py +20 -0
  487. synth_ai/cli/_typer_patch.py +47 -0
  488. synth_ai/cli/_validate_task_app.py +29 -0
  489. synth_ai/cli/balance.py +16 -4
  490. synth_ai/cli/calc.py +36 -21
  491. synth_ai/cli/claude.py +70 -0
  492. synth_ai/cli/codex.py +267 -0
  493. synth_ai/cli/commands/__init__.py +18 -0
  494. synth_ai/cli/commands/baseline/__init__.py +12 -0
  495. synth_ai/cli/commands/baseline/core.py +637 -0
  496. synth_ai/cli/commands/baseline/list.py +93 -0
  497. synth_ai/cli/commands/demo/__init__.py +6 -0
  498. synth_ai/cli/commands/demo/core.py +163 -0
  499. synth_ai/cli/commands/eval/__init__.py +19 -0
  500. synth_ai/cli/commands/eval/core.py +1112 -0
  501. synth_ai/cli/commands/eval/errors.py +81 -0
  502. synth_ai/cli/commands/eval/validation.py +133 -0
  503. synth_ai/cli/commands/filter/__init__.py +12 -0
  504. synth_ai/cli/commands/filter/core.py +424 -0
  505. synth_ai/cli/commands/filter/errors.py +55 -0
  506. synth_ai/cli/commands/filter/validation.py +77 -0
  507. synth_ai/cli/commands/help/__init__.py +185 -0
  508. synth_ai/cli/commands/help/core.py +72 -0
  509. synth_ai/cli/commands/smoke/__init__.py +7 -0
  510. synth_ai/cli/commands/smoke/core.py +1437 -0
  511. synth_ai/cli/commands/status/__init__.py +66 -0
  512. synth_ai/cli/commands/status/client.py +192 -0
  513. synth_ai/cli/commands/status/config.py +92 -0
  514. synth_ai/cli/commands/status/errors.py +20 -0
  515. synth_ai/cli/commands/status/formatters.py +164 -0
  516. synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
  517. synth_ai/cli/commands/status/subcommands/files.py +79 -0
  518. synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
  519. synth_ai/cli/commands/status/subcommands/models.py +79 -0
  520. synth_ai/cli/commands/status/subcommands/pricing.py +22 -0
  521. synth_ai/cli/commands/status/subcommands/runs.py +81 -0
  522. synth_ai/cli/commands/status/subcommands/session.py +183 -0
  523. synth_ai/cli/commands/status/subcommands/summary.py +47 -0
  524. synth_ai/cli/commands/status/subcommands/usage.py +203 -0
  525. synth_ai/cli/commands/status/utils.py +114 -0
  526. synth_ai/cli/commands/train/__init__.py +53 -0
  527. synth_ai/cli/commands/train/core.py +21 -0
  528. synth_ai/cli/commands/train/errors.py +117 -0
  529. synth_ai/cli/commands/train/judge_schemas.py +200 -0
  530. synth_ai/cli/commands/train/judge_validation.py +305 -0
  531. synth_ai/cli/commands/train/validation.py +386 -0
  532. synth_ai/cli/demo.py +32 -140
  533. synth_ai/cli/deploy.py +233 -0
  534. synth_ai/cli/eval/__init__.py +36 -0
  535. synth_ai/cli/eval/core.py +5 -0
  536. synth_ai/cli/eval/errors.py +31 -0
  537. synth_ai/cli/eval/validation.py +5 -0
  538. synth_ai/cli/filter/__init__.py +28 -0
  539. synth_ai/cli/filter/core.py +5 -0
  540. synth_ai/cli/filter/errors.py +23 -0
  541. synth_ai/cli/filter/validation.py +5 -0
  542. synth_ai/cli/legacy_root_backup.py +28 -22
  543. synth_ai/cli/lib/__init__.py +10 -0
  544. synth_ai/cli/lib/task_app_discovery.py +7 -0
  545. synth_ai/cli/lib/task_app_env.py +518 -0
  546. synth_ai/cli/mcp.py +34 -0
  547. synth_ai/cli/modal_serve/__init__.py +12 -0
  548. synth_ai/cli/modal_serve/core.py +14 -0
  549. synth_ai/cli/modal_serve/errors.py +8 -0
  550. synth_ai/cli/modal_serve/validation.py +11 -0
  551. synth_ai/cli/opencode.py +256 -0
  552. synth_ai/cli/recent.py +13 -7
  553. synth_ai/cli/rl_demo.py +156 -116
  554. synth_ai/cli/root.py +131 -132
  555. synth_ai/cli/serve/__init__.py +12 -0
  556. synth_ai/cli/serve/core.py +14 -0
  557. synth_ai/cli/serve/errors.py +8 -0
  558. synth_ai/cli/serve/validation.py +11 -0
  559. synth_ai/cli/setup.py +49 -0
  560. synth_ai/cli/status.py +7 -125
  561. synth_ai/cli/task_app_deploy.py +7 -0
  562. synth_ai/cli/task_app_list.py +25 -0
  563. synth_ai/cli/task_app_modal_serve.py +11 -0
  564. synth_ai/cli/task_app_serve.py +11 -0
  565. synth_ai/cli/task_apps.py +2284 -257
  566. synth_ai/cli/traces.py +9 -5
  567. synth_ai/cli/train/__init__.py +12 -0
  568. synth_ai/cli/train/core.py +21 -0
  569. synth_ai/cli/train/errors.py +8 -0
  570. synth_ai/cli/train/validation.py +24 -0
  571. synth_ai/cli/train.py +5 -0
  572. synth_ai/cli/turso.py +73 -0
  573. synth_ai/cli/watch.py +13 -18
  574. synth_ai/demos/__init__.py +10 -0
  575. synth_ai/demos/core/__init__.py +28 -1
  576. synth_ai/demos/core/cli.py +579 -291
  577. synth_ai/demos/crafter/__init__.py +1 -0
  578. synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
  579. synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
  580. synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
  581. synth_ai/demos/demo_registry.py +176 -0
  582. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  583. synth_ai/demos/demo_task_apps/core.py +64 -28
  584. synth_ai/demos/demo_task_apps/crafter/__init__.py +1 -0
  585. synth_ai/demos/demo_task_apps/crafter/configs/crafter_fft_4b.toml +53 -0
  586. synth_ai/demos/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +73 -0
  587. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +184 -0
  588. synth_ai/demos/demo_task_apps/math/_common.py +1 -2
  589. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  590. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -6
  591. synth_ai/demos/demo_task_apps/math/modal_task_app.py +185 -83
  592. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -2
  593. synth_ai/demos/math/__init__.py +1 -0
  594. synth_ai/demos/math/_common.py +16 -0
  595. synth_ai/demos/math/app.py +38 -0
  596. synth_ai/demos/math/config.toml +76 -0
  597. synth_ai/demos/math/deploy_modal.py +54 -0
  598. synth_ai/demos/math/modal_task_app.py +703 -0
  599. synth_ai/demos/math/task_app_entry.py +51 -0
  600. synth_ai/environments/environment/core.py +7 -1
  601. synth_ai/environments/examples/bandit/engine.py +12 -5
  602. synth_ai/environments/examples/bandit/environment.py +0 -1
  603. synth_ai/environments/examples/bandit/taskset.py +4 -4
  604. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +7 -4
  605. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +9 -5
  606. synth_ai/environments/examples/crafter_classic/environment.py +93 -2
  607. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +4 -3
  608. synth_ai/environments/examples/enron/engine.py +7 -2
  609. synth_ai/environments/examples/enron/environment.py +68 -0
  610. synth_ai/environments/examples/red/engine.py +60 -12
  611. synth_ai/environments/examples/red/engine_helpers/memory_map.py +7 -0
  612. synth_ai/environments/examples/red/engine_helpers/reward_components.py +151 -179
  613. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +477 -0
  614. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +32 -0
  615. synth_ai/environments/examples/red/environment.py +86 -0
  616. synth_ai/environments/examples/red/trace_hooks_v3.py +168 -0
  617. synth_ai/environments/examples/sokoban/taskset.py +116 -0
  618. synth_ai/environments/examples/verilog/engine.py +104 -12
  619. synth_ai/environments/examples/wordle/environment.py +0 -1
  620. synth_ai/environments/reproducibility/tree.py +5 -6
  621. synth_ai/environments/service/app.py +11 -12
  622. synth_ai/environments/service/core_routes.py +10 -9
  623. synth_ai/environments/stateful/engine.py +1 -1
  624. synth_ai/environments/tasks/core.py +1 -0
  625. synth_ai/environments/tasks/filters.py +5 -6
  626. synth_ai/environments/tasks/utils.py +4 -5
  627. synth_ai/evals/__init__.py +15 -0
  628. synth_ai/evals/base.py +14 -5
  629. synth_ai/evals/client.py +82 -0
  630. synth_ai/evals/types.py +42 -0
  631. synth_ai/http.py +8 -22
  632. synth_ai/http_client.py +45 -12
  633. synth_ai/inference/__init__.py +0 -2
  634. synth_ai/inference/client.py +21 -7
  635. synth_ai/jobs/client.py +129 -80
  636. synth_ai/judge_schemas.py +127 -0
  637. synth_ai/learning/__init__.py +51 -6
  638. synth_ai/learning/algorithms.py +14 -0
  639. synth_ai/learning/client.py +122 -30
  640. synth_ai/learning/config.py +2 -40
  641. synth_ai/learning/constants.py +0 -2
  642. synth_ai/learning/ft_client.py +4 -56
  643. synth_ai/learning/health.py +14 -8
  644. synth_ai/learning/jobs.py +43 -47
  645. synth_ai/learning/prompt_learning_client.py +276 -0
  646. synth_ai/learning/prompt_learning_types.py +185 -0
  647. synth_ai/{rl → learning/rl}/__init__.py +14 -5
  648. synth_ai/learning/rl/client.py +269 -0
  649. synth_ai/learning/rl/config.py +31 -0
  650. synth_ai/{rl → learning/rl}/contracts.py +5 -10
  651. synth_ai/{rl → learning/rl}/env_keys.py +45 -16
  652. synth_ai/learning/rl/secrets.py +13 -0
  653. synth_ai/learning/rl_client.py +2 -253
  654. synth_ai/learning/sft/__init__.py +29 -0
  655. synth_ai/learning/sft/client.py +68 -0
  656. synth_ai/learning/sft/config.py +270 -0
  657. synth_ai/learning/sft/data.py +698 -0
  658. synth_ai/learning/sse.py +25 -26
  659. synth_ai/learning/validators.py +29 -25
  660. synth_ai/mcp/__init__.py +5 -0
  661. synth_ai/mcp/__main__.py +8 -0
  662. synth_ai/mcp/main.py +254 -0
  663. synth_ai/mcp/setup.py +100 -0
  664. synth_ai/modal.py +257 -0
  665. synth_ai/pricing/__init__.py +3 -0
  666. synth_ai/pricing/model_pricing.py +64 -0
  667. synth_ai/session/__init__.py +75 -0
  668. synth_ai/session/client.py +383 -0
  669. synth_ai/session/constants.py +63 -0
  670. synth_ai/session/exceptions.py +105 -0
  671. synth_ai/session/manager.py +139 -0
  672. synth_ai/session/models.py +89 -0
  673. synth_ai/session/query.py +110 -0
  674. synth_ai/spec/__init__.py +46 -0
  675. synth_ai/spec/dataclasses.py +149 -0
  676. synth_ai/spec/loader.py +144 -0
  677. synth_ai/spec/serializer.py +199 -0
  678. synth_ai/spec/validation.py +250 -0
  679. synth_ai/streaming/__init__.py +29 -0
  680. synth_ai/streaming/config.py +94 -0
  681. synth_ai/streaming/handlers.py +589 -0
  682. synth_ai/streaming/streamer.py +320 -0
  683. synth_ai/streaming/types.py +95 -0
  684. synth_ai/task/__init__.py +50 -30
  685. synth_ai/task/apps/__init__.py +63 -19
  686. synth_ai/task/auth.py +35 -23
  687. synth_ai/task/client.py +15 -13
  688. synth_ai/task/config.py +261 -0
  689. synth_ai/task/contracts.py +165 -64
  690. synth_ai/task/datasets.py +9 -6
  691. synth_ai/task/errors.py +11 -10
  692. synth_ai/task/health.py +17 -11
  693. synth_ai/task/inference_api.py +101 -0
  694. synth_ai/task/json.py +58 -24
  695. synth_ai/task/proxy.py +59 -66
  696. synth_ai/task/rubrics/__init__.py +55 -0
  697. synth_ai/task/rubrics/loaders.py +156 -0
  698. synth_ai/task/rubrics/models.py +57 -0
  699. synth_ai/task/rubrics/scoring.py +116 -0
  700. synth_ai/task/rubrics/strict.py +149 -0
  701. synth_ai/task/rubrics.py +22 -15
  702. synth_ai/task/server.py +65 -31
  703. synth_ai/task/trace_correlation_helpers.py +328 -0
  704. synth_ai/task/tracing_utils.py +44 -28
  705. synth_ai/task/validators.py +449 -6
  706. synth_ai/task/vendors.py +5 -7
  707. synth_ai/tracing_v3/__init__.py +4 -0
  708. synth_ai/tracing_v3/abstractions.py +21 -4
  709. synth_ai/tracing_v3/config.py +167 -22
  710. synth_ai/tracing_v3/constants.py +21 -0
  711. synth_ai/tracing_v3/db_config.py +42 -29
  712. synth_ai/tracing_v3/decorators.py +80 -45
  713. synth_ai/tracing_v3/examples/basic_usage.py +15 -9
  714. synth_ai/tracing_v3/hooks.py +6 -4
  715. synth_ai/tracing_v3/llm_call_record_helpers.py +161 -61
  716. synth_ai/tracing_v3/migration_helper.py +1 -2
  717. synth_ai/tracing_v3/replica_sync.py +12 -7
  718. synth_ai/tracing_v3/serialization.py +130 -0
  719. synth_ai/tracing_v3/session_tracer.py +73 -16
  720. synth_ai/tracing_v3/storage/base.py +89 -1
  721. synth_ai/tracing_v3/storage/config.py +63 -16
  722. synth_ai/tracing_v3/storage/factory.py +11 -9
  723. synth_ai/tracing_v3/storage/utils.py +15 -11
  724. synth_ai/tracing_v3/trace_utils.py +317 -0
  725. synth_ai/tracing_v3/turso/__init__.py +8 -21
  726. synth_ai/tracing_v3/turso/daemon.py +123 -15
  727. synth_ai/tracing_v3/turso/models.py +5 -2
  728. synth_ai/tracing_v3/turso/native_manager.py +1293 -0
  729. synth_ai/tracing_v3/utils.py +5 -4
  730. synth_ai/tunnel.py +143 -0
  731. synth_ai/tunnel_deploy.py +278 -0
  732. synth_ai/types.py +8 -0
  733. synth_ai/urls.py +11 -0
  734. synth_ai/utils/__init__.py +166 -0
  735. synth_ai/utils/agents.py +74 -0
  736. synth_ai/utils/apps.py +152 -0
  737. synth_ai/utils/base_url.py +94 -0
  738. synth_ai/utils/bin.py +39 -0
  739. synth_ai/utils/claude.py +36 -0
  740. synth_ai/utils/cli.py +284 -0
  741. synth_ai/utils/config.py +81 -0
  742. synth_ai/utils/env.py +346 -0
  743. synth_ai/utils/errors.py +85 -0
  744. synth_ai/utils/http.py +172 -0
  745. synth_ai/utils/json.py +72 -0
  746. synth_ai/utils/log_filter.py +99 -0
  747. synth_ai/utils/logging.py +198 -0
  748. synth_ai/utils/modal.py +299 -0
  749. synth_ai/utils/paths.py +95 -0
  750. synth_ai/utils/process.py +233 -0
  751. synth_ai/utils/prompts.py +39 -0
  752. synth_ai/utils/sqld.py +122 -0
  753. synth_ai/utils/ssl.py +25 -0
  754. synth_ai/utils/task_app_discovery.py +882 -0
  755. synth_ai/utils/task_app_env.py +186 -0
  756. synth_ai/utils/task_app_state.py +318 -0
  757. synth_ai/utils/tunnel/__init__.py +12 -0
  758. synth_ai/utils/tunnel/config.py +55 -0
  759. synth_ai/utils/user_config.py +137 -0
  760. synth_ai/uvicorn.py +77 -0
  761. synth_ai-0.2.23.dev3.dist-info/METADATA +357 -0
  762. synth_ai-0.2.23.dev3.dist-info/RECORD +983 -0
  763. {synth_ai-0.2.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/entry_points.txt +0 -1
  764. {synth_ai-0.2.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/top_level.txt +1 -0
  765. synth_ai/cli/man.py +0 -106
  766. synth_ai/core/experiment.py +0 -15
  767. synth_ai/core/system.py +0 -15
  768. synth_ai/demo_registry.py +0 -258
  769. synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
  770. synth_ai/experimental/synth_oss.py +0 -446
  771. synth_ai/handshake.py +0 -107
  772. synth_ai/install_sqld.sh +0 -40
  773. synth_ai/learning/offline/dpo.py +0 -0
  774. synth_ai/learning/offline/providers.py +0 -7
  775. synth_ai/learning/offline/sft.py +0 -0
  776. synth_ai/learning/offline/shared.py +0 -0
  777. synth_ai/learning/online/grpo.py +0 -0
  778. synth_ai/learning/online/irft.py +0 -0
  779. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  780. synth_ai/learning/prompts/gepa.py +0 -0
  781. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
  782. synth_ai/learning/prompts/mipro.py +0 -289
  783. synth_ai/learning/prompts/random_search.py +0 -246
  784. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  785. synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
  786. synth_ai/lm/__init__.py +0 -51
  787. synth_ai/lm/caching/constants.py +0 -6
  788. synth_ai/lm/caching/dbs.py +0 -0
  789. synth_ai/lm/caching/ephemeral.py +0 -102
  790. synth_ai/lm/caching/handler.py +0 -137
  791. synth_ai/lm/caching/initialize.py +0 -11
  792. synth_ai/lm/caching/persistent.py +0 -114
  793. synth_ai/lm/config.py +0 -110
  794. synth_ai/lm/constants.py +0 -32
  795. synth_ai/lm/core/__init__.py +0 -8
  796. synth_ai/lm/core/all.py +0 -73
  797. synth_ai/lm/core/exceptions.py +0 -7
  798. synth_ai/lm/core/main.py +0 -319
  799. synth_ai/lm/core/main_v3.py +0 -594
  800. synth_ai/lm/core/synth_models.py +0 -48
  801. synth_ai/lm/core/vendor_clients.py +0 -188
  802. synth_ai/lm/cost/monitor.py +0 -1
  803. synth_ai/lm/cost/statefulness.py +0 -1
  804. synth_ai/lm/injection.py +0 -80
  805. synth_ai/lm/overrides.py +0 -206
  806. synth_ai/lm/provider_support/__init__.py +0 -8
  807. synth_ai/lm/provider_support/anthropic.py +0 -972
  808. synth_ai/lm/provider_support/openai.py +0 -1139
  809. synth_ai/lm/provider_support/suppress_logging.py +0 -31
  810. synth_ai/lm/structured_outputs/handler.py +0 -440
  811. synth_ai/lm/structured_outputs/inject.py +0 -297
  812. synth_ai/lm/structured_outputs/rehabilitate.py +0 -185
  813. synth_ai/lm/tools/__init__.py +0 -3
  814. synth_ai/lm/tools/base.py +0 -172
  815. synth_ai/lm/unified_interface.py +0 -202
  816. synth_ai/lm/vendors/base.py +0 -81
  817. synth_ai/lm/vendors/core/anthropic_api.py +0 -387
  818. synth_ai/lm/vendors/core/gemini_api.py +0 -292
  819. synth_ai/lm/vendors/core/mistral_api.py +0 -322
  820. synth_ai/lm/vendors/core/openai_api.py +0 -225
  821. synth_ai/lm/vendors/core/synth_dev_api.py +0 -0
  822. synth_ai/lm/vendors/local/ollama.py +0 -0
  823. synth_ai/lm/vendors/openai_standard.py +0 -780
  824. synth_ai/lm/vendors/openai_standard_responses.py +0 -256
  825. synth_ai/lm/vendors/retries.py +0 -22
  826. synth_ai/lm/vendors/supported/custom_endpoint.py +0 -417
  827. synth_ai/lm/vendors/supported/deepseek.py +0 -69
  828. synth_ai/lm/vendors/supported/grok.py +0 -75
  829. synth_ai/lm/vendors/supported/groq.py +0 -16
  830. synth_ai/lm/vendors/supported/ollama.py +0 -15
  831. synth_ai/lm/vendors/supported/openrouter.py +0 -74
  832. synth_ai/lm/vendors/supported/together.py +0 -11
  833. synth_ai/lm/vendors/synth_client.py +0 -808
  834. synth_ai/lm/warmup.py +0 -186
  835. synth_ai/rl/secrets.py +0 -19
  836. synth_ai/scripts/verify_rewards.py +0 -100
  837. synth_ai/task/apps/grpo_crafter.py +0 -438
  838. synth_ai/tracing/__init__.py +0 -30
  839. synth_ai/tracing_v1/__init__.py +0 -33
  840. synth_ai/tracing_v3/turso/manager.py +0 -774
  841. synth_ai/v0/tracing/abstractions.py +0 -224
  842. synth_ai/v0/tracing/base_client.py +0 -91
  843. synth_ai/v0/tracing/client_manager.py +0 -131
  844. synth_ai/v0/tracing/config.py +0 -142
  845. synth_ai/v0/tracing/context.py +0 -146
  846. synth_ai/v0/tracing/decorators.py +0 -682
  847. synth_ai/v0/tracing/events/__init__.py +0 -0
  848. synth_ai/v0/tracing/events/manage.py +0 -147
  849. synth_ai/v0/tracing/events/scope.py +0 -86
  850. synth_ai/v0/tracing/events/store.py +0 -228
  851. synth_ai/v0/tracing/immediate_client.py +0 -151
  852. synth_ai/v0/tracing/local.py +0 -18
  853. synth_ai/v0/tracing/log_client_base.py +0 -73
  854. synth_ai/v0/tracing/retry_queue.py +0 -186
  855. synth_ai/v0/tracing/trackers.py +0 -515
  856. synth_ai/v0/tracing/upload.py +0 -512
  857. synth_ai/v0/tracing/utils.py +0 -9
  858. synth_ai/v0/tracing_v1/__init__.py +0 -16
  859. synth_ai/v0/tracing_v1/abstractions.py +0 -224
  860. synth_ai/v0/tracing_v1/base_client.py +0 -91
  861. synth_ai/v0/tracing_v1/client_manager.py +0 -131
  862. synth_ai/v0/tracing_v1/config.py +0 -142
  863. synth_ai/v0/tracing_v1/context.py +0 -146
  864. synth_ai/v0/tracing_v1/decorators.py +0 -703
  865. synth_ai/v0/tracing_v1/events/__init__.py +0 -0
  866. synth_ai/v0/tracing_v1/events/manage.py +0 -147
  867. synth_ai/v0/tracing_v1/events/scope.py +0 -86
  868. synth_ai/v0/tracing_v1/events/store.py +0 -228
  869. synth_ai/v0/tracing_v1/immediate_client.py +0 -151
  870. synth_ai/v0/tracing_v1/local.py +0 -18
  871. synth_ai/v0/tracing_v1/log_client_base.py +0 -73
  872. synth_ai/v0/tracing_v1/retry_queue.py +0 -186
  873. synth_ai/v0/tracing_v1/trackers.py +0 -515
  874. synth_ai/v0/tracing_v1/upload.py +0 -527
  875. synth_ai/v0/tracing_v1/utils.py +0 -9
  876. synth_ai/zyk/__init__.py +0 -30
  877. synth_ai-0.2.9.dev0.dist-info/METADATA +0 -131
  878. synth_ai-0.2.9.dev0.dist-info/RECORD +0 -444
  879. {synth_ai/lm/caching → examples/task_apps}/__init__.py +0 -0
  880. {synth_ai/lm/cost → examples/task_apps/crafter}/__init__.py +0 -0
  881. {synth_ai/lm/structured_outputs → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server}/__init__.py +0 -0
  882. {synth_ai/lm/vendors → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests}/__init__.py +0 -0
  883. {synth_ai/lm/vendors/core → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils}/__init__.py +0 -0
  884. {synth_ai/lm/vendors/local → examples/task_apps/math}/__init__.py +0 -0
  885. {synth_ai/lm/vendors/supported → examples/workflows}/__init__.py +0 -0
  886. {synth_ai/v0/tracing → examples/workflows/math_rl}/__init__.py +0 -0
  887. /synth_ai/{compound/cais.py → cli/__main__.py} +0 -0
  888. /synth_ai/{learning/filtering.py → py.typed} +0 -0
  889. {synth_ai-0.2.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/WHEEL +0 -0
  890. {synth_ai-0.2.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -1,25 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
- import argparse
3
+ import contextlib
4
4
  import json
5
5
  import os
6
- import sys
7
- import time
8
- from pathlib import Path
9
- from typing import Any, Dict, Callable
10
6
  import shutil
11
7
  import stat
8
+ import sys
12
9
  import textwrap
10
+ import time
11
+ from collections.abc import Callable
12
+ from pathlib import Path
13
+ from typing import Any
13
14
 
14
- from synth_ai.demos.demo_task_apps import core as demo_core
15
- from synth_ai.demos.demo_task_apps.core import DemoEnv, DEFAULT_TASK_APP_SECRET_NAME
16
15
  from synth_ai.demo_registry import (
17
- CopySpec,
18
16
  DemoTemplate,
19
17
  get_demo_template,
20
18
  list_demo_templates,
21
19
  )
22
- from synth_ai.handshake import run_handshake, HandshakeError
20
+ from synth_ai.demos.demo_task_apps import core as demo_core
21
+ from synth_ai.demos.demo_task_apps.core import DEFAULT_TASK_APP_SECRET_NAME, DemoEnv
22
+ from synth_ai.handshake import HandshakeError, run_handshake
23
+ from synth_ai.utils.process import get_subprocess_env, should_filter_log_line
23
24
 
24
25
 
25
26
  def _key_preview(value: str, label: str) -> str:
@@ -44,35 +45,71 @@ def _is_modal_public_url(u: str) -> bool:
44
45
  return False
45
46
 
46
47
 
47
- def cmd_setup(_args: argparse.Namespace) -> int:
48
- # 1) Always perform SDK handshake and overwrite .env with returned keys
48
+ def setup() -> int:
49
+ # Change to demo directory if stored
50
+ demo_dir = demo_core.load_demo_dir()
51
+ if demo_dir and os.path.isdir(demo_dir):
52
+ os.chdir(demo_dir)
53
+ print(f"Using demo directory: {demo_dir}")
54
+
55
+ # 1) Try to fetch keys from frontend; fall back to manual input if fetch fails
56
+ synth_key = ""
57
+ rl_env_key = ""
58
+ org_name = "this organization"
59
+
49
60
  try:
50
61
  print("\n⏳ Connecting SDK to your browser session…")
51
62
  res = run_handshake()
52
- user = res.get("user") or {}
53
63
  org = res.get("org") or {}
54
64
  keys = res.get("keys") or {}
55
65
  synth_key = str(keys.get("synth") or "").strip()
56
66
  rl_env_key = str(keys.get("rl_env") or "").strip()
57
- if not synth_key or not rl_env_key:
58
- raise HandshakeError("handshake returned missing keys")
59
- # Overwrite .env with the latest values from the account/org
60
- demo_core.persist_dotenv_values({
67
+ org_name = org.get("name") or "this organization"
68
+ print(f" Connected to {org_name}!")
69
+ except (HandshakeError, Exception) as e:
70
+ print(f"⚠️ Failed to fetch keys from frontend: {e}")
71
+ print("Falling back to manual entry...")
72
+
73
+ # Prompt for manual input if any key is missing
74
+ if not synth_key:
75
+ try:
76
+ synth_key = input(
77
+ "Failed to fetch your Synth API key. Please enter your Synth API key here:\n> "
78
+ ).strip()
79
+ except (EOFError, KeyboardInterrupt):
80
+ print("\nSetup cancelled.")
81
+ return 1
82
+ if not synth_key:
83
+ print("Synth API key is required.")
84
+ return 1
85
+
86
+ if not rl_env_key:
87
+ try:
88
+ rl_env_key = input(
89
+ "Failed to fetch your RL Environment API key. Please enter your RL Environment API key here:\n> "
90
+ ).strip()
91
+ except (EOFError, KeyboardInterrupt):
92
+ print("\nSetup cancelled.")
93
+ return 1
94
+ if not rl_env_key:
95
+ print("RL Environment API key is required.")
96
+ return 1
97
+
98
+ # Persist both keys to .env
99
+ dotenv_path = demo_core.persist_dotenv_values(
100
+ {
61
101
  "SYNTH_API_KEY": synth_key,
62
102
  "ENVIRONMENT_API_KEY": rl_env_key,
63
- })
64
- org_name = (org.get("name") or "this organization")
65
- print(f"✅ Connected to {org_name}!")
66
- except HandshakeError as e:
67
- print(f"Handshake failed: {e}")
68
- return 1
69
- except Exception as e:
70
- print(f"Unexpected handshake error: {e}")
71
- return 1
103
+ }
104
+ )
105
+
106
+ # Store .env path for subsequent commands
107
+ demo_core.persist_env_file_path(dotenv_path)
72
108
 
73
109
  # 2) Reload env after handshake to pick up values from .env (suppress env prints)
74
- import io
75
110
  import contextlib
111
+ import io
112
+
76
113
  _buf = io.StringIO()
77
114
  with contextlib.redirect_stdout(_buf):
78
115
  env = demo_core.load_env()
@@ -89,22 +126,22 @@ def cmd_setup(_args: argparse.Namespace) -> int:
89
126
  return
90
127
  current = env.task_app_base_url
91
128
  needs_lookup = False
92
- if not current:
93
- needs_lookup = True
94
- elif not _is_modal_public_url(current):
129
+ if not current or not _is_modal_public_url(current):
95
130
  needs_lookup = True
96
131
  if not needs_lookup:
97
132
  return
98
- code, out = _popen_capture([
99
- "uv",
100
- "run",
101
- "python",
102
- "-m",
103
- "modal",
104
- "app",
105
- "url",
106
- env.task_app_name,
107
- ])
133
+ code, out = _popen_capture(
134
+ [
135
+ "uv",
136
+ "run",
137
+ "python",
138
+ "-m",
139
+ "modal",
140
+ "app",
141
+ "url",
142
+ env.task_app_name,
143
+ ]
144
+ )
108
145
  if code != 0 or not out:
109
146
  return
110
147
  new_url = ""
@@ -134,15 +171,16 @@ def cmd_setup(_args: argparse.Namespace) -> int:
134
171
 
135
172
  _maybe_fix_task_url()
136
173
 
137
- ok_backend = False
138
- ok_task = False
139
174
  if env.dev_backend_url:
140
- api = env.dev_backend_url.rstrip("/") + ("" if env.dev_backend_url.endswith("/api") else "/api")
141
- ok_backend = demo_core.assert_http_ok(api + "/health", method="GET")
175
+ api = env.dev_backend_url.rstrip("/") + (
176
+ "" if env.dev_backend_url.endswith("/api") else "/api"
177
+ )
178
+ demo_core.assert_http_ok(api + "/health", method="GET")
142
179
  # Intentionally suppress backend health print for concise output
143
180
  if env.task_app_base_url:
144
- ok_task = demo_core.assert_http_ok(env.task_app_base_url.rstrip("/") + "/health", method="GET") or \
145
- demo_core.assert_http_ok(env.task_app_base_url.rstrip("/"), method="GET")
181
+ demo_core.assert_http_ok(
182
+ env.task_app_base_url.rstrip("/") + "/health", method="GET"
183
+ ) or demo_core.assert_http_ok(env.task_app_base_url.rstrip("/"), method="GET")
146
184
  # Intentionally suppress task app health print
147
185
  else:
148
186
  print("\nSet your task app URL by running:\nuvx synth-ai rl_demo deploy\n")
@@ -150,13 +188,19 @@ def cmd_setup(_args: argparse.Namespace) -> int:
150
188
  # Omit uv version print to keep output concise
151
189
 
152
190
  # Keep exit code neutral; not all checks are critical for pairing
191
+ print(f"\nKeys saved to: {dotenv_path}")
153
192
  return 0
154
193
 
155
194
 
156
- def _popen_capture(cmd: list[str], cwd: str | None = None, env: dict | None = None) -> tuple[int, str]:
195
+ def _popen_capture(
196
+ cmd: list[str], cwd: str | None = None, env: dict | None = None
197
+ ) -> tuple[int, str]:
157
198
  import subprocess
199
+
158
200
  try:
159
- proc = subprocess.Popen(cmd, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
201
+ proc = subprocess.Popen(
202
+ cmd, cwd=cwd, env=get_subprocess_env(env), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
203
+ )
160
204
  out, _ = proc.communicate()
161
205
  return int(proc.returncode or 0), out or ""
162
206
  except Exception as e:
@@ -173,7 +217,7 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
173
217
  proc = subprocess.Popen(
174
218
  cmd,
175
219
  cwd=cwd,
176
- env=env,
220
+ env=get_subprocess_env(env),
177
221
  stdout=subprocess.PIPE,
178
222
  stderr=subprocess.STDOUT,
179
223
  text=True,
@@ -186,7 +230,8 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
186
230
  def _pump(stdout) -> None:
187
231
  try:
188
232
  for line in stdout:
189
- print(line.rstrip())
233
+ if not should_filter_log_line(line):
234
+ print(line.rstrip())
190
235
  except Exception:
191
236
  pass
192
237
 
@@ -200,7 +245,9 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
200
245
  return int(proc.returncode or 0)
201
246
 
202
247
 
203
- def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | None = None) -> tuple[int, str]:
248
+ def _popen_stream_capture(
249
+ cmd: list[str], cwd: str | None = None, env: dict | None = None
250
+ ) -> tuple[int, str]:
204
251
  """Stream subprocess output to stdout and also capture it into a buffer."""
205
252
  import subprocess
206
253
  import threading
@@ -210,7 +257,7 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
210
257
  proc = subprocess.Popen(
211
258
  cmd,
212
259
  cwd=cwd,
213
- env=env,
260
+ env=get_subprocess_env(env),
214
261
  stdout=subprocess.PIPE,
215
262
  stderr=subprocess.STDOUT,
216
263
  text=True,
@@ -224,8 +271,9 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
224
271
  try:
225
272
  for line in stdout:
226
273
  line = line.rstrip()
227
- print(line)
228
- buf_lines.append(line)
274
+ if not should_filter_log_line(line):
275
+ print(line)
276
+ buf_lines.append(line)
229
277
  except Exception:
230
278
  pass
231
279
 
@@ -251,7 +299,19 @@ def _find_asgi_apps(root: Path) -> list[Path]:
251
299
  - "@modal.asgi_app()"
252
300
  """
253
301
  results: list[Path] = []
254
- skip_dirs = {".git", ".hg", ".svn", "node_modules", "dist", "build", "__pycache__", ".ruff_cache", ".mypy_cache", "venv", ".venv"}
302
+ skip_dirs = {
303
+ ".git",
304
+ ".hg",
305
+ ".svn",
306
+ "node_modules",
307
+ "dist",
308
+ "build",
309
+ "__pycache__",
310
+ ".ruff_cache",
311
+ ".mypy_cache",
312
+ "venv",
313
+ ".venv",
314
+ }
255
315
  for dirpath, dirnames, filenames in os.walk(root):
256
316
  dirnames[:] = [d for d in dirnames if d not in skip_dirs]
257
317
  for name in filenames:
@@ -265,16 +325,20 @@ def _find_asgi_apps(root: Path) -> list[Path]:
265
325
  results.append(path)
266
326
  except Exception:
267
327
  continue
328
+
268
329
  # Stable order: prioritize files under synth_demo/ first, then alphabetical
269
330
  def _priority(p: Path) -> tuple[int, str]:
270
331
  rel = str(p.resolve())
271
332
  in_demo = "/synth_demo/" in rel or rel.endswith("/synth_demo/task_app.py")
272
333
  return (0 if in_demo else 1, rel)
334
+
273
335
  results.sort(key=_priority)
274
336
  return results
275
337
 
276
338
 
277
- def _prompt_value(label: str, default: str | int | float, cast: Callable[[str], Any] | None = None) -> Any:
339
+ def _prompt_value(
340
+ label: str, default: str | int | float, cast: Callable[[str], Any] | None = None
341
+ ) -> Any:
278
342
  prompt = f"{label} [{default}]: "
279
343
  try:
280
344
  raw = input(prompt).strip()
@@ -293,7 +357,19 @@ def _prompt_value(label: str, default: str | int | float, cast: Callable[[str],
293
357
 
294
358
  def _find_vllm_tomls(root: Path) -> list[Path]:
295
359
  results: list[Path] = []
296
- skip_dirs = {".git", ".hg", ".svn", "node_modules", "dist", "build", "__pycache__", ".ruff_cache", ".mypy_cache", "venv", ".venv"}
360
+ skip_dirs = {
361
+ ".git",
362
+ ".hg",
363
+ ".svn",
364
+ "node_modules",
365
+ "dist",
366
+ "build",
367
+ "__pycache__",
368
+ ".ruff_cache",
369
+ ".mypy_cache",
370
+ "venv",
371
+ ".venv",
372
+ }
297
373
  for dirpath, dirnames, filenames in os.walk(root):
298
374
  dirnames[:] = [d for d in dirnames if d not in skip_dirs]
299
375
  for name in filenames:
@@ -313,7 +389,9 @@ def _create_new_config(env: DemoEnv) -> str:
313
389
  default_path = os.path.join(os.getcwd(), "demo_config.toml")
314
390
  while True:
315
391
  try:
316
- destination = input(f"Path to save new config [{default_path}]: ").strip() or default_path
392
+ destination = (
393
+ input(f"Path to save new config [{default_path}]: ").strip() or default_path
394
+ )
317
395
  except Exception:
318
396
  destination = default_path
319
397
  destination = os.path.abspath(destination)
@@ -322,7 +400,9 @@ def _create_new_config(env: DemoEnv) -> str:
322
400
  continue
323
401
  if os.path.exists(destination):
324
402
  try:
325
- overwrite = input(f"{destination} exists. Overwrite? [y/N]: ").strip().lower() or "n"
403
+ overwrite = (
404
+ input(f"{destination} exists. Overwrite? [y/N]: ").strip().lower() or "n"
405
+ )
326
406
  except Exception:
327
407
  overwrite = "n"
328
408
  if not overwrite.startswith("y"):
@@ -334,7 +414,9 @@ def _create_new_config(env: DemoEnv) -> str:
334
414
  model_name = _prompt_value("Model name", "Qwen/Qwen3-0.6B")
335
415
  compute_gpu_type = _prompt_value("Compute GPU type", "H100")
336
416
  compute_gpu_count = _prompt_value("Compute GPU count", 4, int)
337
- topology_gpu_type = _prompt_value("Topology GPU type", f"{compute_gpu_type}:{compute_gpu_count}")
417
+ topology_gpu_type = _prompt_value(
418
+ "Topology GPU type", f"{compute_gpu_type}:{compute_gpu_count}"
419
+ )
338
420
  gpus_for_vllm = _prompt_value("Topology gpus_for_vllm", 2, int)
339
421
  gpus_for_training = _prompt_value("Topology gpus_for_training", 1, int)
340
422
  tensor_parallel = _prompt_value("Topology tensor_parallel", 2, int)
@@ -352,8 +434,9 @@ def _create_new_config(env: DemoEnv) -> str:
352
434
  task_url_default = env.task_app_base_url or ""
353
435
  services_task_url = _prompt_value("services.task_url", task_url_default)
354
436
 
355
- template = textwrap.dedent(
356
- f"""\
437
+ template = (
438
+ textwrap.dedent(
439
+ f"""\
357
440
  # Crafter online RL training configuration (research local copy)
358
441
 
359
442
  [model]
@@ -495,7 +578,9 @@ def _create_new_config(env: DemoEnv) -> str:
495
578
  [services]
496
579
  task_url = \"{services_task_url}\"
497
580
  """
498
- ).strip() + "\n"
581
+ ).strip()
582
+ + "\n"
583
+ )
499
584
 
500
585
  with open(destination, "w", encoding="utf-8") as fh:
501
586
  fh.write(template)
@@ -514,7 +599,11 @@ def _select_or_create_config(explicit: str | None, env: DemoEnv) -> str:
514
599
  discovered = _find_vllm_tomls(search_root)
515
600
 
516
601
  extras: list[Path] = []
517
- packaged = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml")))
602
+ packaged = Path(
603
+ os.path.abspath(
604
+ os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml")
605
+ )
606
+ )
518
607
  extras.append(packaged)
519
608
  home_cfg = Path(os.path.expanduser("~/.synth-ai/demo_config.toml"))
520
609
  extras.append(home_cfg)
@@ -560,29 +649,36 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
560
649
 
561
650
  env_key = (env.env_api_key or "").strip()
562
651
  if not env_key:
563
- raise RuntimeError(f"[{label}] ENVIRONMENT_API_KEY missing. Run `uvx synth-ai rl_demo deploy` first.")
652
+ raise RuntimeError(
653
+ f"[{label}] ENVIRONMENT_API_KEY missing. Run `uvx synth-ai rl_demo deploy` first."
654
+ )
564
655
 
565
656
  task_url = env.task_app_base_url
566
657
  if not task_url or not _is_modal_public_url(task_url):
567
658
  resolved = ""
568
659
  if env.task_app_name:
569
660
  try:
570
- choice = input(
571
- f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: "
572
- ).strip().lower() or "y"
661
+ choice = (
662
+ input(f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: ")
663
+ .strip()
664
+ .lower()
665
+ or "y"
666
+ )
573
667
  except Exception:
574
668
  choice = "y"
575
669
  if choice.startswith("y"):
576
- code, out = _popen_capture([
577
- "uv",
578
- "run",
579
- "python",
580
- "-m",
581
- "modal",
582
- "app",
583
- "url",
584
- env.task_app_name,
585
- ])
670
+ code, out = _popen_capture(
671
+ [
672
+ "uv",
673
+ "run",
674
+ "python",
675
+ "-m",
676
+ "modal",
677
+ "app",
678
+ "url",
679
+ env.task_app_name,
680
+ ]
681
+ )
586
682
  if code == 0 and out:
587
683
  for tok in out.split():
588
684
  if _is_modal_public_url(tok):
@@ -591,7 +687,9 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
591
687
  if not resolved:
592
688
  print(f"[{label}] Task app URL not configured or not a valid Modal public URL.")
593
689
  print("Examples: https://<app-name>-fastapi-app.modal.run")
594
- entered = input("Enter Task App base URL (must contain '.modal.run'), or press Enter to abort: ").strip()
690
+ entered = input(
691
+ "Enter Task App base URL (must contain '.modal.run'), or press Enter to abort: "
692
+ ).strip()
595
693
  if not entered or not _is_modal_public_url(entered):
596
694
  raise RuntimeError(f"[{label}] Valid Task App URL is required.")
597
695
  task_url = entered.rstrip("/")
@@ -608,11 +706,13 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
608
706
  demo_core.persist_task_url(task_url, name=app_name)
609
707
 
610
708
  demo_core.persist_task_url(task_url, name=app_name)
611
- demo_core.persist_dotenv_values({
612
- "TASK_APP_BASE_URL": task_url,
613
- "TASK_APP_NAME": app_name,
614
- "TASK_APP_SECRET_NAME": DEFAULT_TASK_APP_SECRET_NAME,
615
- })
709
+ demo_core.persist_dotenv_values(
710
+ {
711
+ "TASK_APP_BASE_URL": task_url,
712
+ "TASK_APP_NAME": app_name,
713
+ "TASK_APP_SECRET_NAME": DEFAULT_TASK_APP_SECRET_NAME,
714
+ }
715
+ )
616
716
 
617
717
  if synth_key:
618
718
  os.environ["SYNTH_API_KEY"] = synth_key
@@ -621,7 +721,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
621
721
  if openai_key:
622
722
  os.environ["OPENAI_API_KEY"] = openai_key
623
723
 
624
- rollout_url = task_url.rstrip("/") + "/health/rollout"
625
724
  print(f"[{label}] Verifying rollout health:")
626
725
  try:
627
726
  ek = (env_key or "").strip()
@@ -636,7 +735,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
636
735
  print(f"[{label}] GET", h)
637
736
  rc, body = _http("GET", h, headers={"X-API-Key": env_key})
638
737
  if rc == 200:
639
- rollout_url = h
640
738
  break
641
739
  print(f"[{label}] status: {rc}")
642
740
  try:
@@ -648,10 +746,8 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
648
746
  print(f"[{label}] body:", preview)
649
747
  if rc != 200:
650
748
  print(f"[{label}] Warning: rollout health check failed ({rc}). Response: {body}")
651
- try:
749
+ with contextlib.suppress(Exception):
652
750
  print(f"[{label}] Sent header X-API-Key → {_key_preview(env_key, 'X-API-Key')}")
653
- except Exception:
654
- pass
655
751
  else:
656
752
  print(f"[{label}] Task app rollout health check OK.")
657
753
 
@@ -666,7 +762,15 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
666
762
  return updated_env
667
763
 
668
764
 
669
- def cmd_deploy(args: argparse.Namespace) -> int:
765
+ def deploy(
766
+ local: bool = False, app: str | None = None, name: str | None = None, script: str | None = None
767
+ ) -> int:
768
+ # Change to demo directory if stored
769
+ demo_dir = demo_core.load_demo_dir()
770
+ if demo_dir and os.path.isdir(demo_dir):
771
+ os.chdir(demo_dir)
772
+ print(f"Using demo directory: {demo_dir}")
773
+
670
774
  env = demo_core.load_env()
671
775
  os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
672
776
  cwd_env_path = os.path.join(os.getcwd(), ".env")
@@ -674,21 +778,32 @@ def cmd_deploy(args: argparse.Namespace) -> int:
674
778
  url = ""
675
779
  app_name = env.task_app_name or ""
676
780
  try:
677
- if args.local:
781
+ if local:
678
782
  print("Starting local Task App…")
679
783
  import subprocess
680
- subprocess.Popen([sys.executable, "-c", "from synth_ai.demos.demo_task_apps.math.app import run; run()"],
681
- stdout=sys.stdout, stderr=sys.stderr)
784
+
785
+ subprocess.Popen(
786
+ [
787
+ sys.executable,
788
+ "-c",
789
+ "from synth_ai.demos.demo_task_apps.math.app import run; run()",
790
+ ],
791
+ env=get_subprocess_env(),
792
+ stdout=sys.stdout,
793
+ stderr=sys.stderr,
794
+ )
682
795
  target = "http://127.0.0.1:8080"
683
796
  app_name = ""
684
797
  for _ in range(30):
685
- if demo_core.assert_http_ok(target + "/health", method="GET") or demo_core.assert_http_ok(target, method="GET"):
798
+ if demo_core.assert_http_ok(
799
+ target + "/health", method="GET"
800
+ ) or demo_core.assert_http_ok(target, method="GET"):
686
801
  url = target
687
802
  break
688
803
  time.sleep(1)
689
804
  else:
690
805
  # Auto-detect app path if not supplied; prompt interactively from discovered ASGI apps
691
- app_path = os.path.abspath(args.app) if args.app else None
806
+ app_path = os.path.abspath(app) if app else None
692
807
  if not app_path or not os.path.isfile(app_path):
693
808
  # First pass: look for known common filenames
694
809
  candidates = [
@@ -707,7 +822,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
707
822
  rel = os.path.relpath(str(pth), os.getcwd())
708
823
  print(f" [{idx}] {rel}")
709
824
  try:
710
- sel = input(f"Enter choice [1-{len(found)}] (default 1): ").strip() or "1"
825
+ sel = (
826
+ input(f"Enter choice [1-{len(found)}] (default 1): ").strip() or "1"
827
+ )
711
828
  except Exception:
712
829
  sel = "1"
713
830
  try:
@@ -716,12 +833,13 @@ def cmd_deploy(args: argparse.Namespace) -> int:
716
833
  choice = 1
717
834
  choice = max(1, min(choice, len(found)))
718
835
  app_path = str(found[choice - 1].resolve())
719
- if not app_path and args.script:
836
+ if not app_path and script:
720
837
  # Legacy script fallback if user supplied --script explicitly
721
838
  from synth_ai.demos.demo_task_apps.math.deploy_modal import deploy as modal_deploy
722
- url = modal_deploy(script_path=args.script, env_api_key=env.env_api_key)
723
- if args.name:
724
- app_name = args.name
839
+
840
+ url = modal_deploy(script_path=script, env_api_key=env.env_api_key)
841
+ if name:
842
+ app_name = name
725
843
  else:
726
844
  if not app_path:
727
845
  entered = input("Path to Modal app.py (e.g., ./task_app.py): ").strip()
@@ -732,7 +850,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
732
850
  raise FileNotFoundError(f"App file not found: {app_path}")
733
851
  # Surface the app path before asking for the name
734
852
  print(f"Using task app: {app_path}")
735
- existing_name = (args.name or env.task_app_name or "").strip()
853
+ existing_name = (name or env.task_app_name or "").strip()
736
854
  if not existing_name:
737
855
  existing_name = f"synth-{os.path.splitext(os.path.basename(app_path))[0]}"
738
856
  suggested_name = existing_name
@@ -750,16 +868,19 @@ def cmd_deploy(args: argparse.Namespace) -> int:
750
868
  env_key: str | None = existing_env_key or None
751
869
  if existing_env_key:
752
870
  try:
753
- reuse_choice = input(
754
- "Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: "
755
- ).strip().lower() or "y"
871
+ reuse_choice = (
872
+ input("Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: ")
873
+ .strip()
874
+ .lower()
875
+ or "y"
876
+ )
756
877
  except Exception:
757
878
  reuse_choice = "y"
758
879
  if not reuse_choice.startswith("y"):
759
880
  env_key = None
760
881
 
761
882
  if env_key is None:
762
- from synth_ai.rl.secrets import mint_environment_api_key
883
+ from synth_ai.learning.rl.secrets import mint_environment_api_key
763
884
 
764
885
  env_key = mint_environment_api_key()
765
886
  demo_core.persist_env_api_key(env_key)
@@ -770,35 +891,50 @@ def cmd_deploy(args: argparse.Namespace) -> int:
770
891
  print("[deploy] Minted new ENVIRONMENT_API_KEY")
771
892
  elif env_key:
772
893
  os.environ["ENVIRONMENT_API_KEY"] = env_key
773
-
894
+
774
895
  # Optionally upload the new key to the backend using sealed box helper
775
896
  backend_base = (env.dev_backend_url or "").rstrip("/")
776
- synth_key = (env.synth_api_key or os.environ.get("SYNTH_API_KEY") or local_env.get("SYNTH_API_KEY") or "").strip()
897
+ synth_key = (
898
+ env.synth_api_key
899
+ or os.environ.get("SYNTH_API_KEY")
900
+ or local_env.get("SYNTH_API_KEY")
901
+ or ""
902
+ ).strip()
777
903
  if backend_base and synth_key:
778
- # Pass a base WITHOUT trailing /api to setup_environment_api_key,
779
- # since it appends /api/v1/... internally.
780
- non_api_base = backend_base[:-4] if backend_base.endswith("/api") else backend_base
904
+ # Pass a base WITHOUT trailing /api to setup_environment_api_key,
905
+ # since it appends /api/v1/... internally.
906
+ non_api_base = (
907
+ backend_base[:-4] if backend_base.endswith("/api") else backend_base
908
+ )
909
+ try:
910
+ choice = (
911
+ input(f"Upload ENVIRONMENT_API_KEY to backend {non_api_base}? [Y/n]: ")
912
+ .strip()
913
+ .lower()
914
+ or "y"
915
+ )
916
+ except Exception:
917
+ choice = "y"
918
+ if choice.startswith("y"):
781
919
  try:
782
- choice = input(
783
- f"Upload ENVIRONMENT_API_KEY to backend {non_api_base}? [Y/n]: "
784
- ).strip().lower() or "y"
785
- except Exception:
786
- choice = "y"
787
- if choice.startswith("y"):
788
- try:
789
- print(f"[deploy] Uploading ENVIRONMENT_API_KEY to {non_api_base} …")
790
- from synth_ai.rl.env_keys import setup_environment_api_key
791
-
792
- setup_environment_api_key(non_api_base, synth_key, token=env_key)
793
- print("[deploy] Backend sealed-box upload complete.")
794
- except Exception as upload_err:
795
- print(f"[deploy] Failed to upload ENVIRONMENT_API_KEY: {upload_err}")
796
- print(
797
- "Hint: run `uvx python -c \"from synth_ai.rl.env_keys import setup_environment_api_key as s;"
798
- " s('<backend>', '<synth_api_key>')\"` once the backend is reachable."
799
- )
800
-
801
- synth_key = (env.synth_api_key or os.environ.get("SYNTH_API_KEY") or local_env.get("SYNTH_API_KEY") or "").strip()
920
+ print(f"[deploy] Uploading ENVIRONMENT_API_KEY to {non_api_base} …")
921
+ from synth_ai.learning.rl.env_keys import setup_environment_api_key
922
+
923
+ setup_environment_api_key(non_api_base, synth_key, token=env_key)
924
+ print("[deploy] Backend sealed-box upload complete.")
925
+ except Exception as upload_err:
926
+ print(f"[deploy] Failed to upload ENVIRONMENT_API_KEY: {upload_err}")
927
+ print(
928
+ 'Hint: run `uvx python -c "from synth_ai.learning.rl.env_keys import setup_environment_api_key as s;'
929
+ " s('<backend>', '<synth_api_key>')\"` once the backend is reachable."
930
+ )
931
+
932
+ synth_key = (
933
+ env.synth_api_key
934
+ or os.environ.get("SYNTH_API_KEY")
935
+ or local_env.get("SYNTH_API_KEY")
936
+ or ""
937
+ ).strip()
802
938
  if not synth_key:
803
939
  synth_key = input("Enter SYNTH_API_KEY for deployment (required): ").strip()
804
940
  if not synth_key:
@@ -809,7 +945,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
809
945
  env.synth_api_key = synth_key
810
946
  os.environ["SYNTH_API_KEY"] = synth_key
811
947
 
812
- openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip()
948
+ openai_key = (
949
+ os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or ""
950
+ ).strip()
813
951
  if not openai_key:
814
952
  openai_key = input(
815
953
  "Enter your OpenAI API key, found at https://platform.openai.com/api-keys\n> "
@@ -821,8 +959,20 @@ def cmd_deploy(args: argparse.Namespace) -> int:
821
959
  local_env["OPENAI_API_KEY"] = openai_key
822
960
  os.environ["OPENAI_API_KEY"] = openai_key
823
961
 
824
- deploy_cmd = ["uv", "run", "python", "-m", "modal", "deploy", "--name", name_in, app_path]
825
- print("\nStreaming Modal build/deploy logs (this can take several minutes on first run)…\n")
962
+ deploy_cmd = [
963
+ "uv",
964
+ "run",
965
+ "python",
966
+ "-m",
967
+ "modal",
968
+ "deploy",
969
+ "--name",
970
+ name_in,
971
+ app_path,
972
+ ]
973
+ print(
974
+ "\nStreaming Modal build/deploy logs (this can take several minutes on first run)…\n"
975
+ )
826
976
  code, deploy_logs = _popen_stream_capture(deploy_cmd)
827
977
  if code != 0:
828
978
  raise RuntimeError(f"modal deploy failed (exit {code})")
@@ -830,6 +980,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
830
980
  if not url:
831
981
  try:
832
982
  import re as _re
983
+
833
984
  m_all = _re.findall(r"https?://[^\s]+\.modal\.run", deploy_logs or "")
834
985
  if m_all:
835
986
  url = m_all[-1].strip().rstrip("/")
@@ -844,7 +995,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
844
995
  break
845
996
  # Fallback: try reading recent Modal logs for the app to find a URL line
846
997
  if not url:
847
- code3, out3 = _popen_capture(["uv", "run", "python", "-m", "modal", "app", "list"])
998
+ code3, out3 = _popen_capture(
999
+ ["uv", "run", "python", "-m", "modal", "app", "list"]
1000
+ )
848
1001
  if code3 == 0 and out3:
849
1002
  for line in out3.splitlines():
850
1003
  if name_in in line:
@@ -857,7 +1010,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
857
1010
  # Prompt user if still no valid URL
858
1011
  if not url:
859
1012
  print("\nCould not auto-detect a public Modal URL for the app.")
860
- entered = input("Enter the Modal public URL (must contain '.modal.run'), or press Enter to abort: ").strip()
1013
+ entered = input(
1014
+ "Enter the Modal public URL (must contain '.modal.run'), or press Enter to abort: "
1015
+ ).strip()
861
1016
  if entered and _is_modal_public_url(entered):
862
1017
  url = entered.rstrip("/")
863
1018
  if not url:
@@ -885,8 +1040,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
885
1040
  print(f"Deploy error: {e}")
886
1041
  return 2
887
1042
 
888
-
889
- print("`rl_demo configure` prepares environment and secrets; `synth-ai run` now handles launches.")
1043
+ print(
1044
+ "`rl_demo configure` prepares environment and secrets; `synth-ai run` now handles launches."
1045
+ )
890
1046
  env = demo_core.load_env()
891
1047
  synth_key = (env.synth_api_key or "").strip()
892
1048
  if not synth_key:
@@ -919,43 +1075,65 @@ def cmd_deploy(args: argparse.Namespace) -> int:
919
1075
 
920
1076
 
921
1077
  def _ensure_modal_installed() -> None:
922
- """Install the modal package if it is not already available."""
1078
+ """Install the modal package if it is not already available and check authentication."""
923
1079
 
1080
+ # Check if modal is installed
1081
+ modal_installed = False
924
1082
  try:
925
1083
  import importlib.util as _iu
926
1084
 
927
1085
  if _iu.find_spec("modal") is not None:
928
- print("modal package found")
929
- return
1086
+ modal_installed = True
930
1087
  except Exception:
931
1088
  pass
932
1089
 
933
- print("modal not found; installing…")
934
- try:
935
- if shutil.which("uv"):
936
- code, out = _popen_capture(["uv", "pip", "install", "modal>=1.1.4"])
937
- else:
938
- code, out = _popen_capture([sys.executable, "-m", "pip", "install", "modal>=1.1.4"])
939
- if code != 0:
940
- print(out)
941
- print("Failed to install modal; continuing may fail.")
942
- else:
943
- print("modal installed successfully.")
944
- except Exception as exc:
945
- print(f"modal install error: {exc}")
1090
+ # Install modal if needed
1091
+ if not modal_installed:
1092
+ print("modal not found; installing…")
1093
+ try:
1094
+ if shutil.which("uv"):
1095
+ code, out = _popen_capture(["uv", "pip", "install", "modal>=1.1.4"])
1096
+ else:
1097
+ code, out = _popen_capture([sys.executable, "-m", "pip", "install", "modal>=1.1.4"])
1098
+ if code != 0:
1099
+ print(out)
1100
+ print("Failed to install modal; continuing may fail.")
1101
+ return
1102
+ else:
1103
+ print("✓ modal installed successfully")
1104
+ modal_installed = True
1105
+ except Exception as exc:
1106
+ print(f"modal install error: {exc}")
1107
+ return
946
1108
 
947
- try:
948
- import importlib.util as _iu
1109
+ # Verify modal is importable
1110
+ if modal_installed:
1111
+ try:
1112
+ import importlib.util as _iu
949
1113
 
950
- if _iu.find_spec("modal") is None:
951
- print("Warning: modal is still not importable after install attempt.")
952
- else:
953
- print("modal package ready")
954
- except Exception:
955
- print("Warning: unable to verify modal installation.")
1114
+ if _iu.find_spec("modal") is None:
1115
+ print("Warning: modal is still not importable after install attempt.")
1116
+ return
1117
+ except Exception:
1118
+ print("Warning: unable to verify modal installation.")
1119
+ return
1120
+
1121
+ # Check modal authentication status
1122
+ auth_ok, auth_msg = demo_core.modal_auth_status()
1123
+ if auth_ok:
1124
+ print(f"✓ Modal authenticated: {auth_msg}")
1125
+ else:
1126
+ print("\n⚠️ Modal authentication required")
1127
+ print(f" Status: {auth_msg}")
1128
+ print("\n To authenticate Modal, run:")
1129
+ print(" modal setup")
1130
+ print("\n Or set environment variables:")
1131
+ print(" export MODAL_TOKEN_ID=your-token-id")
1132
+ print(" export MODAL_TOKEN_SECRET=your-token-secret")
1133
+ print("\n You can deploy later after authenticating.\n")
956
1134
 
957
1135
 
958
- def cmd_init(args: argparse.Namespace) -> int:
1136
+ def init(template: str | None = None, dest: str | None = None, force: bool = False) -> int:
959
1137
  """Materialise a demo task app template into the current directory."""
960
1138
 
961
1139
  templates = list(list_demo_templates())
@@ -964,47 +1142,101 @@ def cmd_init(args: argparse.Namespace) -> int:
964
1142
  return 1
965
1143
 
966
1144
  selected: DemoTemplate | None = None
967
- if args.template:
968
- selected = get_demo_template(args.template)
1145
+ if template:
1146
+ selected = get_demo_template(template)
969
1147
  if selected is None:
970
1148
  available = ", ".join(t.template_id for t in templates)
971
- print(f"Unknown template '{args.template}'. Available: {available}")
1149
+ print(f"Unknown template '{template}'. Available: {available}")
972
1150
  return 1
973
1151
  else:
974
- print("Select a demo template:" + "\n")
975
- for idx, template in enumerate(templates, start=1):
976
- print(f" [{idx}] {template.name} ({template.template_id})")
977
- print(f" {template.description}")
978
- try:
979
- choice_raw = input(f"Enter choice [1-{len(templates)}] (default 1): ").strip() or "1"
980
- except Exception:
981
- choice_raw = "1"
982
- if not choice_raw.isdigit():
983
- print("Selection must be a number.")
984
- return 1
985
- choice_idx = int(choice_raw)
986
- if not 1 <= choice_idx <= len(templates):
987
- print("Selection out of range.")
988
- return 1
989
- selected = templates[choice_idx - 1]
1152
+ if force:
1153
+ selected = templates[0]
1154
+ print(
1155
+ f"Using default template: {selected.name} ({selected.template_id}) "
1156
+ f"(pass --template to choose another)"
1157
+ )
1158
+ else:
1159
+ print("Select a demo template:" + "\n")
1160
+ for idx, tpl in enumerate(templates, start=1):
1161
+ print(f" [{idx}] {tpl.name} ({tpl.template_id})")
1162
+ print(f" {tpl.description}")
1163
+ try:
1164
+ choice_raw = input(f"Enter choice [1-{len(templates)}] (default 1): ").strip() or "1"
1165
+ except Exception:
1166
+ choice_raw = "1"
1167
+ if not choice_raw.isdigit():
1168
+ print("Selection must be a number.")
1169
+ return 1
1170
+ choice_idx = int(choice_raw)
1171
+ if not 1 <= choice_idx <= len(templates):
1172
+ print("Selection out of range.")
1173
+ return 1
1174
+ selected = templates[choice_idx - 1]
990
1175
 
991
1176
  assert selected is not None
992
1177
 
993
1178
  default_subdir = selected.default_subdir or selected.template_id
994
- default_dest = Path(args.dest).expanduser().resolve() if args.dest else (Path.cwd() / default_subdir).resolve()
995
- try:
996
- dest_input = input(f"Destination directory [{default_dest}]: ").strip()
997
- except Exception:
1179
+
1180
+ # Check if default destination is already occupied and switch to local_demos/ if needed
1181
+ if dest:
1182
+ default_dest = Path(dest).expanduser().resolve()
1183
+ else:
1184
+ primary_dest = Path.cwd() / default_subdir
1185
+ if primary_dest.exists() and any(primary_dest.iterdir()):
1186
+ # Switch to local_demos/ automatically if primary location is occupied
1187
+ default_dest = (Path.cwd() / "local_demos" / default_subdir).resolve()
1188
+ else:
1189
+ default_dest = primary_dest.resolve()
1190
+
1191
+ if force:
998
1192
  dest_input = ""
1193
+ else:
1194
+ try:
1195
+ dest_input = input(f"Destination directory [{default_dest}]: ").strip()
1196
+ except Exception:
1197
+ dest_input = ""
999
1198
  destination = Path(dest_input).expanduser().resolve() if dest_input else default_dest
1000
1199
 
1200
+ # Track whether we should skip individual file prompts (if we already cleared the directory)
1201
+ directory_cleared = False
1202
+
1001
1203
  if destination.exists():
1002
1204
  if destination.is_file():
1003
1205
  print(f"Destination {destination} is a file. Provide a directory path.")
1004
1206
  return 1
1005
- if not args.force and any(destination.iterdir()):
1006
- print(f"Destination {destination} is not empty. Use --force or choose another directory.")
1007
- return 1
1207
+ if any(destination.iterdir()):
1208
+ if force:
1209
+ response = "y"
1210
+ else:
1211
+ try:
1212
+ response = (
1213
+ input(f"Destination {destination} is not empty. Overwrite? [y/N]: ")
1214
+ .strip()
1215
+ .lower()
1216
+ )
1217
+ except (EOFError, KeyboardInterrupt):
1218
+ print("\nCancelled.")
1219
+ return 1
1220
+ if response not in ("y", "yes"):
1221
+ print("Cancelled. Choose another directory or delete the existing one.")
1222
+ return 1
1223
+ # User agreed to overwrite - clear the entire directory including hidden files
1224
+ print(f"Clearing {destination}...")
1225
+ try:
1226
+ # Remove all contents including hidden files (.env, .git, etc.)
1227
+ shutil.rmtree(destination)
1228
+ except Exception as e:
1229
+ print(f"Error clearing directory: {e}")
1230
+ print("Please manually remove the directory and try again.")
1231
+ return 1
1232
+ # Recreate empty directory
1233
+ destination.mkdir(parents=True, exist_ok=True)
1234
+ # Verify it's actually empty
1235
+ if any(destination.iterdir()):
1236
+ print(f"Warning: Directory {destination} still contains files after clearing.")
1237
+ print("Some files may not have been removed. Please check manually.")
1238
+ return 1
1239
+ directory_cleared = True
1008
1240
  else:
1009
1241
  destination.mkdir(parents=True, exist_ok=True)
1010
1242
 
@@ -1018,29 +1250,95 @@ def cmd_init(args: argparse.Namespace) -> int:
1018
1250
  print(f"Template source missing: {src_path}")
1019
1251
  return 1
1020
1252
  dest_path = (destination / spec.destination).resolve()
1021
- dest_path.parent.mkdir(parents=True, exist_ok=True)
1022
- if dest_path.exists() and not args.force:
1023
- print(f"Refusing to overwrite existing file: {dest_path} (use --force)")
1024
- return 1
1025
- shutil.copy2(src_path, dest_path)
1026
- if spec.make_executable:
1027
- try:
1028
- st = os.stat(dest_path)
1029
- os.chmod(dest_path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
1030
- except Exception:
1031
- pass
1253
+
1254
+ # Handle directory copying
1255
+ if src_path.is_dir():
1256
+ if dest_path.exists() and not directory_cleared:
1257
+ if force:
1258
+ response = "y"
1259
+ else:
1260
+ try:
1261
+ response = (
1262
+ input(f"Directory {dest_path.name} exists. Overwrite? [y/N]: ")
1263
+ .strip()
1264
+ .lower()
1265
+ )
1266
+ except (EOFError, KeyboardInterrupt):
1267
+ print("\nCancelled.")
1268
+ return 1
1269
+ if response not in ("y", "yes"):
1270
+ print(f"Skipping {dest_path.name}")
1271
+ continue
1272
+ shutil.rmtree(dest_path)
1273
+ elif dest_path.exists() and directory_cleared:
1274
+ shutil.rmtree(dest_path)
1275
+ shutil.copytree(src_path, dest_path)
1276
+ else:
1277
+ # Handle file copying
1278
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
1279
+ if dest_path.exists() and not directory_cleared:
1280
+ if force:
1281
+ response = "y"
1282
+ else:
1283
+ try:
1284
+ response = (
1285
+ input(f"File {dest_path.name} exists. Overwrite? [y/N]: ")
1286
+ .strip()
1287
+ .lower()
1288
+ )
1289
+ except (EOFError, KeyboardInterrupt):
1290
+ print("\nCancelled.")
1291
+ return 1
1292
+ if response not in ("y", "yes"):
1293
+ print(f"Skipping {dest_path.name}")
1294
+ continue
1295
+ shutil.copy2(src_path, dest_path)
1296
+ if spec.make_executable:
1297
+ try:
1298
+ st = os.stat(dest_path)
1299
+ os.chmod(dest_path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
1300
+ except Exception:
1301
+ pass
1032
1302
 
1033
1303
  if selected.env_lines:
1034
1304
  env_path = destination / ".env"
1035
- if not env_path.exists() or args.force:
1305
+ should_write = True
1306
+ if env_path.exists() and not directory_cleared:
1307
+ if force:
1308
+ response = "y"
1309
+ else:
1310
+ try:
1311
+ response = input("File .env exists. Overwrite? [y/N]: ").strip().lower()
1312
+ except (EOFError, KeyboardInterrupt):
1313
+ print("\nCancelled.")
1314
+ return 1
1315
+ should_write = response in ("y", "yes")
1316
+ if should_write:
1036
1317
  _write_text(env_path, "\n".join(selected.env_lines) + "\n")
1318
+ elif not directory_cleared:
1319
+ print("Skipping .env")
1037
1320
 
1038
1321
  config_src = selected.config_source_path()
1039
1322
  if config_src and config_src.exists():
1040
1323
  cfg_dst = (destination / selected.config_destination).resolve()
1041
- if not cfg_dst.exists() or args.force:
1324
+ should_copy = True
1325
+ if cfg_dst.exists() and not directory_cleared:
1326
+ if force:
1327
+ response = "y"
1328
+ else:
1329
+ try:
1330
+ response = (
1331
+ input(f"File {cfg_dst.name} exists. Overwrite? [y/N]: ").strip().lower()
1332
+ )
1333
+ except (EOFError, KeyboardInterrupt):
1334
+ print("\nCancelled.")
1335
+ return 1
1336
+ should_copy = response in ("y", "yes")
1337
+ if should_copy:
1042
1338
  cfg_dst.parent.mkdir(parents=True, exist_ok=True)
1043
1339
  shutil.copy2(config_src, cfg_dst)
1340
+ elif not directory_cleared:
1341
+ print(f"Skipping {cfg_dst.name}")
1044
1342
 
1045
1343
  if selected.post_copy is not None:
1046
1344
  try:
@@ -1049,6 +1347,14 @@ def cmd_init(args: argparse.Namespace) -> int:
1049
1347
  print(f"Post-processing failed: {post_exc}")
1050
1348
  return 1
1051
1349
 
1350
+ # Store demo directory for subsequent commands
1351
+ demo_core.persist_demo_dir(str(destination))
1352
+
1353
+ # Store .env path if it was created
1354
+ env_file = destination / ".env"
1355
+ if env_file.exists():
1356
+ demo_core.persist_env_file_path(str(env_file))
1357
+
1052
1358
  print(f"Demo template '{selected.name}' materialised at {destination}.")
1053
1359
  print("Files created:")
1054
1360
  for spec in selected.iter_copy_specs():
@@ -1057,6 +1363,7 @@ def cmd_init(args: argparse.Namespace) -> int:
1057
1363
  print(" - .env")
1058
1364
  if selected.config_source_path():
1059
1365
  print(f" - {selected.config_destination}")
1366
+ print("\nDemo directory stored. Subsequent commands will use this directory automatically.")
1060
1367
  print("Review the files, edit .env, and run any provided deploy scripts when ready.")
1061
1368
  return 0
1062
1369
  except KeyboardInterrupt:
@@ -1067,8 +1374,14 @@ def cmd_init(args: argparse.Namespace) -> int:
1067
1374
  return 1
1068
1375
 
1069
1376
 
1070
- def _http(method: str, url: str, headers: Dict[str, str] | None = None, body: Dict[str, Any] | None = None) -> tuple[int, Dict[str, Any] | str]:
1071
- import urllib.request, urllib.error, json as _json, ssl
1377
+ def _http(
1378
+ method: str, url: str, headers: dict[str, str] | None = None, body: dict[str, Any] | None = None
1379
+ ) -> tuple[int, dict[str, Any] | str]:
1380
+ import json as _json
1381
+ import ssl
1382
+ import urllib.error
1383
+ import urllib.request
1384
+
1072
1385
  data = None
1073
1386
  if body is not None:
1074
1387
  data = _json.dumps(body).encode("utf-8")
@@ -1105,10 +1418,23 @@ def _write_text(path: str, content: str) -> None:
1105
1418
  # Note: `prepare` command has been removed; configuration now prepares TOML
1106
1419
 
1107
1420
 
1108
- def cmd_run(args: argparse.Namespace) -> int:
1421
+ def run(
1422
+ config: str | None = None,
1423
+ batch_size: int | None = None,
1424
+ group_size: int | None = None,
1425
+ model: str | None = None,
1426
+ timeout: int = 600,
1427
+ dry_run: bool = False,
1428
+ ) -> int:
1429
+ # Change to demo directory if stored
1430
+ demo_dir = demo_core.load_demo_dir()
1431
+ if demo_dir and os.path.isdir(demo_dir):
1432
+ os.chdir(demo_dir)
1433
+ print(f"Using demo directory: {demo_dir}")
1434
+
1109
1435
  env = demo_core.load_env()
1110
1436
  cwd_env_path = os.path.join(os.getcwd(), ".env")
1111
- local_env = demo_core.load_dotenv_file(cwd_env_path)
1437
+ demo_core.load_dotenv_file(cwd_env_path)
1112
1438
 
1113
1439
  synth_key = (env.synth_api_key or "").strip()
1114
1440
  if not synth_key:
@@ -1140,7 +1466,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1140
1466
  import tomllib
1141
1467
 
1142
1468
  try:
1143
- cfg_path = _select_or_create_config(getattr(args, "config", None), env)
1469
+ cfg_path = _select_or_create_config(config, env)
1144
1470
  except FileNotFoundError as exc:
1145
1471
  print(exc)
1146
1472
  return 1
@@ -1148,7 +1474,11 @@ def cmd_run(args: argparse.Namespace) -> int:
1148
1474
  # Detect monorepo launcher and delegate if available (aligns with run_clustered.sh which works)
1149
1475
  launcher = "/Users/joshpurtell/Documents/GitHub/monorepo/tests/applications/math/rl/start_math_clustered.py"
1150
1476
  if os.path.isfile(launcher):
1151
- backend_base = env.dev_backend_url[:-4] if env.dev_backend_url.endswith("/api") else env.dev_backend_url
1477
+ backend_base = (
1478
+ env.dev_backend_url[:-4]
1479
+ if env.dev_backend_url.endswith("/api")
1480
+ else env.dev_backend_url
1481
+ )
1152
1482
  run_env = os.environ.copy()
1153
1483
  run_env["BACKEND_URL"] = backend_base
1154
1484
  run_env["SYNTH_API_KEY"] = env.synth_api_key
@@ -1158,12 +1488,12 @@ def cmd_run(args: argparse.Namespace) -> int:
1158
1488
  # Optional: TRAINER_START_URL passthrough if already set in environment
1159
1489
  run_env["TRAINER_START_URL"] = run_env.get("TRAINER_START_URL", "")
1160
1490
  # Forward convenience knobs
1161
- if args.batch_size is not None:
1162
- run_env["RL_BATCH_SIZE"] = str(int(args.batch_size))
1163
- if args.group_size is not None:
1164
- run_env["RL_GROUP_SIZE"] = str(int(args.group_size))
1165
- if args.model:
1166
- run_env["RL_MODEL"] = args.model
1491
+ if batch_size is not None:
1492
+ run_env["RL_BATCH_SIZE"] = str(int(batch_size))
1493
+ if group_size is not None:
1494
+ run_env["RL_GROUP_SIZE"] = str(int(group_size))
1495
+ if model:
1496
+ run_env["RL_MODEL"] = model
1167
1497
  cmd = ["uv", "run", "python", launcher]
1168
1498
  print(f"Launching monorepo clustered runner: {' '.join(cmd)}")
1169
1499
  code = _popen_stream(cmd, env=run_env)
@@ -1181,30 +1511,30 @@ def cmd_run(args: argparse.Namespace) -> int:
1181
1511
  print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
1182
1512
  if ek:
1183
1513
  print(f" {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
1184
- print("Ensure the ENVIRONMENT_API_KEY you deployed with matches the task app and remains exported.")
1514
+ print(
1515
+ "Ensure the ENVIRONMENT_API_KEY you deployed with matches the task app and remains exported."
1516
+ )
1185
1517
  return code
1186
1518
 
1187
1519
  # Fallback: legacy jobs API flow
1188
1520
  with open(cfg_path, "rb") as fh:
1189
1521
  inline_cfg = tomllib.load(fh)
1190
- with open(cfg_path, "r") as fh2:
1522
+ with open(cfg_path) as fh2:
1191
1523
  toml_text = fh2.read()
1192
- if args.batch_size is not None:
1193
- inline_cfg.setdefault("training", {})["batch_size"] = int(args.batch_size)
1194
- if args.group_size is not None:
1195
- inline_cfg.setdefault("training", {})["group_size"] = int(args.group_size)
1196
- model_name = args.model or (inline_cfg.get("model", {}) or {}).get("name", "Qwen/Qwen3-0.6B")
1524
+ if batch_size is not None:
1525
+ inline_cfg.setdefault("training", {})["batch_size"] = int(batch_size)
1526
+ if group_size is not None:
1527
+ inline_cfg.setdefault("training", {})["group_size"] = int(group_size)
1528
+ model_name = model or (inline_cfg.get("model", {}) or {}).get("name", "Qwen/Qwen3-0.6B")
1197
1529
  api = env.dev_backend_url.rstrip("/") + ("" if env.dev_backend_url.endswith("/api") else "/api")
1198
1530
  # Print backend and key preview before request for clearer diagnostics
1199
1531
  try:
1200
1532
  sk = (env.synth_api_key or "").strip()
1201
- sk_len = len(sk)
1202
- sk_tail = sk[-5:] if sk_len >= 5 else sk
1203
1533
  print(f"[run] Backend API: {api}")
1204
1534
  print(f"[run] {_key_preview(sk, 'SYNTH_API_KEY')}")
1205
1535
  except Exception:
1206
1536
  pass
1207
- data_fragment: Dict[str, Any] = {
1537
+ data_fragment: dict[str, Any] = {
1208
1538
  "model": model_name,
1209
1539
  "endpoint_base_url": env.task_app_base_url,
1210
1540
  "config": inline_cfg,
@@ -1222,23 +1552,28 @@ def cmd_run(args: argparse.Namespace) -> int:
1222
1552
  if inline_cfg["compute"].get("gpu_type"):
1223
1553
  compute["gpu_type"] = str(inline_cfg["compute"]["gpu_type"]).upper()
1224
1554
  if inline_cfg["compute"].get("gpu_count"):
1225
- compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
1555
+ compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
1226
1556
  if not compute:
1227
1557
  topo = inline_cfg.get("topology") or {}
1228
1558
  gshape = str(topo.get("gpu_type") or "")
1229
1559
  if ":" in gshape:
1230
1560
  t, c = gshape.split(":", 1)
1231
1561
  compute = {"gpu_type": t.upper(), "gpu_count": int(c)}
1232
- body: Dict[str, Any] = {
1562
+ body: dict[str, Any] = {
1233
1563
  "job_type": "rl",
1234
1564
  "data": data_fragment,
1235
1565
  }
1236
1566
  if compute:
1237
1567
  body["compute"] = compute
1238
- code, js = _http("POST", api + "/rl/jobs", headers={
1239
- "Content-Type": "application/json",
1240
- "Authorization": f"Bearer {env.synth_api_key}",
1241
- }, body=body)
1568
+ code, js = _http(
1569
+ "POST",
1570
+ api + "/rl/jobs",
1571
+ headers={
1572
+ "Content-Type": "application/json",
1573
+ "Authorization": f"Bearer {env.synth_api_key}",
1574
+ },
1575
+ body=body,
1576
+ )
1242
1577
  if code not in (200, 201) or not isinstance(js, dict):
1243
1578
  print("Job create failed:", code)
1244
1579
  print(f"Backend: {api}")
@@ -1276,12 +1611,14 @@ def cmd_run(args: argparse.Namespace) -> int:
1276
1611
  try:
1277
1612
  sent_key = detail.get("sent_key")
1278
1613
  if isinstance(sent_key, str):
1279
- print(f"[run] Backend detail.sent_key {_key_preview(sent_key, 'detail.sent_key')}")
1614
+ print(
1615
+ f"[run] Backend detail.sent_key {_key_preview(sent_key, 'detail.sent_key')}"
1616
+ )
1280
1617
  except Exception:
1281
1618
  pass
1282
1619
  try:
1283
1620
  sent_keys = detail.get("sent_keys")
1284
- if isinstance(sent_keys, (list, tuple)):
1621
+ if isinstance(sent_keys, list | tuple):
1285
1622
  previews = []
1286
1623
  for idx, val in enumerate(sent_keys):
1287
1624
  if isinstance(val, str):
@@ -1306,12 +1643,19 @@ def cmd_run(args: argparse.Namespace) -> int:
1306
1643
  # Extra hints for auth failures
1307
1644
  try:
1308
1645
  sk = (env.synth_api_key or "").strip()
1309
- if int(code) == 401 or (isinstance(js, dict) and any(isinstance(v, str) and "Invalid API key" in v for v in js.values())):
1646
+ if int(code) == 401 or (
1647
+ isinstance(js, dict)
1648
+ and any(isinstance(v, str) and "Invalid API key" in v for v in js.values())
1649
+ ):
1310
1650
  base_url = env.dev_backend_url
1311
- print("Hint: HTTP 401 Unauthorized from backend. Verify SYNTH_API_KEY for:", base_url)
1651
+ print(
1652
+ "Hint: HTTP 401 Unauthorized from backend. Verify SYNTH_API_KEY for:", base_url
1653
+ )
1312
1654
  if sk:
1313
1655
  print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
1314
- print("Ensure the ENVIRONMENT_API_KEY and OPENAI_API_KEY used for deployment remain valid.")
1656
+ print(
1657
+ "Ensure the ENVIRONMENT_API_KEY and OPENAI_API_KEY used for deployment remain valid."
1658
+ )
1315
1659
  except Exception:
1316
1660
  pass
1317
1661
  return 2
@@ -1363,9 +1707,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1363
1707
  "rl.performance.metrics",
1364
1708
  ):
1365
1709
  print(f"[{seq}] {typ}: {msg}")
1366
- mc, mj = _http(
1367
- "GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50"
1368
- )
1710
+ mc, mj = _http("GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50")
1369
1711
  if mc == 200 and isinstance(mj, dict):
1370
1712
  pts = mj.get("points") or []
1371
1713
  for p in pts:
@@ -1373,62 +1715,8 @@ def cmd_run(args: argparse.Namespace) -> int:
1373
1715
  if name == "eval.reward_mean":
1374
1716
  print(f"metric eval.reward_mean step={p.get('step')} value={p.get('value')}")
1375
1717
  break
1376
- if time.time() - start_t > (args.timeout or 600):
1718
+ if time.time() - start_t > (timeout or 600):
1377
1719
  print("Timeout waiting for terminal state.")
1378
1720
  break
1379
1721
  time.sleep(2)
1380
1722
  return 0
1381
-
1382
-
1383
- def main(argv: list[str] | None = None) -> int:
1384
- p = argparse.ArgumentParser(prog="synth-ai")
1385
- sub = p.add_subparsers(dest="cmd")
1386
-
1387
- def _add_parser(names: list[str], *, configure: Callable[[argparse.ArgumentParser], None]) -> None:
1388
- for name in names:
1389
- parser = sub.add_parser(name)
1390
- configure(parser)
1391
-
1392
- _add_parser(["rl_demo.setup", "demo.setup"], configure=lambda parser: parser.set_defaults(func=cmd_setup))
1393
-
1394
- def _init_opts(parser):
1395
- parser.add_argument("--template", type=str, default=None, help="Template id to instantiate")
1396
- parser.add_argument("--dest", type=str, default=None, help="Destination directory for files")
1397
- parser.add_argument("--force", action="store_true", help="Overwrite existing files in destination")
1398
- parser.set_defaults(func=cmd_init)
1399
-
1400
- _add_parser(["rl_demo.init", "demo.init"], configure=_init_opts)
1401
-
1402
- # (prepare command removed)
1403
-
1404
- def _deploy_opts(parser):
1405
- parser.add_argument("--local", action="store_true", help="Run local FastAPI instead of Modal deploy")
1406
- parser.add_argument("--app", type=str, default=None, help="Path to Modal app.py for uv run modal deploy")
1407
- parser.add_argument("--name", type=str, default=None, help="Modal app name")
1408
- parser.add_argument("--script", type=str, default=None, help="Path to deploy_task_app.sh (optional legacy)")
1409
- parser.set_defaults(func=cmd_deploy)
1410
-
1411
- _add_parser(["rl_demo.deploy", "demo.deploy"], configure=_deploy_opts)
1412
-
1413
- _add_parser(["rl_demo.configure", "demo.configure"], configure=lambda parser: parser.set_defaults(func=cmd_run))
1414
-
1415
- def _run_opts(parser):
1416
- parser.add_argument("--config", type=str, default=None, help="Path to TOML config (skip prompt)")
1417
- parser.add_argument("--batch-size", type=int, default=None)
1418
- parser.add_argument("--group-size", type=int, default=None)
1419
- parser.add_argument("--model", type=str, default=None)
1420
- parser.add_argument("--timeout", type=int, default=600)
1421
- parser.add_argument("--dry-run", action="store_true", help="Print request body and exit")
1422
- parser.set_defaults(func=cmd_run)
1423
-
1424
- _add_parser(["run", "rl_demo.run", "demo.run"], configure=_run_opts)
1425
-
1426
- args = p.parse_args(argv)
1427
- if not hasattr(args, "func"):
1428
- p.print_help()
1429
- return 1
1430
- return int(args.func(args) or 0)
1431
-
1432
-
1433
- if __name__ == "__main__":
1434
- sys.exit(main())