synth-ai 0.2.14__py3-none-any.whl → 0.4.1__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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (1091) hide show
  1. synth_ai/__init__.py +19 -40
  2. synth_ai/__main__.py +30 -3
  3. synth_ai/cli/__init__.py +105 -70
  4. synth_ai/cli/__main__.py +42 -0
  5. synth_ai/cli/_internal/__init__.py +5 -0
  6. synth_ai/cli/_internal/modal_wrapper.py +31 -0
  7. synth_ai/cli/_internal/storage.py +20 -0
  8. synth_ai/cli/_internal/typer_patch.py +47 -0
  9. synth_ai/cli/_internal/validate_task_app.py +29 -0
  10. synth_ai/cli/agents/__init__.py +17 -0
  11. synth_ai/cli/agents/claude.py +77 -0
  12. synth_ai/cli/agents/codex.py +265 -0
  13. synth_ai/cli/agents/opencode.py +253 -0
  14. synth_ai/cli/commands/__init__.py +18 -0
  15. synth_ai/cli/commands/artifacts/__init__.py +13 -0
  16. synth_ai/cli/commands/artifacts/client.py +119 -0
  17. synth_ai/cli/commands/artifacts/config.py +57 -0
  18. synth_ai/cli/commands/artifacts/core.py +24 -0
  19. synth_ai/cli/commands/artifacts/download.py +188 -0
  20. synth_ai/cli/commands/artifacts/export.py +186 -0
  21. synth_ai/cli/commands/artifacts/list.py +156 -0
  22. synth_ai/cli/commands/artifacts/parsing.py +250 -0
  23. synth_ai/cli/commands/artifacts/show.py +336 -0
  24. synth_ai/cli/commands/baseline/__init__.py +12 -0
  25. synth_ai/cli/commands/baseline/core.py +636 -0
  26. synth_ai/cli/commands/baseline/list.py +94 -0
  27. synth_ai/cli/commands/demo/__init__.py +3 -0
  28. synth_ai/cli/commands/demo/core.py +153 -0
  29. synth_ai/cli/commands/eval/__init__.py +19 -0
  30. synth_ai/cli/commands/eval/core.py +1113 -0
  31. synth_ai/cli/commands/eval/errors.py +81 -0
  32. synth_ai/cli/commands/eval/validation.py +133 -0
  33. synth_ai/cli/commands/filter/__init__.py +12 -0
  34. synth_ai/cli/commands/filter/core.py +424 -0
  35. synth_ai/cli/commands/filter/errors.py +55 -0
  36. synth_ai/cli/commands/filter/validation.py +77 -0
  37. synth_ai/cli/commands/help/__init__.py +185 -0
  38. synth_ai/cli/commands/help/core.py +72 -0
  39. synth_ai/cli/commands/scan/__init__.py +19 -0
  40. synth_ai/cli/commands/scan/cloudflare_scanner.py +403 -0
  41. synth_ai/cli/commands/scan/core.py +344 -0
  42. synth_ai/cli/commands/scan/health_checker.py +242 -0
  43. synth_ai/cli/commands/scan/local_scanner.py +278 -0
  44. synth_ai/cli/commands/scan/models.py +83 -0
  45. synth_ai/cli/commands/smoke/__init__.py +7 -0
  46. synth_ai/cli/commands/smoke/core.py +1438 -0
  47. synth_ai/cli/commands/status/__init__.py +66 -0
  48. synth_ai/cli/commands/status/client.py +192 -0
  49. synth_ai/cli/commands/status/config.py +92 -0
  50. synth_ai/cli/commands/status/errors.py +20 -0
  51. synth_ai/cli/commands/status/formatters.py +164 -0
  52. synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
  53. synth_ai/cli/commands/status/subcommands/files.py +79 -0
  54. synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
  55. synth_ai/cli/commands/status/subcommands/models.py +79 -0
  56. synth_ai/cli/commands/status/subcommands/pricing.py +23 -0
  57. synth_ai/cli/commands/status/subcommands/runs.py +81 -0
  58. synth_ai/cli/commands/status/subcommands/session.py +182 -0
  59. synth_ai/cli/commands/status/subcommands/summary.py +47 -0
  60. synth_ai/cli/commands/status/subcommands/usage.py +203 -0
  61. synth_ai/cli/commands/status/utils.py +114 -0
  62. synth_ai/cli/commands/train/__init__.py +53 -0
  63. synth_ai/cli/commands/train/core.py +22 -0
  64. synth_ai/cli/commands/train/errors.py +117 -0
  65. synth_ai/cli/commands/train/judge_schemas.py +201 -0
  66. synth_ai/cli/commands/train/judge_validation.py +305 -0
  67. synth_ai/cli/commands/train/prompt_learning_validation.py +633 -0
  68. synth_ai/cli/commands/train/validation.py +392 -0
  69. synth_ai/cli/demo_apps/__init__.py +10 -0
  70. synth_ai/cli/demo_apps/core/__init__.py +28 -0
  71. synth_ai/cli/demo_apps/core/cli.py +1735 -0
  72. synth_ai/cli/demo_apps/crafter/crafter_fft_4b.toml +55 -0
  73. synth_ai/cli/demo_apps/crafter/grpo_crafter_task_app.py +186 -0
  74. synth_ai/cli/demo_apps/crafter/rl_from_base_qwen4b.toml +74 -0
  75. synth_ai/cli/demo_apps/demo_registry.py +176 -0
  76. synth_ai/cli/demo_apps/demo_task_apps/core.py +440 -0
  77. synth_ai/cli/demo_apps/demo_task_apps/crafter/__init__.py +1 -0
  78. synth_ai/cli/demo_apps/demo_task_apps/crafter/grpo_crafter_task_app.py +185 -0
  79. synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +742 -0
  80. synth_ai/cli/demo_apps/demo_task_apps/math/task_app_entry.py +39 -0
  81. synth_ai/cli/demo_apps/math/__init__.py +1 -0
  82. synth_ai/cli/demo_apps/math/_common.py +16 -0
  83. synth_ai/cli/demo_apps/math/app.py +38 -0
  84. synth_ai/cli/demo_apps/math/config.toml +76 -0
  85. synth_ai/cli/demo_apps/math/deploy_modal.py +54 -0
  86. synth_ai/cli/demo_apps/math/modal_task_app.py +702 -0
  87. synth_ai/cli/demo_apps/math/task_app_entry.py +53 -0
  88. synth_ai/cli/demo_apps/mipro/main.py +271 -0
  89. synth_ai/cli/demo_apps/mipro/task_app.py +933 -0
  90. synth_ai/cli/demo_apps/mipro/train_cfg.toml +92 -0
  91. synth_ai/cli/demos/__init__.py +12 -0
  92. synth_ai/cli/demos/demo.py +32 -0
  93. synth_ai/cli/demos/rl_demo.py +254 -0
  94. synth_ai/cli/deploy.py +216 -0
  95. synth_ai/cli/infra/__init__.py +14 -0
  96. synth_ai/cli/infra/balance.py +216 -0
  97. synth_ai/cli/infra/mcp.py +35 -0
  98. synth_ai/cli/infra/modal_app.py +36 -0
  99. synth_ai/cli/infra/setup.py +69 -0
  100. synth_ai/cli/infra/status.py +16 -0
  101. synth_ai/cli/infra/turso.py +77 -0
  102. synth_ai/cli/lib/__init__.py +10 -0
  103. synth_ai/cli/lib/agents.py +76 -0
  104. synth_ai/cli/lib/apps/modal_app.py +101 -0
  105. synth_ai/cli/lib/apps/task_app.py +643 -0
  106. synth_ai/cli/lib/bin.py +39 -0
  107. synth_ai/cli/lib/env.py +375 -0
  108. synth_ai/cli/lib/errors.py +85 -0
  109. synth_ai/cli/lib/modal.py +315 -0
  110. synth_ai/cli/lib/plotting.py +126 -0
  111. synth_ai/cli/lib/prompt_args.py +39 -0
  112. synth_ai/cli/lib/prompts.py +284 -0
  113. synth_ai/cli/lib/sqld.py +122 -0
  114. synth_ai/cli/lib/task_app_discovery.py +884 -0
  115. synth_ai/cli/lib/task_app_env.py +295 -0
  116. synth_ai/cli/lib/train_cfgs.py +300 -0
  117. synth_ai/cli/lib/tunnel_records.py +207 -0
  118. synth_ai/cli/local/__init__.py +14 -0
  119. synth_ai/cli/local/experiment_queue/__init__.py +72 -0
  120. synth_ai/cli/local/experiment_queue/api_schemas.py +221 -0
  121. synth_ai/cli/local/experiment_queue/celery_app.py +208 -0
  122. synth_ai/cli/local/experiment_queue/config.py +128 -0
  123. synth_ai/cli/local/experiment_queue/config_utils.py +272 -0
  124. synth_ai/cli/local/experiment_queue/database.py +175 -0
  125. synth_ai/cli/local/experiment_queue/dispatcher.py +119 -0
  126. synth_ai/cli/local/experiment_queue/models.py +231 -0
  127. synth_ai/cli/local/experiment_queue/progress_info.py +160 -0
  128. synth_ai/cli/local/experiment_queue/results.py +373 -0
  129. synth_ai/cli/local/experiment_queue/schemas.py +131 -0
  130. synth_ai/cli/local/experiment_queue/service.py +344 -0
  131. synth_ai/cli/local/experiment_queue/status.py +372 -0
  132. synth_ai/cli/local/experiment_queue/status_tracker.py +360 -0
  133. synth_ai/cli/local/experiment_queue/tasks.py +1984 -0
  134. synth_ai/cli/local/experiment_queue/trace_storage.py +65 -0
  135. synth_ai/cli/local/experiment_queue/validation.py +157 -0
  136. synth_ai/cli/local/session/__init__.py +92 -0
  137. synth_ai/cli/local/session/client.py +383 -0
  138. synth_ai/cli/local/session/constants.py +63 -0
  139. synth_ai/cli/local/session/exceptions.py +105 -0
  140. synth_ai/cli/local/session/manager.py +139 -0
  141. synth_ai/cli/local/session/models.py +89 -0
  142. synth_ai/cli/local/session/query.py +110 -0
  143. synth_ai/cli/root.py +30 -6
  144. synth_ai/cli/task_apps/__init__.py +26 -0
  145. synth_ai/cli/task_apps/commands.py +3153 -0
  146. synth_ai/cli/task_apps/deploy.py +7 -0
  147. synth_ai/cli/task_apps/list.py +26 -0
  148. synth_ai/cli/task_apps/main.py +36 -0
  149. synth_ai/cli/task_apps/modal_serve.py +11 -0
  150. synth_ai/cli/task_apps/serve.py +11 -0
  151. synth_ai/cli/training/__init__.py +8 -0
  152. synth_ai/cli/training/train.py +5 -0
  153. synth_ai/cli/training/train_cfg.py +34 -0
  154. synth_ai/cli/training/watch.py +506 -0
  155. synth_ai/cli/turso.py +34 -55
  156. synth_ai/cli/usage.py +159 -0
  157. synth_ai/cli/utils/__init__.py +8 -0
  158. synth_ai/cli/utils/experiments.py +235 -0
  159. synth_ai/cli/utils/queue.py +504 -0
  160. synth_ai/cli/utils/recent.py +133 -0
  161. synth_ai/cli/utils/traces.py +164 -0
  162. synth_ai/contracts/__init__.py +67 -0
  163. synth_ai/core/__init__.py +100 -0
  164. synth_ai/core/_utils/__init__.py +54 -0
  165. synth_ai/core/_utils/base_url.py +10 -0
  166. synth_ai/core/_utils/http.py +10 -0
  167. synth_ai/core/_utils/prompts.py +14 -0
  168. synth_ai/core/_utils/task_app_state.py +12 -0
  169. synth_ai/core/_utils/user_config.py +10 -0
  170. synth_ai/core/apps/common.py +116 -0
  171. synth_ai/core/auth.py +95 -0
  172. synth_ai/core/cfgs.py +240 -0
  173. synth_ai/core/config/__init__.py +16 -0
  174. synth_ai/core/config/base.py +168 -0
  175. synth_ai/core/config/resolver.py +89 -0
  176. synth_ai/core/env.py +220 -0
  177. synth_ai/core/errors.py +126 -0
  178. synth_ai/core/http.py +230 -0
  179. synth_ai/core/integrations/__init__.py +11 -0
  180. synth_ai/core/integrations/cloudflare.py +1710 -0
  181. synth_ai/core/integrations/mcp/__init__.py +6 -0
  182. synth_ai/core/integrations/mcp/__main__.py +8 -0
  183. synth_ai/core/integrations/mcp/claude.py +36 -0
  184. synth_ai/core/integrations/mcp/main.py +254 -0
  185. synth_ai/core/integrations/mcp/setup.py +100 -0
  186. synth_ai/core/integrations/modal.py +277 -0
  187. synth_ai/core/json.py +72 -0
  188. synth_ai/core/log_filter.py +99 -0
  189. synth_ai/core/logging.py +82 -0
  190. synth_ai/core/paths.py +107 -0
  191. synth_ai/core/pricing.py +109 -0
  192. synth_ai/core/process.py +233 -0
  193. synth_ai/core/ssl.py +25 -0
  194. synth_ai/core/storage/__init__.py +71 -0
  195. synth_ai/core/task_app_state.py +318 -0
  196. synth_ai/core/telemetry.py +282 -0
  197. synth_ai/core/tracing_v3/__init__.py +99 -0
  198. synth_ai/core/tracing_v3/abstractions.py +302 -0
  199. synth_ai/core/tracing_v3/config.py +229 -0
  200. synth_ai/core/tracing_v3/constants.py +21 -0
  201. synth_ai/core/tracing_v3/db_config.py +182 -0
  202. synth_ai/core/tracing_v3/decorators.py +401 -0
  203. synth_ai/core/tracing_v3/llm_call_record_helpers.py +437 -0
  204. synth_ai/core/tracing_v3/migration_helper.py +119 -0
  205. synth_ai/core/tracing_v3/session_tracer.py +542 -0
  206. synth_ai/core/tracing_v3/storage/base.py +211 -0
  207. synth_ai/core/tracing_v3/storage/config.py +109 -0
  208. synth_ai/core/tracing_v3/storage/factory.py +39 -0
  209. synth_ai/core/tracing_v3/trace_utils.py +326 -0
  210. synth_ai/core/tracing_v3/turso/daemon.py +278 -0
  211. synth_ai/core/tracing_v3/turso/models.py +470 -0
  212. synth_ai/core/tracing_v3/turso/native_manager.py +1385 -0
  213. synth_ai/core/tracing_v3/utils.py +108 -0
  214. synth_ai/core/urls.py +18 -0
  215. synth_ai/core/user_config.py +137 -0
  216. synth_ai/core/uvicorn.py +222 -0
  217. synth_ai/data/__init__.py +110 -0
  218. synth_ai/data/enums.py +141 -0
  219. synth_ai/data/rewards.py +152 -0
  220. synth_ai/data/specs.py +36 -0
  221. synth_ai/data/traces.py +35 -0
  222. synth_ai/products/__init__.py +6 -0
  223. synth_ai/products/graph_evolve/__init__.py +46 -0
  224. synth_ai/products/graph_evolve/client.py +226 -0
  225. synth_ai/products/graph_evolve/config.py +591 -0
  226. synth_ai/products/graph_evolve/converters/__init__.py +42 -0
  227. synth_ai/products/graph_evolve/converters/openai_sft.py +484 -0
  228. synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +109 -0
  229. synth_ai/products/graph_evolve/run.py +222 -0
  230. synth_ai/sdk/__init__.py +119 -0
  231. synth_ai/sdk/api/__init__.py +1 -0
  232. synth_ai/sdk/api/models/supported.py +514 -0
  233. synth_ai/sdk/api/research_agent/__init__.py +86 -0
  234. synth_ai/sdk/api/research_agent/cli.py +428 -0
  235. synth_ai/sdk/api/research_agent/config.py +357 -0
  236. synth_ai/sdk/api/research_agent/job.py +717 -0
  237. synth_ai/sdk/api/train/__init__.py +85 -0
  238. synth_ai/sdk/api/train/builders.py +895 -0
  239. synth_ai/sdk/api/train/cli.py +2188 -0
  240. synth_ai/sdk/api/train/config_finder.py +267 -0
  241. synth_ai/sdk/api/train/configs/__init__.py +65 -0
  242. synth_ai/sdk/api/train/configs/prompt_learning.py +1706 -0
  243. synth_ai/sdk/api/train/configs/rl.py +188 -0
  244. synth_ai/sdk/api/train/configs/sft.py +99 -0
  245. synth_ai/sdk/api/train/configs/shared.py +81 -0
  246. synth_ai/sdk/api/train/context_learning.py +312 -0
  247. synth_ai/sdk/api/train/env_resolver.py +418 -0
  248. synth_ai/sdk/api/train/graph_validators.py +216 -0
  249. synth_ai/sdk/api/train/graphgen.py +984 -0
  250. synth_ai/sdk/api/train/graphgen_models.py +823 -0
  251. synth_ai/sdk/api/train/graphgen_validators.py +109 -0
  252. synth_ai/sdk/api/train/pollers.py +124 -0
  253. synth_ai/sdk/api/train/progress/__init__.py +97 -0
  254. synth_ai/sdk/api/train/progress/dataclasses.py +569 -0
  255. synth_ai/sdk/api/train/progress/events.py +326 -0
  256. synth_ai/sdk/api/train/progress/results.py +428 -0
  257. synth_ai/sdk/api/train/progress/tracker.py +641 -0
  258. synth_ai/sdk/api/train/prompt_learning.py +470 -0
  259. synth_ai/sdk/api/train/rl.py +442 -0
  260. synth_ai/sdk/api/train/sft.py +396 -0
  261. synth_ai/sdk/api/train/summary.py +522 -0
  262. synth_ai/sdk/api/train/supported_algos.py +147 -0
  263. synth_ai/sdk/api/train/task_app.py +331 -0
  264. synth_ai/sdk/api/train/utils.py +279 -0
  265. synth_ai/sdk/api/train/validators.py +2424 -0
  266. synth_ai/sdk/baseline/__init__.py +25 -0
  267. synth_ai/sdk/baseline/config.py +209 -0
  268. synth_ai/sdk/baseline/discovery.py +216 -0
  269. synth_ai/sdk/baseline/execution.py +154 -0
  270. synth_ai/sdk/graphs/__init__.py +15 -0
  271. synth_ai/sdk/graphs/completions.py +570 -0
  272. synth_ai/sdk/inference/__init__.py +6 -0
  273. synth_ai/sdk/inference/client.py +128 -0
  274. synth_ai/sdk/jobs/__init__.py +16 -0
  275. synth_ai/sdk/jobs/client.py +371 -0
  276. synth_ai/sdk/judging/__init__.py +15 -0
  277. synth_ai/sdk/judging/base.py +24 -0
  278. synth_ai/sdk/judging/client.py +191 -0
  279. synth_ai/sdk/judging/schemas.py +222 -0
  280. synth_ai/sdk/learning/__init__.py +69 -0
  281. synth_ai/sdk/learning/client.py +240 -0
  282. synth_ai/sdk/learning/ft_client.py +7 -0
  283. synth_ai/sdk/learning/health.py +49 -0
  284. synth_ai/sdk/learning/jobs.py +202 -0
  285. synth_ai/sdk/learning/prompt_extraction.py +334 -0
  286. synth_ai/sdk/learning/prompt_learning_client.py +455 -0
  287. synth_ai/sdk/learning/prompt_learning_types.py +185 -0
  288. synth_ai/sdk/learning/rl/client.py +268 -0
  289. synth_ai/sdk/learning/rl/contracts.py +27 -0
  290. synth_ai/sdk/learning/rl/env_keys.py +166 -0
  291. synth_ai/sdk/learning/rl/secrets.py +13 -0
  292. synth_ai/sdk/learning/sft/client.py +95 -0
  293. synth_ai/sdk/learning/sft/config.py +270 -0
  294. synth_ai/sdk/learning/sft/data.py +698 -0
  295. synth_ai/sdk/learning/validators.py +52 -0
  296. synth_ai/sdk/research_agent/__init__.py +34 -0
  297. synth_ai/sdk/research_agent/container_builder.py +328 -0
  298. synth_ai/sdk/research_agent/container_spec.py +198 -0
  299. synth_ai/sdk/research_agent/defaults.py +34 -0
  300. synth_ai/sdk/research_agent/results_collector.py +69 -0
  301. synth_ai/sdk/specs/__init__.py +46 -0
  302. synth_ai/sdk/specs/dataclasses.py +149 -0
  303. synth_ai/sdk/specs/loader.py +144 -0
  304. synth_ai/sdk/specs/serializer.py +199 -0
  305. synth_ai/sdk/specs/validation.py +250 -0
  306. synth_ai/sdk/streaming/__init__.py +35 -0
  307. synth_ai/sdk/streaming/config.py +94 -0
  308. synth_ai/sdk/streaming/handlers.py +1997 -0
  309. synth_ai/sdk/streaming/streamer.py +704 -0
  310. synth_ai/sdk/streaming/types.py +112 -0
  311. synth_ai/sdk/task/__init__.py +151 -0
  312. synth_ai/sdk/task/apps/__init__.py +133 -0
  313. synth_ai/sdk/task/config.py +261 -0
  314. synth_ai/sdk/task/contracts.py +298 -0
  315. synth_ai/sdk/task/datasets.py +108 -0
  316. synth_ai/sdk/task/in_process.py +1190 -0
  317. synth_ai/sdk/task/in_process_runner.py +309 -0
  318. synth_ai/sdk/task/inference_api.py +299 -0
  319. synth_ai/sdk/task/proxy.py +287 -0
  320. synth_ai/sdk/task/rubrics/__init__.py +55 -0
  321. synth_ai/sdk/task/rubrics/loaders.py +156 -0
  322. synth_ai/sdk/task/rubrics.py +219 -0
  323. synth_ai/sdk/task/server.py +580 -0
  324. synth_ai/sdk/task/trace_correlation_helpers.py +506 -0
  325. synth_ai/sdk/task/tracing_utils.py +95 -0
  326. synth_ai/sdk/task/validators.py +456 -0
  327. synth_ai/sdk/tracing/__init__.py +39 -0
  328. synth_ai/sdk/training/__init__.py +102 -0
  329. synth_ai/sdk/usage/__init__.py +37 -0
  330. synth_ai/sdk/usage/client.py +171 -0
  331. synth_ai/sdk/usage/models.py +261 -0
  332. synth_ai/utils/__init__.py +213 -0
  333. synth_ai-0.4.1.dist-info/METADATA +195 -0
  334. synth_ai-0.4.1.dist-info/RECORD +379 -0
  335. synth_ai-0.4.1.dist-info/top_level.txt +1 -0
  336. examples/__init__.py +0 -16
  337. examples/analyze_semantic_words.sh +0 -17
  338. examples/crafter_debug_render.py +0 -186
  339. examples/dev/qwen3_32b_qlora_4xh100.toml +0 -40
  340. examples/multi_step/configs/README_verilog_rl.md +0 -77
  341. examples/multi_step/configs/VERILOG_REWARDS.md +0 -90
  342. examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +0 -183
  343. examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +0 -35
  344. examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +0 -36
  345. examples/multi_step/configs/crafter_rl_outcome.toml +0 -74
  346. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +0 -187
  347. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +0 -83
  348. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +0 -78
  349. examples/multi_step/configs/crafter_synth_backend.md +0 -40
  350. examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +0 -31
  351. examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +0 -33
  352. examples/multi_step/configs/verilog_rl_lora.toml +0 -190
  353. examples/multi_step/crafter_rl_lora.md +0 -70
  354. examples/multi_step/judges/crafter_backend_judge.py +0 -220
  355. examples/multi_step/judges/verilog_backend_judge.py +0 -234
  356. examples/multi_step/readme.md +0 -48
  357. examples/multi_step/sse_metrics_streaming_notes.md +0 -357
  358. examples/multi_step/task_app_config_notes.md +0 -494
  359. examples/multi_step/verilog_rl_lora.md +0 -218
  360. examples/qwen_coder/README.md +0 -102
  361. examples/qwen_coder/_shared.py +0 -113
  362. examples/qwen_coder/configs/coder_lora_30b.toml +0 -61
  363. examples/qwen_coder/configs/coder_lora_4b.toml +0 -57
  364. examples/qwen_coder/configs/coder_lora_small.toml +0 -58
  365. examples/qwen_coder/generate_dataset.py +0 -98
  366. examples/qwen_coder/infer_ft_smoke.py +0 -65
  367. examples/qwen_coder/infer_prod_proxy.py +0 -73
  368. examples/qwen_coder/infer_via_synth.py +0 -87
  369. examples/qwen_coder/scripts/infer_coder.sh +0 -19
  370. examples/qwen_coder/scripts/train_coder_30b.sh +0 -22
  371. examples/qwen_coder/sft_full_17b.py +0 -103
  372. examples/qwen_coder/sft_lora_30b.py +0 -110
  373. examples/qwen_coder/subset_jsonl.py +0 -39
  374. examples/qwen_coder/todos.md +0 -38
  375. examples/qwen_coder/validate_jsonl.py +0 -60
  376. examples/rl/README.md +0 -169
  377. examples/rl/download_dataset.py +0 -80
  378. examples/run_crafter_demo.sh +0 -10
  379. examples/sft/README.md +0 -139
  380. examples/sft/configs/crafter_fft_qwen0p6b.toml +0 -44
  381. examples/sft/configs/crafter_lora_qwen0p6b.toml +0 -45
  382. examples/sft/evaluate.py +0 -119
  383. examples/sft/export_dataset.py +0 -117
  384. examples/sft/generate_traces.py +0 -164
  385. examples/swe/__init__.py +0 -12
  386. examples/swe/task_app/README.md +0 -105
  387. examples/swe/task_app/__init__.py +0 -2
  388. examples/swe/task_app/grpo_swe_mini.py +0 -601
  389. examples/swe/task_app/grpo_swe_mini_task_app.py +0 -136
  390. examples/swe/task_app/hosted/README.md +0 -173
  391. examples/swe/task_app/hosted/__init__.py +0 -5
  392. examples/swe/task_app/hosted/branching.py +0 -143
  393. examples/swe/task_app/hosted/environment_routes.py +0 -1289
  394. examples/swe/task_app/hosted/envs/__init__.py +0 -1
  395. examples/swe/task_app/hosted/envs/crafter/__init__.py +0 -6
  396. examples/swe/task_app/hosted/envs/crafter/app.py +0 -1
  397. examples/swe/task_app/hosted/envs/crafter/environment.py +0 -522
  398. examples/swe/task_app/hosted/envs/crafter/policy.py +0 -478
  399. examples/swe/task_app/hosted/envs/crafter/react_agent.py +0 -108
  400. examples/swe/task_app/hosted/envs/crafter/shared.py +0 -305
  401. examples/swe/task_app/hosted/envs/crafter/tools.py +0 -47
  402. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +0 -8
  403. examples/swe/task_app/hosted/envs/mini_swe/environment.py +0 -1164
  404. examples/swe/task_app/hosted/envs/mini_swe/policy.py +0 -355
  405. examples/swe/task_app/hosted/envs/mini_swe/shared.py +0 -83
  406. examples/swe/task_app/hosted/envs/mini_swe/tools.py +0 -96
  407. examples/swe/task_app/hosted/hosted_app.py +0 -204
  408. examples/swe/task_app/hosted/inference/__init__.py +0 -5
  409. examples/swe/task_app/hosted/inference/openai_client.py +0 -618
  410. examples/swe/task_app/hosted/main.py +0 -100
  411. examples/swe/task_app/hosted/policy_routes.py +0 -1079
  412. examples/swe/task_app/hosted/registry.py +0 -195
  413. examples/swe/task_app/hosted/rollout.py +0 -1911
  414. examples/swe/task_app/hosted/storage/__init__.py +0 -5
  415. examples/swe/task_app/hosted/storage/volume.py +0 -211
  416. examples/swe/task_app/hosted/test_agents.py +0 -161
  417. examples/swe/task_app/hosted/test_service.py +0 -136
  418. examples/swe/task_app/hosted/utils.py +0 -62
  419. examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +0 -258
  420. examples/task_apps/TESTING.md +0 -275
  421. examples/task_apps/crafter/CREATE_SFT_DATASET.md +0 -273
  422. examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +0 -152
  423. examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +0 -174
  424. examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +0 -268
  425. examples/task_apps/crafter/QUERY_EXAMPLES.md +0 -203
  426. examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +0 -316
  427. examples/task_apps/crafter/__init__.py +0 -0
  428. examples/task_apps/crafter/eval_image_only_gpt4o.toml +0 -28
  429. examples/task_apps/crafter/eval_text_only_groq_llama.toml +0 -36
  430. examples/task_apps/crafter/filter_sft_dataset.toml +0 -16
  431. examples/task_apps/crafter/task_app/README.md +0 -42
  432. examples/task_apps/crafter/task_app/__init__.py +0 -5
  433. examples/task_apps/crafter/task_app/grpo_crafter.py +0 -973
  434. examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +0 -146
  435. examples/task_apps/crafter/task_app/synth_envs_hosted/README.md +0 -173
  436. examples/task_apps/crafter/task_app/synth_envs_hosted/__init__.py +0 -5
  437. examples/task_apps/crafter/task_app/synth_envs_hosted/branching.py +0 -143
  438. examples/task_apps/crafter/task_app/synth_envs_hosted/environment_routes.py +0 -1226
  439. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/__init__.py +0 -1
  440. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/__init__.py +0 -6
  441. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/app.py +0 -1
  442. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +0 -532
  443. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +0 -547
  444. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +0 -123
  445. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/shared.py +0 -305
  446. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/tools.py +0 -47
  447. examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +0 -204
  448. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/__init__.py +0 -5
  449. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +0 -704
  450. examples/task_apps/crafter/task_app/synth_envs_hosted/main.py +0 -100
  451. examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +0 -1152
  452. examples/task_apps/crafter/task_app/synth_envs_hosted/registry.py +0 -195
  453. examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +0 -2160
  454. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/__init__.py +0 -5
  455. examples/task_apps/crafter/task_app/synth_envs_hosted/storage/volume.py +0 -211
  456. examples/task_apps/crafter/task_app/synth_envs_hosted/test_agents.py +0 -161
  457. examples/task_apps/crafter/task_app/synth_envs_hosted/test_service.py +0 -136
  458. examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +0 -218
  459. examples/task_apps/dev/pokemon_emerald/__init__.py +0 -2
  460. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +0 -811
  461. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +0 -120
  462. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +0 -160
  463. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +0 -155
  464. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +0 -69
  465. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +0 -96
  466. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +0 -1502
  467. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +0 -4
  468. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +0 -68
  469. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +0 -216
  470. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +0 -35
  471. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +0 -631
  472. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +0 -1544
  473. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +0 -1428
  474. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +0 -4848
  475. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +0 -41
  476. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +0 -298
  477. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +0 -95
  478. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +0 -204
  479. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/__init__.py +0 -0
  480. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +0 -2152
  481. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +0 -429
  482. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +0 -155
  483. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +0 -78
  484. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/__init__.py +0 -0
  485. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +0 -122
  486. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +0 -76
  487. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +0 -413
  488. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +0 -204
  489. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +0 -133
  490. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +0 -229
  491. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +0 -300
  492. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +0 -205
  493. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +0 -200
  494. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +0 -284
  495. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +0 -468
  496. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +0 -575
  497. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +0 -311
  498. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +0 -259
  499. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/__init__.py +0 -0
  500. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +0 -372
  501. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +0 -296
  502. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +0 -275
  503. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +0 -22
  504. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +0 -44
  505. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +0 -514
  506. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +0 -415
  507. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +0 -1763
  508. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +0 -33
  509. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +0 -106
  510. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +0 -334
  511. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +0 -1020
  512. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +0 -188
  513. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +0 -1481
  514. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +0 -862
  515. examples/task_apps/dev/pokemon_emerald/modal_app.py +0 -114
  516. examples/task_apps/dev/pokemon_emerald/task_app/README.md +0 -81
  517. examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +0 -6
  518. examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +0 -685
  519. examples/task_apps/enron/__init__.py +0 -1
  520. examples/task_apps/enron/eval_groq_qwen32.toml +0 -16
  521. examples/task_apps/enron/filter_sft.toml +0 -5
  522. examples/task_apps/enron/task_app/README.md +0 -14
  523. examples/task_apps/enron/task_app/__init__.py +0 -1
  524. examples/task_apps/enron/task_app/grpo_enron.py +0 -906
  525. examples/task_apps/enron/task_app/grpo_enron_task_app.py +0 -146
  526. examples/task_apps/enron/tests/__init__.py +0 -4
  527. examples/task_apps/enron/tests/conftest.py +0 -115
  528. examples/task_apps/enron/tests/integration/__init__.py +0 -4
  529. examples/task_apps/enron/tests/integration/test_enron_eval.py +0 -179
  530. examples/task_apps/enron/tests/integration/test_enron_rollout.py +0 -135
  531. examples/task_apps/enron/tests/unit/__init__.py +0 -4
  532. examples/task_apps/enron/tests/unit/test_enron_environment.py +0 -126
  533. examples/task_apps/math/README.md +0 -22
  534. examples/task_apps/math/__init__.py +0 -0
  535. examples/task_apps/math/math_single_step.py +0 -1000
  536. examples/task_apps/math/math_task_app.py +0 -115
  537. examples/task_apps/pokemon_battle/__init__.py +0 -2
  538. examples/task_apps/pokemon_battle/modal_app.py +0 -104
  539. examples/task_apps/pokemon_battle/task_app/README.md +0 -68
  540. examples/task_apps/pokemon_battle/task_app/__init__.py +0 -6
  541. examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +0 -932
  542. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +0 -283
  543. examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +0 -155
  544. examples/task_apps/pokemon_red/README.md +0 -357
  545. examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +0 -415
  546. examples/task_apps/pokemon_red/__init__.py +0 -3
  547. examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +0 -29
  548. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +0 -225
  549. examples/task_apps/pokemon_red/pallet_town_rl_config.toml +0 -75
  550. examples/task_apps/pokemon_red/task_app.py +0 -799
  551. examples/task_apps/pokemon_red/test_pallet_town_rewards.py +0 -193
  552. examples/task_apps/sokoban/README.md +0 -307
  553. examples/task_apps/sokoban/__init__.py +0 -3
  554. examples/task_apps/sokoban/eval_groq_qwen32.toml +0 -16
  555. examples/task_apps/sokoban/eval_openai_gpt5.toml +0 -16
  556. examples/task_apps/sokoban/filter_sft.toml +0 -5
  557. examples/task_apps/sokoban/task_app.py +0 -1058
  558. examples/task_apps/sokoban/tests/__init__.py +0 -4
  559. examples/task_apps/sokoban/tests/conftest.py +0 -113
  560. examples/task_apps/sokoban/tests/integration/__init__.py +0 -4
  561. examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +0 -57
  562. examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +0 -198
  563. examples/task_apps/sokoban/tests/unit/__init__.py +0 -4
  564. examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +0 -114
  565. examples/task_apps/verilog/__init__.py +0 -1
  566. examples/task_apps/verilog/eval_groq_qwen32b.toml +0 -24
  567. examples/task_apps/verilog/filter_sft.toml +0 -5
  568. examples/task_apps/verilog/task_app/README.md +0 -12
  569. examples/task_apps/verilog/task_app/__init__.py +0 -1
  570. examples/task_apps/verilog/task_app/grpo_verilog.py +0 -1166
  571. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +0 -145
  572. examples/task_apps/verilog/tests/__init__.py +0 -4
  573. examples/task_apps/verilog/tests/conftest.py +0 -115
  574. examples/task_apps/verilog/tests/integration/__init__.py +0 -4
  575. examples/task_apps/verilog/tests/integration/test_verilog_eval.py +0 -181
  576. examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +0 -55
  577. examples/task_apps/verilog/tests/unit/__init__.py +0 -4
  578. examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +0 -118
  579. examples/vlm/PROPOSAL.md +0 -53
  580. examples/vlm/README.md +0 -68
  581. examples/vlm/configs/crafter_vlm_gpt4o.toml +0 -44
  582. examples/vlm/crafter_image_only_agent.py +0 -207
  583. examples/vlm/crafter_openai_vlm_agent.py +0 -277
  584. examples/vlm/filter_image_rows.py +0 -63
  585. examples/vlm/run_crafter_vlm_benchmark.py +0 -316
  586. examples/warming_up_to_rl/analyze_trace_db.py +0 -422
  587. examples/warming_up_to_rl/configs/crafter_fft.toml +0 -48
  588. examples/warming_up_to_rl/configs/crafter_fft_4b.toml +0 -54
  589. examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +0 -20
  590. examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +0 -13
  591. examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +0 -23
  592. examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +0 -35
  593. examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +0 -26
  594. examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +0 -36
  595. examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +0 -32
  596. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +0 -83
  597. examples/warming_up_to_rl/configs/rl_from_ft.toml +0 -56
  598. examples/warming_up_to_rl/export_trace_sft.py +0 -723
  599. examples/warming_up_to_rl/groq_test.py +0 -97
  600. examples/warming_up_to_rl/manage_secrets.py +0 -131
  601. examples/warming_up_to_rl/old/event_rewards.md +0 -234
  602. examples/warming_up_to_rl/old/notes.md +0 -73
  603. examples/warming_up_to_rl/readme.md +0 -179
  604. examples/warming_up_to_rl/run_eval.py +0 -736
  605. examples/warming_up_to_rl/run_fft_and_save.py +0 -380
  606. examples/warming_up_to_rl/run_local_rollout.py +0 -239
  607. examples/warming_up_to_rl/run_local_rollout_modal.py +0 -248
  608. examples/warming_up_to_rl/run_local_rollout_parallel.py +0 -405
  609. examples/warming_up_to_rl/run_local_rollout_traced.py +0 -477
  610. examples/warming_up_to_rl/run_rl_and_save.py +0 -124
  611. examples/warming_up_to_rl/run_rollout_remote.py +0 -156
  612. examples/workflows/__init__.py +0 -0
  613. examples/workflows/math_rl/__init__.py +0 -0
  614. examples/workflows/math_rl/configs/eval_base_qwen.toml +0 -15
  615. examples/workflows/math_rl/configs/eval_rl_qwen.toml +0 -11
  616. examples/workflows/math_rl/configs/rl_from_base_qwen.toml +0 -35
  617. examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +0 -74
  618. examples/workflows/math_rl/configs/rl_from_ft_qwen.toml +0 -35
  619. examples/workflows/math_rl/download_dataset.py +0 -80
  620. examples/workflows/math_rl/run_eval.py +0 -436
  621. examples/workflows/math_rl/run_rl_and_save.py +0 -111
  622. synth_ai/api/models/supported.py +0 -377
  623. synth_ai/api/train/__init__.py +0 -5
  624. synth_ai/api/train/builders.py +0 -351
  625. synth_ai/api/train/cli.py +0 -635
  626. synth_ai/api/train/config_finder.py +0 -228
  627. synth_ai/api/train/configs/__init__.py +0 -44
  628. synth_ai/api/train/configs/rl.py +0 -134
  629. synth_ai/api/train/configs/sft.py +0 -95
  630. synth_ai/api/train/configs/shared.py +0 -24
  631. synth_ai/api/train/env_resolver.py +0 -349
  632. synth_ai/api/train/pollers.py +0 -75
  633. synth_ai/api/train/supported_algos.py +0 -147
  634. synth_ai/api/train/task_app.py +0 -195
  635. synth_ai/api/train/utils.py +0 -225
  636. synth_ai/cli/_modal_wrapper.py +0 -29
  637. synth_ai/cli/_storage.py +0 -20
  638. synth_ai/cli/_typer_patch.py +0 -49
  639. synth_ai/cli/_validate_task_app.py +0 -11
  640. synth_ai/cli/balance.py +0 -216
  641. synth_ai/cli/calc.py +0 -84
  642. synth_ai/cli/demo.py +0 -165
  643. synth_ai/cli/legacy_root_backup.py +0 -468
  644. synth_ai/cli/man.py +0 -106
  645. synth_ai/cli/recent.py +0 -132
  646. synth_ai/cli/rl_demo.py +0 -254
  647. synth_ai/cli/status.py +0 -134
  648. synth_ai/cli/task_apps.py +0 -4523
  649. synth_ai/cli/traces.py +0 -164
  650. synth_ai/cli/tui.py +0 -57
  651. synth_ai/cli/watch.py +0 -506
  652. synth_ai/compound/cais.py +0 -0
  653. synth_ai/config/base_url.py +0 -107
  654. synth_ai/core/experiment.py +0 -13
  655. synth_ai/core/system.py +0 -15
  656. synth_ai/demo_registry.py +0 -295
  657. synth_ai/demos/core/__init__.py +0 -1
  658. synth_ai/demos/core/cli.py +0 -1718
  659. synth_ai/demos/demo_task_apps/core.py +0 -440
  660. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +0 -184
  661. synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
  662. synth_ai/demos/demo_task_apps/math/modal_task_app.py +0 -739
  663. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -37
  664. synth_ai/environments/__init__.py +0 -31
  665. synth_ai/environments/environment/__init__.py +0 -1
  666. synth_ai/environments/environment/artifacts/__init__.py +0 -1
  667. synth_ai/environments/environment/artifacts/base.py +0 -52
  668. synth_ai/environments/environment/core.py +0 -67
  669. synth_ai/environments/environment/db/__init__.py +0 -1
  670. synth_ai/environments/environment/db/sqlite.py +0 -45
  671. synth_ai/environments/environment/registry.py +0 -233
  672. synth_ai/environments/environment/resources/sqlite.py +0 -45
  673. synth_ai/environments/environment/results.py +0 -1
  674. synth_ai/environments/environment/rewards/__init__.py +0 -1
  675. synth_ai/environments/environment/rewards/core.py +0 -29
  676. synth_ai/environments/environment/shared_engine.py +0 -26
  677. synth_ai/environments/environment/tools/__init__.py +0 -200
  678. synth_ai/environments/examples/__init__.py +0 -1
  679. synth_ai/environments/examples/bandit/__init__.py +0 -33
  680. synth_ai/environments/examples/bandit/engine.py +0 -302
  681. synth_ai/environments/examples/bandit/environment.py +0 -194
  682. synth_ai/environments/examples/bandit/taskset.py +0 -200
  683. synth_ai/environments/examples/crafter_classic/__init__.py +0 -8
  684. synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +0 -250
  685. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +0 -59
  686. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +0 -152
  687. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +0 -24
  688. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +0 -1194
  689. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +0 -56
  690. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +0 -32
  691. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  692. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +0 -384
  693. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +0 -53
  694. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +0 -178
  695. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +0 -222
  696. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +0 -183
  697. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +0 -210
  698. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +0 -206
  699. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +0 -49
  700. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +0 -64
  701. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +0 -88
  702. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +0 -77
  703. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +0 -324
  704. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  705. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +0 -362
  706. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +0 -49
  707. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +0 -332
  708. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +0 -97
  709. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +0 -217
  710. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +0 -87
  711. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +0 -88
  712. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +0 -195
  713. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +0 -400
  714. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +0 -195
  715. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +0 -56
  716. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +0 -858
  717. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +0 -52
  718. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +0 -874
  719. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +0 -1412
  720. synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +0 -216
  721. synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +0 -296
  722. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +0 -58
  723. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +0 -464
  724. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +0 -152
  725. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +0 -51
  726. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +0 -1412
  727. synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +0 -112
  728. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +0 -203
  729. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +0 -305
  730. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +0 -126
  731. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +0 -94
  732. synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +0 -142
  733. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +0 -26
  734. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +0 -984
  735. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +0 -724
  736. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +0 -386
  737. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +0 -205
  738. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +0 -150
  739. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +0 -283
  740. synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +0 -280
  741. synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +0 -456
  742. synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +0 -166
  743. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +0 -102
  744. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +0 -128
  745. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +0 -655
  746. synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +0 -202
  747. synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +0 -166
  748. synth_ai/environments/examples/crafter_classic/config_logging.py +0 -111
  749. synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
  750. synth_ai/environments/examples/crafter_classic/engine.py +0 -579
  751. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +0 -64
  752. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +0 -6
  753. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +0 -75
  754. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +0 -267
  755. synth_ai/environments/examples/crafter_classic/environment.py +0 -495
  756. synth_ai/environments/examples/crafter_classic/taskset.py +0 -233
  757. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +0 -228
  758. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +0 -299
  759. synth_ai/environments/examples/crafter_custom/__init__.py +0 -4
  760. synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +0 -1
  761. synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +0 -202
  762. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +0 -7
  763. synth_ai/environments/examples/crafter_custom/crafter/config.py +0 -182
  764. synth_ai/environments/examples/crafter_custom/crafter/constants.py +0 -8
  765. synth_ai/environments/examples/crafter_custom/crafter/engine.py +0 -269
  766. synth_ai/environments/examples/crafter_custom/crafter/env.py +0 -262
  767. synth_ai/environments/examples/crafter_custom/crafter/objects.py +0 -417
  768. synth_ai/environments/examples/crafter_custom/crafter/recorder.py +0 -187
  769. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +0 -118
  770. synth_ai/environments/examples/crafter_custom/dataset_builder.py +0 -373
  771. synth_ai/environments/examples/crafter_custom/environment.py +0 -312
  772. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +0 -159
  773. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +0 -158
  774. synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +0 -71
  775. synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +0 -105
  776. synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +0 -119
  777. synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +0 -52
  778. synth_ai/environments/examples/crafter_custom/run_dataset.py +0 -305
  779. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +0 -156
  780. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +0 -281
  781. synth_ai/environments/examples/enron/art_helpers/types_enron.py +0 -25
  782. synth_ai/environments/examples/enron/engine.py +0 -300
  783. synth_ai/environments/examples/enron/environment.py +0 -234
  784. synth_ai/environments/examples/enron/taskset.py +0 -112
  785. synth_ai/environments/examples/enron/units/keyword_stats.py +0 -112
  786. synth_ai/environments/examples/minigrid/__init__.py +0 -48
  787. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +0 -1188
  788. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +0 -48
  789. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +0 -562
  790. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +0 -221
  791. synth_ai/environments/examples/minigrid/engine.py +0 -589
  792. synth_ai/environments/examples/minigrid/environment.py +0 -274
  793. synth_ai/environments/examples/minigrid/environment_mapping.py +0 -242
  794. synth_ai/environments/examples/minigrid/puzzle_loader.py +0 -417
  795. synth_ai/environments/examples/minigrid/taskset.py +0 -583
  796. synth_ai/environments/examples/nethack/__init__.py +0 -7
  797. synth_ai/environments/examples/nethack/achievements.py +0 -337
  798. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +0 -981
  799. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +0 -74
  800. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +0 -831
  801. synth_ai/environments/examples/nethack/engine.py +0 -739
  802. synth_ai/environments/examples/nethack/environment.py +0 -256
  803. synth_ai/environments/examples/nethack/helpers/__init__.py +0 -41
  804. synth_ai/environments/examples/nethack/helpers/action_mapping.py +0 -301
  805. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +0 -402
  806. synth_ai/environments/examples/nethack/helpers/observation_utils.py +0 -433
  807. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +0 -200
  808. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +0 -269
  809. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +0 -308
  810. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +0 -431
  811. synth_ai/environments/examples/nethack/taskset.py +0 -323
  812. synth_ai/environments/examples/red/__init__.py +0 -7
  813. synth_ai/environments/examples/red/agent_demos/__init__.py +0 -1
  814. synth_ai/environments/examples/red/config_logging.py +0 -110
  815. synth_ai/environments/examples/red/engine.py +0 -721
  816. synth_ai/environments/examples/red/engine_helpers/__init__.py +0 -1
  817. synth_ai/environments/examples/red/engine_helpers/memory_map.py +0 -35
  818. synth_ai/environments/examples/red/engine_helpers/reward_components.py +0 -276
  819. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +0 -142
  820. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +0 -57
  821. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +0 -284
  822. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +0 -150
  823. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +0 -138
  824. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +0 -57
  825. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +0 -331
  826. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +0 -121
  827. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +0 -477
  828. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +0 -559
  829. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +0 -313
  830. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +0 -148
  831. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +0 -247
  832. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +0 -368
  833. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +0 -172
  834. synth_ai/environments/examples/red/environment.py +0 -298
  835. synth_ai/environments/examples/red/taskset.py +0 -79
  836. synth_ai/environments/examples/red/units/__init__.py +0 -1
  837. synth_ai/environments/examples/sokoban/__init__.py +0 -1
  838. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +0 -899
  839. synth_ai/environments/examples/sokoban/engine.py +0 -678
  840. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +0 -1
  841. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +0 -657
  842. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +0 -18
  843. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +0 -3
  844. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +0 -131
  845. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +0 -370
  846. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +0 -332
  847. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +0 -306
  848. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +0 -67
  849. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +0 -115
  850. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +0 -123
  851. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +0 -394
  852. synth_ai/environments/examples/sokoban/environment.py +0 -229
  853. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +0 -440
  854. synth_ai/environments/examples/sokoban/puzzle_loader.py +0 -312
  855. synth_ai/environments/examples/sokoban/taskset.py +0 -544
  856. synth_ai/environments/examples/tictactoe/__init__.py +0 -1
  857. synth_ai/environments/examples/tictactoe/engine.py +0 -368
  858. synth_ai/environments/examples/tictactoe/environment.py +0 -240
  859. synth_ai/environments/examples/tictactoe/taskset.py +0 -215
  860. synth_ai/environments/examples/verilog/__init__.py +0 -10
  861. synth_ai/environments/examples/verilog/engine.py +0 -421
  862. synth_ai/environments/examples/verilog/environment.py +0 -350
  863. synth_ai/environments/examples/verilog/taskset.py +0 -420
  864. synth_ai/environments/examples/wordle/__init__.py +0 -29
  865. synth_ai/environments/examples/wordle/engine.py +0 -398
  866. synth_ai/environments/examples/wordle/environment.py +0 -159
  867. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +0 -75
  868. synth_ai/environments/examples/wordle/taskset.py +0 -230
  869. synth_ai/environments/reproducibility/core.py +0 -42
  870. synth_ai/environments/reproducibility/helpers.py +0 -0
  871. synth_ai/environments/reproducibility/tree.py +0 -363
  872. synth_ai/environments/service/app.py +0 -97
  873. synth_ai/environments/service/core_routes.py +0 -1021
  874. synth_ai/environments/service/external_registry.py +0 -56
  875. synth_ai/environments/service/registry.py +0 -9
  876. synth_ai/environments/stateful/__init__.py +0 -1
  877. synth_ai/environments/stateful/core.py +0 -163
  878. synth_ai/environments/stateful/engine.py +0 -21
  879. synth_ai/environments/stateful/state.py +0 -7
  880. synth_ai/environments/tasks/api.py +0 -19
  881. synth_ai/environments/tasks/core.py +0 -81
  882. synth_ai/environments/tasks/filters.py +0 -40
  883. synth_ai/environments/tasks/utils.py +0 -90
  884. synth_ai/environments/v0_observability/history.py +0 -3
  885. synth_ai/environments/v0_observability/log.py +0 -2
  886. synth_ai/evals/__init__.py +0 -15
  887. synth_ai/evals/base.py +0 -13
  888. synth_ai/evals/client.py +0 -82
  889. synth_ai/handshake.py +0 -109
  890. synth_ai/http.py +0 -26
  891. synth_ai/http_client.py +0 -136
  892. synth_ai/inference/__init__.py +0 -5
  893. synth_ai/inference/client.py +0 -34
  894. synth_ai/jobs/client.py +0 -295
  895. synth_ai/judge_schemas.py +0 -127
  896. synth_ai/learning/__init__.py +0 -59
  897. synth_ai/learning/client.py +0 -241
  898. synth_ai/learning/ft_client.py +0 -7
  899. synth_ai/learning/health.py +0 -49
  900. synth_ai/learning/jobs.py +0 -201
  901. synth_ai/learning/rl/client.py +0 -267
  902. synth_ai/learning/rl/contracts.py +0 -27
  903. synth_ai/learning/rl/env_keys.py +0 -166
  904. synth_ai/learning/rl/secrets.py +0 -13
  905. synth_ai/learning/sft/client.py +0 -68
  906. synth_ai/learning/sft/config.py +0 -270
  907. synth_ai/learning/sft/data.py +0 -295
  908. synth_ai/learning/validators.py +0 -49
  909. synth_ai/lm/__init__.py +0 -25
  910. synth_ai/task/__init__.py +0 -121
  911. synth_ai/task/apps/__init__.py +0 -129
  912. synth_ai/task/config.py +0 -257
  913. synth_ai/task/contracts.py +0 -236
  914. synth_ai/task/datasets.py +0 -108
  915. synth_ai/task/proxy.py +0 -251
  916. synth_ai/task/rubrics/__init__.py +0 -56
  917. synth_ai/task/rubrics/loaders.py +0 -152
  918. synth_ai/task/server.py +0 -432
  919. synth_ai/task/trace_correlation_helpers.py +0 -315
  920. synth_ai/task/tracing_utils.py +0 -84
  921. synth_ai/task/validators.py +0 -418
  922. synth_ai/tracing_v3/__init__.py +0 -97
  923. synth_ai/tracing_v3/abstractions.py +0 -302
  924. synth_ai/tracing_v3/config.py +0 -84
  925. synth_ai/tracing_v3/db_config.py +0 -194
  926. synth_ai/tracing_v3/decorators.py +0 -398
  927. synth_ai/tracing_v3/llm_call_record_helpers.py +0 -391
  928. synth_ai/tracing_v3/migration_helper.py +0 -120
  929. synth_ai/tracing_v3/session_tracer.py +0 -540
  930. synth_ai/tracing_v3/storage/base.py +0 -210
  931. synth_ai/tracing_v3/storage/config.py +0 -75
  932. synth_ai/tracing_v3/storage/factory.py +0 -39
  933. synth_ai/tracing_v3/trace_utils.py +0 -317
  934. synth_ai/tracing_v3/turso/daemon.py +0 -151
  935. synth_ai/tracing_v3/turso/models.py +0 -469
  936. synth_ai/tracing_v3/turso/native_manager.py +0 -1209
  937. synth_ai/tracing_v3/utils.py +0 -108
  938. synth_ai/tui/__init__.py +0 -5
  939. synth_ai/tui/__main__.py +0 -13
  940. synth_ai/tui/cli/__init__.py +0 -1
  941. synth_ai/tui/cli/query_experiments.py +0 -164
  942. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  943. synth_ai/tui/dashboard.py +0 -906
  944. synth_ai/v0/api/__init__.py +0 -8
  945. synth_ai/v0/api/models/__init__.py +0 -8
  946. synth_ai/v0/api/models/supported.py +0 -8
  947. synth_ai/v0/config/__init__.py +0 -15
  948. synth_ai/v0/config/base_url.py +0 -12
  949. synth_ai/v0/lm/__init__.py +0 -51
  950. synth_ai/v0/lm/caching/__init__.py +0 -0
  951. synth_ai/v0/lm/caching/constants.py +0 -6
  952. synth_ai/v0/lm/caching/dbs.py +0 -0
  953. synth_ai/v0/lm/caching/ephemeral.py +0 -100
  954. synth_ai/v0/lm/caching/handler.py +0 -137
  955. synth_ai/v0/lm/caching/initialize.py +0 -11
  956. synth_ai/v0/lm/caching/persistent.py +0 -114
  957. synth_ai/v0/lm/config.py +0 -115
  958. synth_ai/v0/lm/constants.py +0 -32
  959. synth_ai/v0/lm/core/__init__.py +0 -8
  960. synth_ai/v0/lm/core/all.py +0 -73
  961. synth_ai/v0/lm/core/exceptions.py +0 -5
  962. synth_ai/v0/lm/core/main.py +0 -331
  963. synth_ai/v0/lm/core/main_v3.py +0 -594
  964. synth_ai/v0/lm/core/synth_models.py +0 -35
  965. synth_ai/v0/lm/core/vendor_clients.py +0 -190
  966. synth_ai/v0/lm/cost/__init__.py +0 -0
  967. synth_ai/v0/lm/cost/monitor.py +0 -1
  968. synth_ai/v0/lm/cost/statefulness.py +0 -1
  969. synth_ai/v0/lm/injection.py +0 -80
  970. synth_ai/v0/lm/overrides.py +0 -206
  971. synth_ai/v0/lm/provider_support/__init__.py +0 -8
  972. synth_ai/v0/lm/provider_support/anthropic.py +0 -972
  973. synth_ai/v0/lm/provider_support/openai.py +0 -1139
  974. synth_ai/v0/lm/provider_support/suppress_logging.py +0 -31
  975. synth_ai/v0/lm/structured_outputs/__init__.py +0 -0
  976. synth_ai/v0/lm/structured_outputs/handler.py +0 -440
  977. synth_ai/v0/lm/structured_outputs/inject.py +0 -297
  978. synth_ai/v0/lm/structured_outputs/rehabilitate.py +0 -185
  979. synth_ai/v0/lm/tools/__init__.py +0 -3
  980. synth_ai/v0/lm/tools/base.py +0 -172
  981. synth_ai/v0/lm/unified_interface.py +0 -202
  982. synth_ai/v0/lm/vendors/__init__.py +0 -0
  983. synth_ai/v0/lm/vendors/base.py +0 -81
  984. synth_ai/v0/lm/vendors/core/__init__.py +0 -0
  985. synth_ai/v0/lm/vendors/core/anthropic_api.py +0 -387
  986. synth_ai/v0/lm/vendors/core/gemini_api.py +0 -292
  987. synth_ai/v0/lm/vendors/core/mistral_api.py +0 -322
  988. synth_ai/v0/lm/vendors/core/openai_api.py +0 -227
  989. synth_ai/v0/lm/vendors/core/synth_dev_api.py +0 -0
  990. synth_ai/v0/lm/vendors/local/__init__.py +0 -0
  991. synth_ai/v0/lm/vendors/local/ollama.py +0 -0
  992. synth_ai/v0/lm/vendors/openai_standard.py +0 -782
  993. synth_ai/v0/lm/vendors/openai_standard_responses.py +0 -259
  994. synth_ai/v0/lm/vendors/retries.py +0 -22
  995. synth_ai/v0/lm/vendors/supported/__init__.py +0 -0
  996. synth_ai/v0/lm/vendors/supported/custom_endpoint.py +0 -415
  997. synth_ai/v0/lm/vendors/supported/deepseek.py +0 -69
  998. synth_ai/v0/lm/vendors/supported/grok.py +0 -75
  999. synth_ai/v0/lm/vendors/supported/groq.py +0 -16
  1000. synth_ai/v0/lm/vendors/supported/ollama.py +0 -15
  1001. synth_ai/v0/lm/vendors/supported/openrouter.py +0 -74
  1002. synth_ai/v0/lm/vendors/supported/together.py +0 -11
  1003. synth_ai/v0/lm/vendors/synth_client.py +0 -835
  1004. synth_ai/v0/lm/warmup.py +0 -186
  1005. synth_ai/v0/tracing/__init__.py +0 -0
  1006. synth_ai/v0/tracing/abstractions.py +0 -224
  1007. synth_ai/v0/tracing/base_client.py +0 -91
  1008. synth_ai/v0/tracing/client_manager.py +0 -131
  1009. synth_ai/v0/tracing/config.py +0 -142
  1010. synth_ai/v0/tracing/context.py +0 -146
  1011. synth_ai/v0/tracing/decorators.py +0 -682
  1012. synth_ai/v0/tracing/events/__init__.py +0 -0
  1013. synth_ai/v0/tracing/events/manage.py +0 -147
  1014. synth_ai/v0/tracing/events/scope.py +0 -86
  1015. synth_ai/v0/tracing/events/store.py +0 -228
  1016. synth_ai/v0/tracing/immediate_client.py +0 -151
  1017. synth_ai/v0/tracing/local.py +0 -18
  1018. synth_ai/v0/tracing/log_client_base.py +0 -73
  1019. synth_ai/v0/tracing/retry_queue.py +0 -186
  1020. synth_ai/v0/tracing/trackers.py +0 -515
  1021. synth_ai/v0/tracing/upload.py +0 -409
  1022. synth_ai/v0/tracing/utils.py +0 -9
  1023. synth_ai/v0/tracing_v1/__init__.py +0 -16
  1024. synth_ai/v0/tracing_v1/abstractions.py +0 -224
  1025. synth_ai/v0/tracing_v1/base_client.py +0 -91
  1026. synth_ai/v0/tracing_v1/client_manager.py +0 -131
  1027. synth_ai/v0/tracing_v1/config.py +0 -142
  1028. synth_ai/v0/tracing_v1/context.py +0 -146
  1029. synth_ai/v0/tracing_v1/decorators.py +0 -703
  1030. synth_ai/v0/tracing_v1/events/__init__.py +0 -0
  1031. synth_ai/v0/tracing_v1/events/manage.py +0 -147
  1032. synth_ai/v0/tracing_v1/events/scope.py +0 -86
  1033. synth_ai/v0/tracing_v1/events/store.py +0 -228
  1034. synth_ai/v0/tracing_v1/immediate_client.py +0 -151
  1035. synth_ai/v0/tracing_v1/local.py +0 -18
  1036. synth_ai/v0/tracing_v1/log_client_base.py +0 -73
  1037. synth_ai/v0/tracing_v1/retry_queue.py +0 -186
  1038. synth_ai/v0/tracing_v1/trackers.py +0 -515
  1039. synth_ai/v0/tracing_v1/upload.py +0 -527
  1040. synth_ai/v0/tracing_v1/utils.py +0 -9
  1041. synth_ai/v0/tracing_v3/__init__.py +0 -10
  1042. synth_ai/v0/tracing_v3/abstractions.py +0 -3
  1043. synth_ai/v0/tracing_v3/decorators.py +0 -3
  1044. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +0 -3
  1045. synth_ai/v0/tracing_v3/session_tracer.py +0 -3
  1046. synth_ai-0.2.14.dist-info/METADATA +0 -139
  1047. synth_ai-0.2.14.dist-info/RECORD +0 -762
  1048. synth_ai-0.2.14.dist-info/top_level.txt +0 -2
  1049. /synth_ai/{demos/demo_task_apps → cli/demo_apps}/crafter/__init__.py +0 -0
  1050. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/__init__.py +0 -0
  1051. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/crafter_fft_4b.toml +0 -0
  1052. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +0 -0
  1053. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
  1054. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/_common.py +0 -0
  1055. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/app.py +0 -0
  1056. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/config.toml +0 -0
  1057. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/deploy_modal.py +0 -0
  1058. {examples/task_apps → synth_ai/core/apps}/__init__.py +0 -0
  1059. /synth_ai/{tracing_v3 → core/tracing_v3}/examples/basic_usage.py +0 -0
  1060. /synth_ai/{tracing_v3 → core/tracing_v3}/hooks.py +0 -0
  1061. /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
  1062. /synth_ai/{tracing_v3 → core/tracing_v3}/replica_sync.py +0 -0
  1063. /synth_ai/{tracing_v3 → core/tracing_v3}/serialization.py +0 -0
  1064. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
  1065. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
  1066. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
  1067. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/utils.py +0 -0
  1068. /synth_ai/{tracing_v3 → core/tracing_v3}/turso/__init__.py +0 -0
  1069. /synth_ai/{evals → sdk/judging}/types.py +0 -0
  1070. /synth_ai/{learning → sdk/learning}/algorithms.py +0 -0
  1071. /synth_ai/{learning → sdk/learning}/config.py +0 -0
  1072. /synth_ai/{learning → sdk/learning}/constants.py +0 -0
  1073. /synth_ai/{learning → sdk/learning}/core.py +0 -0
  1074. /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
  1075. /synth_ai/{learning → sdk/learning}/rl/__init__.py +0 -0
  1076. /synth_ai/{learning → sdk/learning}/rl/config.py +0 -0
  1077. /synth_ai/{learning → sdk/learning}/rl_client.py +0 -0
  1078. /synth_ai/{learning → sdk/learning}/sft/__init__.py +0 -0
  1079. /synth_ai/{learning → sdk/learning}/sse.py +0 -0
  1080. /synth_ai/{task → sdk/task}/auth.py +0 -0
  1081. /synth_ai/{task → sdk/task}/client.py +0 -0
  1082. /synth_ai/{task → sdk/task}/errors.py +0 -0
  1083. /synth_ai/{task → sdk/task}/health.py +0 -0
  1084. /synth_ai/{task → sdk/task}/json.py +0 -0
  1085. /synth_ai/{task → sdk/task}/rubrics/models.py +0 -0
  1086. /synth_ai/{task → sdk/task}/rubrics/scoring.py +0 -0
  1087. /synth_ai/{task → sdk/task}/rubrics/strict.py +0 -0
  1088. /synth_ai/{task → sdk/task}/vendors.py +0 -0
  1089. {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/WHEEL +0 -0
  1090. {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/entry_points.txt +0 -0
  1091. {synth_ai-0.2.14.dist-info → synth_ai-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1190 @@
1
+ """In-process task app support for local development and demos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import os
8
+ import signal
9
+ import socket
10
+ import subprocess
11
+ import threading
12
+ from pathlib import Path
13
+ from typing import Any, Callable, Optional
14
+ from urllib.parse import urlparse
15
+
16
+ import httpx
17
+ import uvicorn
18
+ from uvicorn._types import ASGIApplication
19
+
20
+ from synth_ai.core.apps.common import get_asgi_app, load_module
21
+ from synth_ai.core.integrations.cloudflare import (
22
+ create_tunnel,
23
+ ensure_cloudflared_installed,
24
+ open_managed_tunnel,
25
+ open_quick_tunnel_with_dns_verification,
26
+ rotate_tunnel,
27
+ stop_tunnel,
28
+ wait_for_health_check,
29
+ )
30
+ from synth_ai.core.paths import REPO_ROOT, configure_import_paths
31
+ from synth_ai.sdk.task.server import TaskAppConfig, create_task_app
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Global registry for signal handlers
36
+ _registered_instances: set[InProcessTaskApp] = set()
37
+
38
+
39
+ def _find_available_port(host: str, start_port: int, max_attempts: int = 100) -> int:
40
+ """
41
+ Find an available port starting from start_port.
42
+
43
+ Args:
44
+ host: Host to bind to
45
+ start_port: Starting port number
46
+ max_attempts: Maximum number of ports to try
47
+
48
+ Returns:
49
+ Available port number
50
+
51
+ Raises:
52
+ RuntimeError: If no available port found
53
+ """
54
+ for offset in range(max_attempts):
55
+ port = start_port + offset
56
+ try:
57
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
58
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
59
+ sock.bind((host, port))
60
+ return port
61
+ except OSError:
62
+ continue
63
+
64
+ raise RuntimeError(
65
+ f"Could not find available port starting from {start_port} "
66
+ f"(tried {max_attempts} ports)"
67
+ )
68
+
69
+
70
+ def _is_port_available(host: str, port: int) -> bool:
71
+ """Check if a port is available."""
72
+ try:
73
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
74
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
75
+ sock.bind((host, port))
76
+ return True
77
+ except OSError:
78
+ return False
79
+
80
+
81
+
82
+
83
+ def _kill_process_on_port(host: str, port: int) -> None:
84
+ """
85
+ Attempt to kill any process using the specified port.
86
+
87
+ Note: This is a best-effort operation and may not work on all systems.
88
+ """
89
+ import subprocess
90
+ import sys
91
+
92
+ try:
93
+ if sys.platform == "win32":
94
+ # Windows
95
+ result = subprocess.run(
96
+ ["netstat", "-ano"], capture_output=True, text=True, check=False
97
+ )
98
+ for line in result.stdout.splitlines():
99
+ if f":{port}" in line and "LISTENING" in line:
100
+ parts = line.split()
101
+ if len(parts) > 4:
102
+ pid = parts[-1]
103
+ try:
104
+ subprocess.run(
105
+ ["taskkill", "/F", "/PID", pid],
106
+ capture_output=True,
107
+ check=False,
108
+ )
109
+ logger.info(f"Killed process {pid} on port {port}")
110
+ except Exception:
111
+ pass
112
+ else:
113
+ # Unix-like (macOS, Linux)
114
+ result = subprocess.run(
115
+ ["lsof", "-ti", f":{port}"],
116
+ capture_output=True,
117
+ text=True,
118
+ check=False,
119
+ )
120
+ if result.stdout.strip():
121
+ pids = result.stdout.strip().split()
122
+ for pid in pids:
123
+ try:
124
+ subprocess.run(
125
+ ["kill", "-9", pid],
126
+ capture_output=True,
127
+ check=False,
128
+ )
129
+ logger.info(f"Killed process {pid} on port {port}")
130
+ except Exception:
131
+ pass
132
+ except Exception as e:
133
+ logger.debug(f"Could not kill process on port {port}: {e}")
134
+
135
+
136
+ async def _resolve_via_public_dns(hostname: str, timeout: float = 5.0) -> Optional[str]:
137
+ """
138
+ Resolve hostname using public DNS servers (1.1.1.1, 8.8.8.8).
139
+
140
+ This bypasses local DNS caching issues that can cause NXDOMAIN errors
141
+ when the local resolver has stale cached responses.
142
+
143
+ Returns the first resolved IP address, or None if resolution fails.
144
+ """
145
+ loop = asyncio.get_event_loop()
146
+
147
+ for dns_server in ("1.1.1.1", "8.8.8.8"):
148
+ try:
149
+ result = await loop.run_in_executor(
150
+ None,
151
+ lambda server=dns_server: subprocess.run(
152
+ ["dig", f"@{server}", "+short", hostname],
153
+ capture_output=True,
154
+ text=True,
155
+ timeout=timeout,
156
+ ),
157
+ )
158
+ if result.returncode == 0 and result.stdout.strip():
159
+ # Return first IP (dig may return multiple)
160
+ first_ip = result.stdout.strip().splitlines()[0].strip()
161
+ if first_ip and not first_ip.endswith("."): # Skip CNAME responses
162
+ logger.debug(f"Resolved {hostname} via {dns_server}: {first_ip}")
163
+ return first_ip
164
+ except FileNotFoundError:
165
+ # dig not available, try socket resolution instead
166
+ try:
167
+ result = await loop.run_in_executor(
168
+ None,
169
+ lambda: socket.gethostbyname(hostname),
170
+ )
171
+ if result:
172
+ logger.debug(f"Resolved {hostname} via system DNS: {result}")
173
+ return result
174
+ except socket.gaierror:
175
+ pass
176
+ except subprocess.TimeoutExpired:
177
+ logger.debug(f"DNS timeout resolving {hostname} via {dns_server}")
178
+ except Exception as e:
179
+ logger.debug(f"DNS resolution error for {hostname} via {dns_server}: {e}")
180
+
181
+ return None
182
+
183
+
184
+ async def _verify_tunnel_ready(
185
+ tunnel_url: str,
186
+ api_key: str,
187
+ *,
188
+ max_retries: int | None = None,
189
+ retry_delay: float | None = None,
190
+ timeout_per_request: float = 10.0,
191
+ verify_tls: bool = True,
192
+ ) -> bool:
193
+ """
194
+ Verify that a Cloudflare tunnel is actually routing traffic (not just DNS-resolvable).
195
+
196
+ A tunnel can resolve via DNS before HTTP routing is ready. This helper polls both
197
+ /health and /task_info until they return 200 or retries are exhausted.
198
+
199
+ IMPORTANT: Uses public DNS (1.1.1.1) to bypass local DNS cache issues.
200
+ Local DNS may have stale NXDOMAIN cached even after tunnel DNS is created.
201
+
202
+ Environment variables for tuning:
203
+ SYNTH_TUNNEL_VERIFY_MAX_RETRIES: Number of retry attempts (default: 30)
204
+ SYNTH_TUNNEL_VERIFY_DELAY_SECS: Delay between retries in seconds (default: 2.0)
205
+
206
+ With defaults, waits up to 60 seconds for tunnel to become ready.
207
+ """
208
+ # Allow env var overrides for reliability tuning
209
+ if max_retries is None:
210
+ max_retries = int(os.getenv("SYNTH_TUNNEL_VERIFY_MAX_RETRIES", "30"))
211
+ if retry_delay is None:
212
+ retry_delay = float(os.getenv("SYNTH_TUNNEL_VERIFY_DELAY_SECS", "2.0"))
213
+
214
+ # Initial delay before first check - tunnels often need a moment after DNS resolves
215
+ initial_delay = float(os.getenv("SYNTH_TUNNEL_VERIFY_INITIAL_DELAY_SECS", "3.0"))
216
+ if initial_delay > 0:
217
+ logger.debug(f"Waiting {initial_delay}s for tunnel to stabilize before verification...")
218
+ await asyncio.sleep(initial_delay)
219
+
220
+ # Parse hostname from URL
221
+ parsed = urlparse(tunnel_url)
222
+ hostname = parsed.netloc
223
+ scheme = parsed.scheme or "https"
224
+
225
+ headers = {
226
+ "X-API-Key": api_key,
227
+ "Authorization": f"Bearer {api_key}",
228
+ "Host": hostname, # Always set Host header for IP-based requests
229
+ }
230
+ aliases = (os.getenv("ENVIRONMENT_API_KEY_ALIASES") or "").strip()
231
+ if aliases:
232
+ headers["X-API-Keys"] = ",".join(
233
+ [api_key, *[p.strip() for p in aliases.split(",") if p.strip()]]
234
+ )
235
+
236
+ logger.info(f"Verifying tunnel is routing traffic (max {max_retries} attempts, {retry_delay}s delay)...")
237
+
238
+ # Track resolved IP to avoid re-resolving every attempt
239
+ resolved_ip: Optional[str] = None
240
+
241
+ for attempt in range(max_retries):
242
+ try:
243
+ # Try to resolve IP via public DNS if we don't have it yet
244
+ if resolved_ip is None:
245
+ resolved_ip = await _resolve_via_public_dns(hostname)
246
+ if resolved_ip:
247
+ logger.info(f"Resolved tunnel hostname via public DNS: {hostname} -> {resolved_ip}")
248
+
249
+ # If we have a resolved IP, use curl with --resolve for proper SNI handling
250
+ # httpx connecting to an IP directly fails SSL handshake due to SNI issues
251
+ if resolved_ip:
252
+ loop = asyncio.get_event_loop()
253
+
254
+ # Use curl with --resolve to bypass local DNS while maintaining proper SNI
255
+ async def curl_check(path: str) -> int:
256
+ try:
257
+ result = await loop.run_in_executor(
258
+ None,
259
+ lambda: subprocess.run(
260
+ [
261
+ "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
262
+ "--resolve", f"{hostname}:443:{resolved_ip}",
263
+ "-H", f"X-API-Key: {api_key}",
264
+ "-H", f"Authorization: Bearer {api_key}",
265
+ f"https://{hostname}{path}",
266
+ ],
267
+ capture_output=True,
268
+ text=True,
269
+ timeout=timeout_per_request,
270
+ ),
271
+ )
272
+ return int(result.stdout.strip()) if result.returncode == 0 else 0
273
+ except Exception:
274
+ return 0
275
+
276
+ health_status = await curl_check("/health")
277
+ task_info_status = await curl_check("/task_info")
278
+
279
+ if health_status == 200 and task_info_status == 200:
280
+ logger.info(
281
+ f"Tunnel ready after {attempt + 1} attempt(s): "
282
+ f"health={health_status}, task_info={task_info_status}"
283
+ )
284
+ return True
285
+
286
+ logger.debug(
287
+ "Tunnel not ready (attempt %s/%s): health=%s task_info=%s",
288
+ attempt + 1,
289
+ max_retries,
290
+ health_status,
291
+ task_info_status,
292
+ )
293
+ else:
294
+ # Fall back to hostname-based request (local DNS)
295
+ base = tunnel_url.rstrip("/")
296
+ async with httpx.AsyncClient(timeout=timeout_per_request, verify=verify_tls) as client:
297
+ health = await client.get(f"{base}/health", headers=headers)
298
+ task_info = await client.get(f"{base}/task_info", headers=headers)
299
+
300
+ if health.status_code == 200 and task_info.status_code == 200:
301
+ logger.info(
302
+ f"Tunnel ready after {attempt + 1} attempt(s): "
303
+ f"health={health.status_code}, task_info={task_info.status_code}"
304
+ )
305
+ return True
306
+
307
+ logger.debug(
308
+ "Tunnel not ready (attempt %s/%s): health=%s task_info=%s",
309
+ attempt + 1,
310
+ max_retries,
311
+ health.status_code,
312
+ task_info.status_code,
313
+ )
314
+ except Exception as exc: # pragma: no cover - defensive
315
+ logger.debug(
316
+ "Tunnel readiness check failed (attempt %s/%s): %s",
317
+ attempt + 1,
318
+ max_retries,
319
+ exc,
320
+ )
321
+ # Clear resolved IP on connection errors - might need to re-resolve
322
+ if "connect" in str(exc).lower() or "resolve" in str(exc).lower():
323
+ resolved_ip = None
324
+
325
+ if attempt < max_retries - 1:
326
+ # Log progress periodically (every 5 attempts)
327
+ if (attempt + 1) % 5 == 0:
328
+ elapsed = (attempt + 1) * retry_delay
329
+ remaining = (max_retries - attempt - 1) * retry_delay
330
+ logger.info(
331
+ f"Still waiting for tunnel... ({elapsed:.0f}s elapsed, {remaining:.0f}s remaining)"
332
+ )
333
+ await asyncio.sleep(retry_delay)
334
+
335
+ logger.warning(f"Tunnel verification exhausted after {max_retries} attempts")
336
+ return False
337
+
338
+
339
+ async def _verify_preconfigured_url_ready(
340
+ tunnel_url: str,
341
+ api_key: str,
342
+ *,
343
+ extra_headers: Optional[dict[str, str]] = None,
344
+ max_retries: int = 10,
345
+ retry_delay: float = 1.0,
346
+ timeout_per_request: float = 10.0,
347
+ ) -> bool:
348
+ """
349
+ Verify that a preconfigured tunnel URL is routing traffic.
350
+
351
+ This is similar to _verify_tunnel_ready but designed for external tunnel
352
+ providers (ngrok, etc.) where we don't control the tunnel setup.
353
+
354
+ Args:
355
+ tunnel_url: The external tunnel URL to verify
356
+ api_key: API key for task app authentication
357
+ extra_headers: Additional headers for the tunnel (e.g., auth tokens)
358
+ max_retries: Number of retry attempts
359
+ retry_delay: Delay between retries in seconds
360
+ timeout_per_request: Timeout for each HTTP request
361
+
362
+ Returns:
363
+ True if tunnel is accessible, False otherwise
364
+ """
365
+ base = tunnel_url.rstrip("/")
366
+ headers = {
367
+ "X-API-Key": api_key,
368
+ "Authorization": f"Bearer {api_key}",
369
+ }
370
+
371
+ # Add any extra headers (e.g., custom auth tokens)
372
+ if extra_headers:
373
+ headers.update(extra_headers)
374
+
375
+ logger.info(f"Verifying preconfigured URL is accessible (max {max_retries} attempts)...")
376
+
377
+ for attempt in range(max_retries):
378
+ try:
379
+ async with httpx.AsyncClient(timeout=timeout_per_request) as client:
380
+ health = await client.get(f"{base}/health", headers=headers)
381
+
382
+ # Only accept 200 for health checks - other codes may indicate misrouting
383
+ if health.status_code == 200:
384
+ logger.info(
385
+ f"Preconfigured URL ready after {attempt + 1} attempt(s): "
386
+ f"health={health.status_code}"
387
+ )
388
+ return True
389
+
390
+ logger.debug(
391
+ "Preconfigured URL not ready (attempt %s/%s): health=%s",
392
+ attempt + 1,
393
+ max_retries,
394
+ health.status_code,
395
+ )
396
+ except Exception as exc:
397
+ logger.debug(
398
+ "Preconfigured URL check failed (attempt %s/%s): %s",
399
+ attempt + 1,
400
+ max_retries,
401
+ exc,
402
+ )
403
+
404
+ if attempt < max_retries - 1:
405
+ await asyncio.sleep(retry_delay)
406
+
407
+ logger.warning(f"Preconfigured URL verification exhausted after {max_retries} attempts")
408
+ return False
409
+
410
+
411
+ class InProcessTaskApp:
412
+ """
413
+ Context manager for running task apps in-process with automatic tunneling.
414
+
415
+ This class simplifies local development and demos by:
416
+ 1. Starting a task app server in a background thread
417
+ 2. Opening a tunnel automatically (Cloudflare by default, or use preconfigured URL)
418
+ 3. Providing the tunnel URL for GEPA/MIPRO jobs
419
+ 4. Cleaning up everything on exit
420
+
421
+ Supports multiple input methods:
422
+ - FastAPI app instance (most direct)
423
+ - TaskAppConfig object
424
+ - Config factory function (Callable[[], TaskAppConfig])
425
+ - Task app file path (fallback for compatibility)
426
+
427
+ Tunnel modes:
428
+ - "quick": Cloudflare quick tunnel (default for local dev)
429
+ - "named": Cloudflare named/managed tunnel
430
+ - "local": No tunnel, use localhost URL directly
431
+ - "preconfigured": Use externally-provided URL (set via preconfigured_url param or
432
+ SYNTH_TASK_APP_URL env var). Useful for ngrok or other external tunnel providers.
433
+
434
+ Example:
435
+ ```python
436
+ from synth_ai.sdk.task.in_process import InProcessTaskApp
437
+ from heartdisease_task_app import build_config
438
+
439
+ # Default: use Cloudflare quick tunnel
440
+ async with InProcessTaskApp(
441
+ config_factory=build_config,
442
+ port=8114,
443
+ ) as task_app:
444
+ print(f"Task app running at: {task_app.url}")
445
+
446
+ # Use preconfigured URL (e.g., from ngrok, localtunnel, etc.)
447
+ async with InProcessTaskApp(
448
+ config_factory=build_config,
449
+ port=8000,
450
+ tunnel_mode="preconfigured",
451
+ preconfigured_url="https://abc123.ngrok.io",
452
+ ) as task_app:
453
+ print(f"Task app running at: {task_app.url}")
454
+ ```
455
+ """
456
+
457
+ def __init__(
458
+ self,
459
+ *,
460
+ app: Optional[ASGIApplication] = None,
461
+ config: Optional[TaskAppConfig] = None,
462
+ config_factory: Optional[Callable[[], TaskAppConfig]] = None,
463
+ task_app_path: Optional[Path | str] = None,
464
+ port: int = 8114,
465
+ host: str = "127.0.0.1",
466
+ tunnel_mode: str = "quick",
467
+ preconfigured_url: Optional[str] = None,
468
+ preconfigured_auth_header: Optional[str] = None,
469
+ preconfigured_auth_token: Optional[str] = None,
470
+ api_key: Optional[str] = None,
471
+ health_check_timeout: float = 30.0,
472
+ auto_find_port: bool = True,
473
+ skip_tunnel_verification: bool = True, # Default True - verification is unreliable
474
+ force_new_tunnel: bool = False,
475
+ on_start: Optional[Callable[[InProcessTaskApp], None]] = None,
476
+ on_stop: Optional[Callable[[InProcessTaskApp], None]] = None,
477
+ ):
478
+ """
479
+ Initialize in-process task app.
480
+
481
+ Args:
482
+ app: FastAPI app instance (most direct)
483
+ config: TaskAppConfig object
484
+ config_factory: Callable that returns TaskAppConfig
485
+ task_app_path: Path to task app .py file (fallback)
486
+ port: Local port to run server on
487
+ host: Host to bind to (default: 127.0.0.1, use 0.0.0.0 for external access)
488
+ tunnel_mode: Tunnel mode - "quick", "named", "local", or "preconfigured"
489
+ preconfigured_url: External tunnel URL to use when tunnel_mode="preconfigured".
490
+ Can also be set via SYNTH_TASK_APP_URL env var.
491
+ preconfigured_auth_header: Optional auth header name for preconfigured URL
492
+ (e.g., "x-custom-auth-token")
493
+ preconfigured_auth_token: Optional auth token value for preconfigured URL
494
+ api_key: API key for health checks (defaults to ENVIRONMENT_API_KEY env var)
495
+ health_check_timeout: Max time to wait for health check in seconds
496
+ auto_find_port: If True, automatically find available port if requested port is busy
497
+ skip_tunnel_verification: If True, skip HTTP verification of tunnel connectivity.
498
+ Useful when the tunnel URL is known to be valid.
499
+ force_new_tunnel: If True, create a fresh tunnel instead of reusing existing one.
500
+ Use this when an existing managed tunnel is stale/broken.
501
+ on_start: Optional callback called when task app starts (receives self)
502
+ on_stop: Optional callback called when task app stops (receives self)
503
+
504
+ Raises:
505
+ ValueError: If multiple or no input methods provided, or invalid parameters
506
+ FileNotFoundError: If task_app_path doesn't exist
507
+ """
508
+ # Validate: exactly one input method
509
+ inputs = [app, config, config_factory, task_app_path]
510
+ if sum(x is not None for x in inputs) != 1:
511
+ raise ValueError(
512
+ "Must provide exactly one of: app, config, config_factory, or task_app_path"
513
+ )
514
+
515
+ # Validate port range
516
+ if not (1024 <= port <= 65535):
517
+ raise ValueError(f"Port must be in range [1024, 65535], got {port}")
518
+
519
+ # Validate host (allow 0.0.0.0 for container environments)
520
+ allowed_hosts = ("127.0.0.1", "localhost", "0.0.0.0")
521
+ if host not in allowed_hosts:
522
+ raise ValueError(
523
+ f"Host must be one of {allowed_hosts} for security reasons, got {host}"
524
+ )
525
+
526
+ # Validate tunnel_mode
527
+ valid_modes = ("local", "quick", "named", "preconfigured")
528
+ if tunnel_mode not in valid_modes:
529
+ raise ValueError(f"tunnel_mode must be one of {valid_modes}, got {tunnel_mode}")
530
+
531
+ # Validate task_app_path if provided
532
+ if task_app_path:
533
+ path = Path(task_app_path)
534
+ if not path.exists():
535
+ raise FileNotFoundError(f"Task app path does not exist: {task_app_path}")
536
+ if path.suffix != ".py":
537
+ raise ValueError(
538
+ f"Task app path must be a .py file, got {task_app_path}"
539
+ )
540
+
541
+ self._app_input = app
542
+ self._config = config
543
+ self._config_factory = config_factory
544
+ self._task_app_path = Path(task_app_path) if task_app_path else None
545
+
546
+ self.port = port
547
+ self.host = host
548
+ self.tunnel_mode = tunnel_mode
549
+ self.preconfigured_url = preconfigured_url
550
+ self.preconfigured_auth_header = preconfigured_auth_header
551
+ self.preconfigured_auth_token = preconfigured_auth_token
552
+ self.api_key = api_key
553
+ self.health_check_timeout = health_check_timeout
554
+ self.auto_find_port = auto_find_port
555
+ self.skip_tunnel_verification = skip_tunnel_verification
556
+ self.force_new_tunnel = force_new_tunnel
557
+ self.on_start = on_start
558
+ self.on_stop = on_stop
559
+
560
+ self.url: Optional[str] = None
561
+ self._tunnel_proc: Optional[Any] = None
562
+ self._app: Optional[ASGIApplication] = None
563
+ self._uvicorn_server: Optional[uvicorn.Server] = None
564
+ self._server_thread: Optional[Any] = None
565
+ self._original_port = port # Track original requested port
566
+ self._is_preconfigured = False # Track if using preconfigured URL
567
+ self._dns_verified_by_backend = False # Track if backend verified DNS propagation
568
+
569
+ async def __aenter__(self) -> InProcessTaskApp:
570
+ """Start task app and tunnel."""
571
+
572
+ # For named tunnels, pre-fetch tunnel config to get the correct port
573
+ # (existing tunnels are configured for a specific port)
574
+ mode = os.getenv("SYNTH_TUNNEL_MODE", self.tunnel_mode)
575
+ if mode == "named":
576
+ try:
577
+ from synth_ai.core.env import get_api_key as get_synth_api_key
578
+ synth_api_key = get_synth_api_key()
579
+ if synth_api_key is None:
580
+ raise ValueError("SYNTH_API_KEY is required for named tunnel mode")
581
+ tunnel_config = await self._fetch_tunnel_config(synth_api_key)
582
+ tunnel_port = tunnel_config.get("local_port")
583
+ if tunnel_config.get("hostname") and tunnel_port and tunnel_port != self.port:
584
+ logger.info(
585
+ f"Existing managed tunnel is configured for port {tunnel_port}, "
586
+ f"adjusting from requested port {self.port}"
587
+ )
588
+ self.port = tunnel_port
589
+ # Store config for later use to avoid re-fetching
590
+ self._prefetched_tunnel_config = tunnel_config
591
+ except Exception as e:
592
+ logger.debug(f"Pre-fetch tunnel config failed: {e}")
593
+ self._prefetched_tunnel_config = None
594
+ else:
595
+ self._prefetched_tunnel_config = None
596
+
597
+ logger.info(f"Starting in-process task app on {self.host}:{self.port}")
598
+
599
+ # For named tunnels, the port is baked into the tunnel config - we MUST use it
600
+ tunnel_config = getattr(self, "_prefetched_tunnel_config", None) or {}
601
+ tunnel_port = tunnel_config.get("local_port")
602
+ is_named_tunnel_port = mode == "named" and tunnel_port and tunnel_port == self.port
603
+
604
+ # Handle port conflicts
605
+ if not _is_port_available(self.host, self.port):
606
+ if is_named_tunnel_port:
607
+ # Named tunnel port is REQUIRED - kill whatever is using it
608
+ print(f"[CLOUDFLARE-FIX] Named tunnel requires port {self.port}, killing existing process...")
609
+ logger.warning(
610
+ f"Named tunnel is configured for port {self.port}, killing existing process..."
611
+ )
612
+ _kill_process_on_port(self.host, self.port)
613
+ await asyncio.sleep(1.0) # Wait for port to free
614
+
615
+ if not _is_port_available(self.host, self.port):
616
+ raise RuntimeError(
617
+ f"Named tunnel requires port {self.port} but it's still in use after kill attempt. "
618
+ "Manually kill the process using this port, or delete and recreate the tunnel."
619
+ )
620
+ print(f"[CLOUDFLARE-FIX] Port {self.port} freed successfully")
621
+ elif self.auto_find_port:
622
+ print(f"Port {self.port} is in use, attempting to find available port...")
623
+ logger.warning(
624
+ f"Port {self.port} is in use, attempting to find available port..."
625
+ )
626
+ self.port = _find_available_port(self.host, self.port)
627
+ logger.info(f"Using port {self.port} instead")
628
+ else:
629
+ # Try to kill process on port
630
+ logger.warning(
631
+ f"Port {self.port} is in use, attempting to free it..."
632
+ )
633
+ _kill_process_on_port(self.host, self.port)
634
+ await asyncio.sleep(0.5) # Brief wait for port to free
635
+
636
+ if not _is_port_available(self.host, self.port):
637
+ raise RuntimeError(
638
+ f"Port {self.port} is still in use. "
639
+ "Set auto_find_port=True to automatically find an available port."
640
+ )
641
+
642
+ # 1. Get FastAPI app from whichever input method was provided
643
+ if self._app_input:
644
+ # Direct app - use as-is
645
+ self._app = self._app_input
646
+
647
+ elif self._config:
648
+ # TaskAppConfig - create app from it
649
+ self._app = create_task_app(self._config) # type: ignore[assignment]
650
+
651
+ elif self._config_factory:
652
+ # Callable - call it to get config, then create app
653
+ config = self._config_factory()
654
+ self._app = create_task_app(config) # type: ignore[assignment]
655
+
656
+ elif self._task_app_path:
657
+ # File path - load module and extract app
658
+ configure_import_paths(self._task_app_path, REPO_ROOT)
659
+ module = load_module(
660
+ self._task_app_path,
661
+ f"_inprocess_{self._task_app_path.stem}_{id(self)}",
662
+ )
663
+
664
+ # Try to get app directly first
665
+ try:
666
+ self._app = get_asgi_app(module) # type: ignore[assignment]
667
+ except RuntimeError:
668
+ # If no app found, try to get build_config function
669
+ build_config = getattr(module, "build_config", None)
670
+ if build_config and callable(build_config):
671
+ config = build_config()
672
+ self._app = create_task_app(config) # type: ignore[assignment]
673
+ else:
674
+ # Try registry lookup as last resort
675
+ from synth_ai.sdk.task.apps import registry
676
+ app_id = getattr(module, "APP_ID", None) or self._task_app_path.stem
677
+ entry = registry.get(app_id)
678
+ if entry and entry.config_factory:
679
+ config = entry.config_factory()
680
+ self._app = create_task_app(config) # type: ignore[assignment]
681
+ else:
682
+ raise RuntimeError(
683
+ f"Task app at {self._task_app_path} must expose either:\n"
684
+ f" - An ASGI app via `app = FastAPI(...)` or factory function\n"
685
+ f" - A `build_config()` function that returns TaskAppConfig\n"
686
+ f" - Be registered with register_task_app()"
687
+ ) from None
688
+
689
+ # 2. Start uvicorn in background thread
690
+ # Use daemon=True for local testing to allow quick exit
691
+ # The thread will be killed when the process exits
692
+ logger.debug(f"Starting uvicorn server on {self.host}:{self.port}")
693
+
694
+ config = uvicorn.Config(
695
+ self._app, # type: ignore[arg-type]
696
+ host=self.host,
697
+ port=self.port,
698
+ reload=False,
699
+ log_level="info",
700
+ )
701
+ self._uvicorn_server = uvicorn.Server(config)
702
+
703
+ def serve():
704
+ try:
705
+ self._uvicorn_server.run() # type: ignore[attr-defined]
706
+ except Exception as exc:
707
+ logger.debug(f"Uvicorn server stopped: {exc}")
708
+
709
+ self._server_thread = threading.Thread(
710
+ target=serve,
711
+ name=f"synth-uvicorn-{self.port}",
712
+ daemon=True, # Daemon thread dies when main process exits
713
+ )
714
+ self._server_thread.start()
715
+
716
+ # 3. Wait for health check
717
+ api_key = self.api_key or self._get_api_key()
718
+ logger.debug(f"Waiting for health check on {self.host}:{self.port}")
719
+ await wait_for_health_check(
720
+ self.host, self.port, api_key, timeout=self.health_check_timeout
721
+ )
722
+ logger.info(f"Health check passed for {self.host}:{self.port}")
723
+
724
+ # 4. Determine tunnel mode (env var can override)
725
+ mode = os.getenv("SYNTH_TUNNEL_MODE", self.tunnel_mode)
726
+
727
+ # Check for preconfigured URL via env var
728
+ env_preconfigured_url = os.getenv("SYNTH_TASK_APP_URL")
729
+ if env_preconfigured_url:
730
+ mode = "preconfigured"
731
+ self.preconfigured_url = env_preconfigured_url
732
+ logger.info(f"Using preconfigured URL from SYNTH_TASK_APP_URL: {env_preconfigured_url}")
733
+
734
+ override_host = os.getenv("SYNTH_TUNNEL_HOSTNAME")
735
+
736
+ if mode == "preconfigured":
737
+ # Preconfigured mode: use externally-provided URL (e.g., ngrok, localtunnel)
738
+ # This bypasses Cloudflare entirely - the caller is responsible for the tunnel
739
+ if not self.preconfigured_url:
740
+ raise ValueError(
741
+ "tunnel_mode='preconfigured' requires preconfigured_url parameter "
742
+ "or SYNTH_TASK_APP_URL environment variable"
743
+ )
744
+
745
+ self.url = self.preconfigured_url.rstrip("/")
746
+ self._tunnel_proc = None
747
+ self._is_preconfigured = True
748
+ logger.info(f"Using preconfigured tunnel URL: {self.url}")
749
+
750
+ # Optionally verify the preconfigured URL is accessible
751
+ if not self.skip_tunnel_verification:
752
+ api_key = self.api_key or self._get_api_key()
753
+
754
+ # Build headers including any custom auth for the tunnel
755
+ extra_headers: dict[str, str] = {}
756
+ if self.preconfigured_auth_header and self.preconfigured_auth_token:
757
+ extra_headers[self.preconfigured_auth_header] = self.preconfigured_auth_token
758
+
759
+ ready = await _verify_preconfigured_url_ready(
760
+ self.url,
761
+ api_key,
762
+ extra_headers=extra_headers,
763
+ max_retries=10, # Fewer retries - external URL should work quickly
764
+ retry_delay=1.0,
765
+ )
766
+ if ready:
767
+ logger.info(f"Preconfigured URL verified and ready: {self.url}")
768
+ else:
769
+ logger.warning(
770
+ f"Preconfigured URL {self.url} may not be accessible. "
771
+ "Proceeding anyway - set skip_tunnel_verification=True to suppress this warning."
772
+ )
773
+ elif mode == "local":
774
+ # Local mode: skip tunnel, use localhost
775
+ self.url = f"http://{self.host}:{self.port}"
776
+ self._tunnel_proc = None
777
+ logger.info(f"Using local mode: {self.url}")
778
+ elif mode == "named":
779
+ # Named tunnel mode: fully automatic managed tunnel
780
+ # 1. Check for existing tunnel
781
+ # 2. Auto-create if none exists
782
+ # 3. Auto-start cloudflared if not accessible
783
+ # 4. Verify tunnel is working
784
+ ensure_cloudflared_installed()
785
+
786
+ # For tunnel config, we need the SYNTH_API_KEY (not ENVIRONMENT_API_KEY)
787
+ from synth_ai.core.env import get_api_key as get_synth_api_key
788
+ synth_api_key = get_synth_api_key()
789
+ if synth_api_key is None:
790
+ raise ValueError("SYNTH_API_KEY is required for named tunnel mode")
791
+
792
+ # For task app auth, use the environment API key
793
+ api_key = self.api_key or self._get_api_key()
794
+
795
+ # Use pre-fetched config (port was already adjusted before server started)
796
+ tunnel_config = getattr(self, "_prefetched_tunnel_config", None) or {}
797
+ if not tunnel_config:
798
+ # Fetch if not pre-fetched (shouldn't happen normally)
799
+ tunnel_config = await self._fetch_tunnel_config(synth_api_key)
800
+
801
+ named_host = tunnel_config.get("hostname")
802
+ tunnel_token = tunnel_config.get("tunnel_token")
803
+
804
+ # Track if backend verified DNS (so we can skip local verification)
805
+ dns_verified_by_backend = False
806
+
807
+ # Force ROTATE tunnel if requested (deletes old + creates new, stays within limits)
808
+ if self.force_new_tunnel:
809
+ print("[CLOUDFLARE-FIX] force_new_tunnel=True, rotating tunnel...")
810
+ logger.info("force_new_tunnel=True, rotating tunnel (delete+create)")
811
+ try:
812
+ rotated = await rotate_tunnel(
813
+ synth_api_key=synth_api_key,
814
+ port=self.port,
815
+ reason="force_new_tunnel=True",
816
+ )
817
+ named_host = rotated.get("hostname")
818
+ tunnel_token = rotated.get("tunnel_token")
819
+ dns_verified_by_backend = rotated.get("dns_verified", False)
820
+ print(f"[CLOUDFLARE-FIX] Rotated to fresh tunnel: {named_host}")
821
+ print(f"[CLOUDFLARE-FIX] DNS verified by backend: {dns_verified_by_backend}")
822
+ logger.info(f"Rotated to fresh managed tunnel: {named_host}, dns_verified={dns_verified_by_backend}")
823
+ except Exception as e:
824
+ print(f"[CLOUDFLARE-FIX] Rotation failed: {e}, using existing tunnel: {named_host}")
825
+ logger.warning(f"Rotation failed: {e}, falling back to existing tunnel: {named_host}")
826
+ if not named_host or not tunnel_token:
827
+ raise RuntimeError(
828
+ f"Tunnel rotation failed and no existing tunnel found: {e}\n"
829
+ "Try using tunnel_mode='quick' instead."
830
+ ) from e
831
+ # Auto-create tunnel if none exists
832
+ elif not named_host:
833
+ logger.info("No managed tunnel found, creating one automatically...")
834
+ try:
835
+ # Generate subdomain from port or use default
836
+ subdomain = f"task-app-{self.port}"
837
+ new_tunnel = await create_tunnel(
838
+ synth_api_key=synth_api_key,
839
+ port=self.port,
840
+ subdomain=subdomain,
841
+ )
842
+ named_host = new_tunnel.get("hostname")
843
+ tunnel_token = new_tunnel.get("tunnel_token")
844
+ dns_verified_by_backend = new_tunnel.get("dns_verified", False)
845
+ logger.info(f"Created managed tunnel: {named_host}, dns_verified={dns_verified_by_backend}")
846
+ except Exception as e:
847
+ # If tunnel creation fails, suggest using quick tunnels
848
+ raise RuntimeError(
849
+ f"Failed to create managed tunnel: {e}\n"
850
+ "This may be because the backend doesn't have Cloudflare configured.\n"
851
+ "Options:\n"
852
+ " 1. Use tunnel_mode='quick' for automatic quick tunnels\n"
853
+ " 2. Ask your admin to configure Cloudflare credentials on the backend"
854
+ ) from e
855
+
856
+ if not named_host or not tunnel_token:
857
+ raise RuntimeError(
858
+ "Tunnel configuration incomplete (missing hostname or token). "
859
+ "Try deleting and recreating the tunnel, or use tunnel_mode='quick'."
860
+ )
861
+
862
+ self.url = f"https://{named_host}"
863
+ # Store dns_verified for use by job (to skip health check)
864
+ self._dns_verified_by_backend = dns_verified_by_backend
865
+
866
+ print(f"[CLOUDFLARE] Named tunnel URL: {self.url}")
867
+
868
+ # CRITICAL: For Cloudflare managed tunnels, DNS will NOT resolve until cloudflared connects.
869
+ # The DNS record exists in Cloudflare, but proxied CNAMEs to .cfargotunnel.com only
870
+ # resolve when the tunnel has an active cloudflared connection.
871
+ # Therefore, we MUST start cloudflared FIRST, then verify the tunnel works.
872
+
873
+ # First, check if cloudflared is already running (tunnel might be accessible)
874
+ ready = await _verify_tunnel_ready(
875
+ self.url,
876
+ api_key,
877
+ max_retries=1, # Single quick check
878
+ retry_delay=0.5,
879
+ verify_tls=_should_verify_tls(),
880
+ )
881
+
882
+ if ready:
883
+ # Tunnel already accessible - cloudflared must be running elsewhere
884
+ self._tunnel_proc = None
885
+ print(f"[CLOUDFLARE] Tunnel already accessible (cloudflared running externally)")
886
+ logger.info(f"Tunnel {self.url} is already accessible (cloudflared running externally)")
887
+ else:
888
+ # Tunnel not accessible - start cloudflared FIRST, then verify
889
+ print(f"[CLOUDFLARE] Starting cloudflared (DNS requires active tunnel connection)...")
890
+ logger.info(f"Starting cloudflared for {self.url}...")
891
+ try:
892
+ self._tunnel_proc = open_managed_tunnel(tunnel_token)
893
+ print(f"[CLOUDFLARE] cloudflared started, PID={self._tunnel_proc.pid}")
894
+ logger.info(f"Started cloudflared (PID: {self._tunnel_proc.pid})")
895
+ except Exception as e:
896
+ print(f"[CLOUDFLARE] ERROR starting cloudflared: {e}")
897
+ raise RuntimeError(
898
+ f"Failed to start cloudflared: {e}\n"
899
+ "Make sure cloudflared is installed: brew install cloudflare/cloudflare/cloudflared"
900
+ ) from e
901
+
902
+ # Wait for cloudflared to connect and tunnel to become accessible
903
+ print(f"[CLOUDFLARE] Waiting for tunnel to become accessible...")
904
+ ready = await _verify_tunnel_ready(
905
+ self.url,
906
+ api_key,
907
+ max_retries=15, # Up to ~30 seconds for tunnel to connect
908
+ retry_delay=2.0,
909
+ verify_tls=_should_verify_tls(),
910
+ )
911
+
912
+ if not ready:
913
+ # Tunnel still not accessible after starting cloudflared
914
+ # Clean up and try auto-rotation
915
+ if self._tunnel_proc:
916
+ stop_tunnel(self._tunnel_proc)
917
+ self._tunnel_proc = None
918
+
919
+ print(f"[CLOUDFLARE] Tunnel {self.url} not accessible, attempting rotation...")
920
+ logger.warning(f"Tunnel {self.url} failed to connect. Attempting rotation...")
921
+
922
+ try:
923
+ rotated = await rotate_tunnel(
924
+ synth_api_key=synth_api_key,
925
+ port=self.port,
926
+ reason=f"Tunnel {named_host} failed to connect",
927
+ )
928
+ named_host = rotated.get("hostname")
929
+ tunnel_token = rotated.get("tunnel_token")
930
+
931
+ if not named_host or not tunnel_token:
932
+ raise RuntimeError("Rotation returned incomplete tunnel config")
933
+
934
+ self.url = f"https://{named_host}"
935
+ print(f"[CLOUDFLARE] Rotated to new tunnel: {self.url}")
936
+
937
+ # Start cloudflared with the new token
938
+ self._tunnel_proc = open_managed_tunnel(tunnel_token)
939
+ print(f"[CLOUDFLARE] Started cloudflared for rotated tunnel, PID={self._tunnel_proc.pid}")
940
+
941
+ # Verify the new tunnel
942
+ ready = await _verify_tunnel_ready(
943
+ self.url,
944
+ api_key,
945
+ max_retries=15,
946
+ retry_delay=2.0,
947
+ verify_tls=_should_verify_tls(),
948
+ )
949
+
950
+ if not ready:
951
+ if self._tunnel_proc:
952
+ stop_tunnel(self._tunnel_proc)
953
+ self._tunnel_proc = None
954
+ raise RuntimeError(
955
+ f"Rotated tunnel {self.url} also failed. "
956
+ "Try using tunnel_mode='quick' instead."
957
+ )
958
+
959
+ print(f"[CLOUDFLARE] Rotated tunnel ready: {self.url}")
960
+
961
+ except Exception as rotate_err:
962
+ raise RuntimeError(
963
+ f"Tunnel failed and rotation failed: {rotate_err}\n"
964
+ "Try using tunnel_mode='quick' instead."
965
+ ) from rotate_err
966
+ else:
967
+ print(f"[CLOUDFLARE] Tunnel connected and ready: {self.url}")
968
+
969
+ logger.info(f"Using managed tunnel: {self.url}")
970
+ elif mode == "quick":
971
+ # Quick tunnel mode: create tunnel with DNS verification and retry
972
+ # Cloudflare quick tunnels can be flaky - retry with fresh tunnels if needed
973
+ ensure_cloudflared_installed()
974
+
975
+ api_key = self.api_key or self._get_api_key()
976
+ max_tunnel_attempts = int(os.getenv("SYNTH_TUNNEL_MAX_ATTEMPTS", "3"))
977
+
978
+ for tunnel_attempt in range(max_tunnel_attempts):
979
+ if tunnel_attempt > 0:
980
+ logger.warning(
981
+ f"Tunnel attempt {tunnel_attempt + 1}/{max_tunnel_attempts} - "
982
+ "requesting fresh tunnel..."
983
+ )
984
+ # Kill the previous tunnel process if it exists
985
+ if self._tunnel_proc:
986
+ try:
987
+ self._tunnel_proc.terminate()
988
+ await asyncio.sleep(1)
989
+ except Exception:
990
+ pass
991
+
992
+ logger.info("Opening Cloudflare quick tunnel...")
993
+ try:
994
+ self.url, self._tunnel_proc = await open_quick_tunnel_with_dns_verification(
995
+ self.port, api_key=api_key
996
+ )
997
+ except Exception as e:
998
+ logger.warning(f"Tunnel creation failed: {e}")
999
+ if tunnel_attempt == max_tunnel_attempts - 1:
1000
+ raise
1001
+ continue
1002
+
1003
+ # Apply hostname override if provided
1004
+ if override_host:
1005
+ parsed = urlparse(self.url)
1006
+ self.url = f"{parsed.scheme}://{override_host}"
1007
+ logger.info(f"Overriding hostname: {self.url}")
1008
+
1009
+ logger.info(f"Tunnel opened: {self.url}")
1010
+
1011
+ # Extra guard: wait for tunnel HTTP routing to become ready (not just DNS)
1012
+ ready = await _verify_tunnel_ready(
1013
+ self.url,
1014
+ api_key,
1015
+ verify_tls=_should_verify_tls(),
1016
+ )
1017
+ if ready:
1018
+ logger.info(f"Tunnel verified and ready: {self.url}")
1019
+ break
1020
+ else:
1021
+ logger.warning(
1022
+ f"Tunnel {self.url} not routing traffic after verification. "
1023
+ f"{'Retrying with fresh tunnel...' if tunnel_attempt < max_tunnel_attempts - 1 else 'Giving up.'}"
1024
+ )
1025
+ if tunnel_attempt == max_tunnel_attempts - 1:
1026
+ raise RuntimeError(
1027
+ f"Failed to establish working tunnel after {max_tunnel_attempts} attempts. "
1028
+ f"Last tunnel URL: {self.url}. "
1029
+ "This may indicate Cloudflare rate limiting or network issues. "
1030
+ "Try: SYNTH_TUNNEL_MODE=local if the backend can reach localhost, "
1031
+ "or use a named Cloudflare tunnel instead of quick tunnels."
1032
+ )
1033
+ else:
1034
+ raise ValueError(f"Unknown SYNTH_TUNNEL_MODE: {mode}")
1035
+
1036
+ # Register for signal handling
1037
+ _registered_instances.add(self)
1038
+
1039
+ # Call on_start callback if provided
1040
+ if self.on_start:
1041
+ try:
1042
+ self.on_start(self)
1043
+ except Exception as e:
1044
+ logger.warning(f"on_start callback raised exception: {e}")
1045
+
1046
+ return self
1047
+
1048
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
1049
+ """Stop tunnel and server."""
1050
+ logger.info("Stopping in-process task app...")
1051
+
1052
+ # Unregister from signal handling
1053
+ _registered_instances.discard(self)
1054
+
1055
+ # Call on_stop callback if provided
1056
+ if self.on_stop:
1057
+ try:
1058
+ self.on_stop(self)
1059
+ except Exception as e:
1060
+ logger.warning(f"on_stop callback raised exception: {e}")
1061
+
1062
+ # Stop tunnel
1063
+ if self._tunnel_proc:
1064
+ logger.debug("Stopping Cloudflare tunnel...")
1065
+ stop_tunnel(self._tunnel_proc)
1066
+ self._tunnel_proc = None
1067
+ logger.info("Tunnel stopped")
1068
+
1069
+ # Stop the uvicorn server thread gracefully to avoid killing the host process
1070
+ if self._server_thread and self._server_thread.is_alive():
1071
+ logger.debug("Stopping uvicorn server thread...")
1072
+ if self._uvicorn_server:
1073
+ self._uvicorn_server.should_exit = True
1074
+ self._server_thread.join(timeout=5.0)
1075
+ if self._server_thread.is_alive():
1076
+ if self._uvicorn_server:
1077
+ # Last resort if graceful shutdown hangs
1078
+ self._uvicorn_server.force_exit = True
1079
+ self._server_thread.join(timeout=1.0)
1080
+ if self._server_thread.is_alive():
1081
+ logger.warning(
1082
+ "Uvicorn server thread did not stop cleanly; "
1083
+ "it will exit with the main process"
1084
+ )
1085
+ self._server_thread = None
1086
+ self._uvicorn_server = None
1087
+
1088
+ def _get_api_key(self) -> str:
1089
+ """Get API key from environment or default."""
1090
+ import os
1091
+
1092
+ # Try to load .env file if available
1093
+ try:
1094
+ from dotenv import load_dotenv
1095
+ load_dotenv(override=False)
1096
+ except ImportError:
1097
+ pass
1098
+
1099
+ return os.getenv("ENVIRONMENT_API_KEY", "test")
1100
+
1101
+ async def _fetch_tunnel_config(self, api_key: str) -> dict:
1102
+ """Fetch the customer's tunnel configuration from the backend.
1103
+
1104
+ Uses the existing /api/v1/tunnels/tunnel endpoint to get the customer's
1105
+ active tunnels. Returns the first active tunnel's config.
1106
+
1107
+ Returns a dict with:
1108
+ - hostname: The customer's configured tunnel hostname (e.g., "myapp.usesynth.ai")
1109
+ - tunnel_token: The cloudflared tunnel token for running the tunnel
1110
+ - local_port: The local port the tunnel routes to
1111
+ - local_host: The local host the tunnel routes to
1112
+ """
1113
+ from synth_ai.core.env import get_backend_url
1114
+
1115
+ backend_url = get_backend_url()
1116
+ url = f"{backend_url}/api/v1/tunnels/"
1117
+
1118
+ async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
1119
+ try:
1120
+ resp = await client.get(
1121
+ url,
1122
+ headers={
1123
+ "Authorization": f"Bearer {api_key}",
1124
+ "X-API-Key": api_key,
1125
+ },
1126
+ )
1127
+
1128
+ if resp.status_code == 404:
1129
+ logger.debug("No tunnels found for this API key")
1130
+ return {}
1131
+
1132
+ if resp.status_code == 401:
1133
+ raise RuntimeError(
1134
+ "Invalid API key. Please check your SYNTH_API_KEY."
1135
+ )
1136
+
1137
+ resp.raise_for_status()
1138
+ tunnels = resp.json()
1139
+
1140
+ # Return the first active tunnel
1141
+ if tunnels and len(tunnels) > 0:
1142
+ tunnel = tunnels[0]
1143
+ return {
1144
+ "hostname": tunnel.get("hostname"),
1145
+ "tunnel_token": tunnel.get("tunnel_token"),
1146
+ "local_port": tunnel.get("local_port", 8000),
1147
+ "local_host": tunnel.get("local_host", "127.0.0.1"),
1148
+ }
1149
+
1150
+ return {}
1151
+
1152
+ except httpx.HTTPStatusError as e:
1153
+ logger.warning(f"Failed to fetch tunnel config: {e}")
1154
+ return {}
1155
+ except Exception as e:
1156
+ logger.debug(f"Tunnel config fetch failed: {e}")
1157
+ return {}
1158
+
1159
+
1160
+ def _setup_signal_handlers() -> None:
1161
+ """Set up signal handlers for graceful shutdown."""
1162
+
1163
+ def signal_handler(signum, frame):
1164
+ """Handle SIGINT/SIGTERM by cleaning up all registered instances."""
1165
+ logger.info(f"Received signal {signum}, cleaning up {len(_registered_instances)} instances...")
1166
+ for instance in list(_registered_instances):
1167
+ try:
1168
+ # Trigger cleanup
1169
+ if instance._tunnel_proc:
1170
+ stop_tunnel(instance._tunnel_proc)
1171
+ instance._tunnel_proc = None
1172
+ except Exception as e:
1173
+ logger.error(f"Error cleaning up instance: {e}")
1174
+ _registered_instances.clear()
1175
+
1176
+ # Register handlers (only once)
1177
+ if not hasattr(_setup_signal_handlers, "_registered"):
1178
+ signal.signal(signal.SIGINT, signal_handler) # type: ignore[misc]
1179
+ signal.signal(signal.SIGTERM, signal_handler) # type: ignore[misc]
1180
+ _setup_signal_handlers._registered = True # type: ignore[attr-defined]
1181
+
1182
+
1183
+ def _should_verify_tls() -> bool:
1184
+ """Return True unless explicitly disabled via env."""
1185
+ val = (os.getenv("SYNTH_TUNNEL_VERIFY_TLS") or "true").strip().lower()
1186
+ return val not in ("0", "false", "no", "off")
1187
+
1188
+
1189
+ # Set up signal handlers on module import
1190
+ _setup_signal_handlers()