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