synth-ai 0.2.9.dev5__py3-none-any.whl → 0.2.10__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 (349) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +23 -17
  3. examples/dev/qwen3_32b_qlora_4xh100.toml +40 -0
  4. examples/multi_step/crafter_rl_lora.md +29 -0
  5. examples/qwen_coder/README.md +102 -0
  6. examples/qwen_coder/_shared.py +113 -0
  7. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  8. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  9. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  10. examples/qwen_coder/generate_dataset.py +98 -0
  11. examples/qwen_coder/infer_ft_smoke.py +65 -0
  12. examples/qwen_coder/infer_prod_proxy.py +73 -0
  13. examples/qwen_coder/infer_via_synth.py +87 -0
  14. examples/qwen_coder/scripts/infer_coder.sh +19 -0
  15. examples/qwen_coder/scripts/train_coder_30b.sh +22 -0
  16. examples/qwen_coder/sft_full_17b.py +103 -0
  17. examples/qwen_coder/sft_lora_30b.py +110 -0
  18. examples/qwen_coder/subset_jsonl.py +39 -0
  19. examples/qwen_coder/todos.md +38 -0
  20. examples/qwen_coder/validate_jsonl.py +60 -0
  21. examples/rl/configs/eval_base_qwen.toml +1 -1
  22. examples/rl/configs/rl_from_base_qwen17.toml +1 -1
  23. examples/rl/download_dataset.py +26 -10
  24. examples/rl/run_eval.py +53 -52
  25. examples/rl/run_rl_and_save.py +29 -12
  26. examples/rl/task_app/math_single_step.py +180 -41
  27. examples/rl/task_app/math_task_app.py +14 -6
  28. examples/sft/README.md +139 -0
  29. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  30. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  31. examples/sft/evaluate.py +117 -0
  32. examples/sft/export_dataset.py +117 -0
  33. examples/sft/generate_traces.py +162 -0
  34. examples/swe/__init__.py +12 -0
  35. examples/swe/task_app/README.md +105 -0
  36. examples/swe/task_app/__init__.py +2 -0
  37. examples/swe/task_app/grpo_swe_mini.py +571 -0
  38. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  39. examples/swe/task_app/hosted/README.md +173 -0
  40. examples/swe/task_app/hosted/__init__.py +5 -0
  41. examples/swe/task_app/hosted/branching.py +143 -0
  42. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  43. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  44. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  45. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  46. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  47. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  48. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  49. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  50. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  51. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  52. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  53. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  54. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  55. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  56. examples/swe/task_app/hosted/hosted_app.py +204 -0
  57. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  58. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  59. examples/swe/task_app/hosted/main.py +100 -0
  60. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  61. examples/swe/task_app/hosted/registry.py +195 -0
  62. examples/swe/task_app/hosted/rollout.py +1869 -0
  63. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  64. examples/swe/task_app/hosted/storage/volume.py +211 -0
  65. examples/swe/task_app/hosted/test_agents.py +161 -0
  66. examples/swe/task_app/hosted/test_service.py +137 -0
  67. examples/swe/task_app/hosted/utils.py +62 -0
  68. examples/vlm/PROPOSAL.md +53 -0
  69. examples/vlm/README.md +68 -0
  70. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  71. examples/vlm/crafter_image_only_agent.py +207 -0
  72. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  73. examples/vlm/filter_image_rows.py +63 -0
  74. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  75. examples/warming_up_to_rl/analyze_trace_db.py +12 -10
  76. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  77. examples/warming_up_to_rl/export_trace_sft.py +218 -36
  78. examples/warming_up_to_rl/groq_test.py +15 -8
  79. examples/warming_up_to_rl/manage_secrets.py +29 -25
  80. examples/warming_up_to_rl/readme.md +9 -2
  81. examples/warming_up_to_rl/run_eval.py +137 -61
  82. examples/warming_up_to_rl/run_fft_and_save.py +131 -60
  83. examples/warming_up_to_rl/run_local_rollout.py +88 -39
  84. examples/warming_up_to_rl/run_local_rollout_modal.py +114 -28
  85. examples/warming_up_to_rl/run_local_rollout_parallel.py +81 -20
  86. examples/warming_up_to_rl/run_local_rollout_traced.py +126 -23
  87. examples/warming_up_to_rl/run_rl_and_save.py +35 -12
  88. examples/warming_up_to_rl/run_rollout_remote.py +44 -19
  89. examples/warming_up_to_rl/task_app/README.md +6 -2
  90. examples/warming_up_to_rl/task_app/grpo_crafter.py +319 -57
  91. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +11 -30
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +137 -182
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -1
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +1 -1
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -1
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +150 -57
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +105 -69
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +19 -7
  101. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +45 -42
  102. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +1 -1
  103. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +47 -45
  104. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +1 -1
  105. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +198 -92
  106. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  107. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +361 -263
  108. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  109. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +394 -274
  110. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +1 -1
  111. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +56 -62
  112. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  113. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +6 -15
  114. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  115. synth_ai/__init__.py +1 -0
  116. synth_ai/api/models/supported.py +376 -0
  117. synth_ai/api/train/builders.py +157 -26
  118. synth_ai/api/train/cli.py +213 -57
  119. synth_ai/api/train/config_finder.py +65 -5
  120. synth_ai/api/train/env_resolver.py +33 -15
  121. synth_ai/api/train/pollers.py +13 -4
  122. synth_ai/api/train/supported_algos.py +139 -0
  123. synth_ai/api/train/task_app.py +5 -3
  124. synth_ai/api/train/utils.py +33 -48
  125. synth_ai/cli/__init__.py +19 -4
  126. synth_ai/cli/_modal_wrapper.py +28 -0
  127. synth_ai/cli/_typer_patch.py +49 -0
  128. synth_ai/cli/balance.py +2 -3
  129. synth_ai/cli/calc.py +1 -1
  130. synth_ai/cli/demo.py +21 -6
  131. synth_ai/cli/recent.py +2 -2
  132. synth_ai/cli/rl_demo.py +77 -17
  133. synth_ai/cli/root.py +116 -39
  134. synth_ai/cli/status.py +2 -2
  135. synth_ai/cli/task_apps.py +1699 -259
  136. synth_ai/cli/traces.py +7 -4
  137. synth_ai/cli/turso.py +73 -0
  138. synth_ai/cli/watch.py +12 -18
  139. synth_ai/core/experiment.py +0 -2
  140. synth_ai/demo_registry.py +68 -31
  141. synth_ai/demos/core/cli.py +516 -194
  142. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  143. synth_ai/demos/demo_task_apps/core.py +64 -28
  144. synth_ai/demos/demo_task_apps/crafter/configs/crafter_fft_4b.toml +2 -3
  145. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +37 -30
  146. synth_ai/demos/demo_task_apps/math/_common.py +1 -2
  147. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  148. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -6
  149. synth_ai/demos/demo_task_apps/math/modal_task_app.py +183 -82
  150. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -2
  151. synth_ai/environments/examples/bandit/engine.py +12 -4
  152. synth_ai/environments/examples/bandit/taskset.py +4 -4
  153. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  154. synth_ai/environments/reproducibility/tree.py +5 -6
  155. synth_ai/environments/service/app.py +11 -12
  156. synth_ai/environments/service/core_routes.py +10 -9
  157. synth_ai/environments/stateful/engine.py +1 -1
  158. synth_ai/environments/tasks/core.py +1 -0
  159. synth_ai/environments/tasks/filters.py +5 -6
  160. synth_ai/environments/tasks/utils.py +4 -5
  161. synth_ai/evals/base.py +0 -2
  162. synth_ai/handshake.py +11 -9
  163. synth_ai/http.py +1 -1
  164. synth_ai/http_client.py +43 -11
  165. synth_ai/inference/__init__.py +0 -2
  166. synth_ai/inference/client.py +20 -6
  167. synth_ai/jobs/client.py +103 -78
  168. synth_ai/learning/__init__.py +41 -6
  169. synth_ai/learning/algorithms.py +14 -0
  170. synth_ai/learning/client.py +121 -29
  171. synth_ai/learning/config.py +2 -40
  172. synth_ai/learning/constants.py +0 -2
  173. synth_ai/learning/ft_client.py +4 -56
  174. synth_ai/learning/health.py +13 -7
  175. synth_ai/learning/jobs.py +43 -47
  176. synth_ai/{rl → learning/rl}/__init__.py +14 -5
  177. synth_ai/learning/rl/client.py +267 -0
  178. synth_ai/learning/rl/config.py +31 -0
  179. synth_ai/{rl → learning/rl}/contracts.py +5 -10
  180. synth_ai/{rl → learning/rl}/env_keys.py +45 -16
  181. synth_ai/learning/rl/secrets.py +13 -0
  182. synth_ai/learning/rl_client.py +2 -253
  183. synth_ai/learning/sft/__init__.py +29 -0
  184. synth_ai/learning/sft/client.py +68 -0
  185. synth_ai/learning/sft/config.py +270 -0
  186. synth_ai/learning/sft/data.py +295 -0
  187. synth_ai/learning/sse.py +25 -26
  188. synth_ai/learning/validators.py +25 -24
  189. synth_ai/lm/__init__.py +21 -47
  190. synth_ai/task/__init__.py +26 -27
  191. synth_ai/task/apps/__init__.py +18 -19
  192. synth_ai/task/auth.py +35 -23
  193. synth_ai/task/client.py +15 -13
  194. synth_ai/task/contracts.py +37 -35
  195. synth_ai/task/datasets.py +9 -6
  196. synth_ai/task/errors.py +11 -10
  197. synth_ai/task/health.py +17 -11
  198. synth_ai/task/json.py +58 -24
  199. synth_ai/task/proxy.py +15 -14
  200. synth_ai/task/rubrics.py +22 -15
  201. synth_ai/task/server.py +43 -17
  202. synth_ai/task/tracing_utils.py +12 -7
  203. synth_ai/task/validators.py +0 -1
  204. synth_ai/task/vendors.py +5 -7
  205. synth_ai/tracing_v3/__init__.py +2 -0
  206. synth_ai/tracing_v3/abstractions.py +21 -4
  207. synth_ai/tracing_v3/db_config.py +26 -1
  208. synth_ai/tracing_v3/decorators.py +18 -15
  209. synth_ai/tracing_v3/examples/basic_usage.py +3 -2
  210. synth_ai/tracing_v3/hooks.py +6 -4
  211. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  212. synth_ai/tracing_v3/replica_sync.py +1 -0
  213. synth_ai/tracing_v3/session_tracer.py +63 -16
  214. synth_ai/tracing_v3/storage/base.py +89 -1
  215. synth_ai/tracing_v3/storage/config.py +21 -8
  216. synth_ai/tracing_v3/storage/factory.py +10 -8
  217. synth_ai/tracing_v3/storage/utils.py +4 -2
  218. synth_ai/tracing_v3/turso/daemon.py +7 -2
  219. synth_ai/tracing_v3/turso/models.py +5 -2
  220. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  221. synth_ai/tracing_v3/utils.py +4 -3
  222. synth_ai/v0/api/__init__.py +8 -0
  223. synth_ai/v0/api/models/__init__.py +8 -0
  224. synth_ai/v0/api/models/supported.py +8 -0
  225. synth_ai/v0/config/__init__.py +15 -0
  226. synth_ai/v0/config/base_url.py +12 -0
  227. synth_ai/v0/lm/__init__.py +51 -0
  228. synth_ai/{lm → v0/lm}/caching/ephemeral.py +3 -5
  229. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  230. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  231. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  232. synth_ai/{lm → v0/lm}/config.py +6 -1
  233. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  234. synth_ai/{lm → v0/lm}/core/exceptions.py +0 -2
  235. synth_ai/{lm → v0/lm}/core/main.py +19 -7
  236. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  237. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -15
  238. synth_ai/{lm → v0/lm}/core/vendor_clients.py +6 -4
  239. synth_ai/{lm → v0/lm}/overrides.py +4 -4
  240. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  241. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  242. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  243. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  244. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +16 -16
  245. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  246. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  247. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +12 -10
  248. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +11 -9
  249. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +8 -5
  250. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +4 -6
  251. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  252. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  253. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  254. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  255. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  256. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  257. synth_ai/{lm → v0/lm}/vendors/synth_client.py +38 -11
  258. synth_ai/v0/tracing/upload.py +32 -135
  259. synth_ai/v0/tracing_v3/__init__.py +10 -0
  260. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  261. synth_ai/v0/tracing_v3/decorators.py +3 -0
  262. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  263. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  264. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/METADATA +10 -7
  265. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/RECORD +294 -258
  266. examples/common_old/backend.py +0 -21
  267. examples/evals_old/README.md +0 -98
  268. examples/evals_old/__init__.py +0 -6
  269. examples/evals_old/compare_models.py +0 -1037
  270. examples/evals_old/example_log.md +0 -145
  271. examples/evals_old/run_demo.sh +0 -126
  272. examples/evals_old/trace_analysis.py +0 -270
  273. examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
  274. examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
  275. examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
  276. examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -239
  277. examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
  278. examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
  279. examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
  280. examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
  281. examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
  282. examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -118
  283. examples/finetuning_old/synth_qwen_v1/README.md +0 -68
  284. examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
  285. examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -239
  286. examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
  287. examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
  288. examples/finetuning_old/synth_qwen_v1/infer.py +0 -37
  289. examples/finetuning_old/synth_qwen_v1/poll.py +0 -44
  290. examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
  291. examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
  292. examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1932
  293. examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -207
  294. examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -232
  295. examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
  296. examples/finetuning_old/synth_qwen_v1/util.py +0 -147
  297. examples/rl_old/task_app.py +0 -962
  298. synth_ai/experimental/synth_oss.py +0 -446
  299. synth_ai/install_sqld.sh +0 -40
  300. synth_ai/learning/filtering.py +0 -0
  301. synth_ai/learning/offline/dpo.py +0 -0
  302. synth_ai/learning/offline/providers.py +0 -7
  303. synth_ai/learning/offline/sft.py +0 -0
  304. synth_ai/learning/offline/shared.py +0 -0
  305. synth_ai/learning/online/grpo.py +0 -0
  306. synth_ai/learning/online/irft.py +0 -0
  307. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  308. synth_ai/learning/prompts/gepa.py +0 -0
  309. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
  310. synth_ai/learning/prompts/mipro.py +0 -289
  311. synth_ai/learning/prompts/random_search.py +0 -246
  312. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  313. synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
  314. synth_ai/rl/secrets.py +0 -19
  315. synth_ai/scripts/verify_rewards.py +0 -100
  316. synth_ai/tracing/__init__.py +0 -30
  317. synth_ai/tracing_v1/__init__.py +0 -33
  318. synth_ai/tracing_v3/turso/__init__.py +0 -25
  319. synth_ai/tracing_v3/turso/manager.py +0 -774
  320. synth_ai/zyk/__init__.py +0 -30
  321. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  322. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  323. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  324. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  325. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  326. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  327. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  328. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  329. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  330. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  331. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  332. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  333. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  334. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  335. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  336. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  337. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  338. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  339. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  340. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  341. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  342. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  343. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  344. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  345. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  346. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/WHEEL +0 -0
  347. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/entry_points.txt +0 -0
  348. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/licenses/LICENSE +0 -0
  349. {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/top_level.txt +0 -0
@@ -37,7 +37,7 @@ Concepts:
37
37
  from __future__ import annotations
38
38
 
39
39
  from dataclasses import asdict, dataclass, field
40
- from datetime import datetime
40
+ from datetime import UTC, datetime
41
41
  from typing import Any
42
42
 
43
43
  from .lm_call_record_abstractions import LLMCallRecord
@@ -61,6 +61,23 @@ class TimeRecord:
61
61
  message_time: int | None = None
62
62
 
63
63
 
64
+ @dataclass(frozen=True)
65
+ class SessionMessageContent:
66
+ """Normalized payload stored alongside session messages."""
67
+
68
+ text: str | None = None
69
+ json_payload: str | None = None
70
+
71
+ def as_text(self) -> str:
72
+ return self.text or (self.json_payload or "")
73
+
74
+ def has_json(self) -> bool:
75
+ return self.json_payload is not None
76
+
77
+ def __str__(self) -> str: # pragma: no cover - convenience for logging
78
+ return self.as_text()
79
+
80
+
64
81
  @dataclass
65
82
  class SessionEventMarkovBlanketMessage:
66
83
  """Message crossing Markov blanket boundaries between systems in a session.
@@ -97,7 +114,7 @@ class SessionEventMarkovBlanketMessage:
97
114
  - 'causal_influence': Direction of causal flow
98
115
  """
99
116
 
100
- content: str
117
+ content: SessionMessageContent
101
118
  message_type: str
102
119
  time_record: TimeRecord
103
120
  metadata: dict[str, Any] = field(default_factory=dict)
@@ -232,7 +249,7 @@ class SessionTimeStep:
232
249
 
233
250
  step_id: str = ""
234
251
  step_index: int = 0
235
- timestamp: datetime = field(default_factory=datetime.utcnow)
252
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
236
253
  turn_number: int | None = None
237
254
  events: list[BaseEvent] = field(default_factory=list)
238
255
  markov_blanket_messages: list[SessionEventMarkovBlanketMessage] = field(default_factory=list)
@@ -266,7 +283,7 @@ class SessionTrace:
266
283
  """
267
284
 
268
285
  session_id: str = ""
269
- created_at: datetime = field(default_factory=datetime.utcnow)
286
+ created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
270
287
  session_time_steps: list[SessionTimeStep] = field(default_factory=list)
271
288
  event_history: list[BaseEvent] = field(default_factory=list)
272
289
  markov_blanket_message_history: list[SessionEventMarkovBlanketMessage] = field(
@@ -4,6 +4,7 @@ Centralized database configuration for v3 tracing.
4
4
 
5
5
  import logging
6
6
  import os
7
+ import shutil
7
8
  from typing import TYPE_CHECKING, Optional
8
9
 
9
10
  if TYPE_CHECKING:
@@ -30,7 +31,7 @@ class DatabaseConfig:
30
31
  http_port: HTTP port for sqld daemon. If None, uses DEFAULT_HTTP_PORT from serve.sh.
31
32
  use_sqld: Whether to use sqld daemon or direct SQLite.
32
33
  """
33
- self.use_sqld = use_sqld
34
+ self.use_sqld = use_sqld and self._sqld_binary_available()
34
35
  self.http_port = http_port or int(os.getenv("SQLD_HTTP_PORT", self.DEFAULT_HTTP_PORT))
35
36
  self._daemon: SqldDaemon | None = None
36
37
 
@@ -70,6 +71,30 @@ class DatabaseConfig:
70
71
  # SQLite URLs need 3 slashes for absolute paths
71
72
  return f"sqlite+aiosqlite:///{actual_db_path}"
72
73
 
74
+ def _sqld_binary_available(self) -> bool:
75
+ """Check if the sqld (Turso) binary is available on PATH."""
76
+ # Respect explicit SQLD_BINARY override when present
77
+ binary_override = os.getenv("SQLD_BINARY")
78
+ candidates = [binary_override, "sqld", "libsql-server"]
79
+
80
+ for candidate in candidates:
81
+ if candidate and shutil.which(candidate):
82
+ return True
83
+
84
+ if binary_override:
85
+ logger.warning(
86
+ "Configured SQLD_BINARY='%s' but the executable was not found on PATH. "
87
+ "Falling back to direct SQLite.",
88
+ binary_override,
89
+ )
90
+ else:
91
+ logger.warning(
92
+ "sqld binary not detected; falling back to SQLite-only mode. "
93
+ "Install Turso's sqld or set SQLD_BINARY to enable the Turso daemon."
94
+ )
95
+
96
+ return False
97
+
73
98
  def start_daemon(self, wait_time: float = 2.0):
74
99
  """
75
100
  Start the sqld daemon if configured.
@@ -1,4 +1,3 @@
1
- from __future__ import annotations
2
1
  """Async-aware decorators for tracing v3.
3
2
 
4
3
  This module provides decorators and context management utilities for the tracing
@@ -23,6 +22,8 @@ The decorators support both sync and async functions where appropriate,
23
22
  though async is preferred for consistency with the rest of the system.
24
23
  """
25
24
 
25
+ from __future__ import annotations
26
+
26
27
  import asyncio
27
28
  import contextvars
28
29
  import functools
@@ -36,12 +37,8 @@ from .utils import calculate_cost, detect_provider
36
37
  # Context variables for session and turn tracking
37
38
  # These variables automatically propagate across async call boundaries,
38
39
  # allowing deeply nested code to access tracing context without explicit passing
39
- _session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar(
40
- "session_id", default=None
41
- )
42
- _turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar(
43
- "turn_number", default=None
44
- )
40
+ _session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar("session_id")
41
+ _turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar("turn_number")
45
42
  _session_tracer_ctx: contextvars.ContextVar[Any | None] = contextvars.ContextVar(
46
43
  "session_tracer", default=None
47
44
  )
@@ -119,7 +116,9 @@ def with_session(require: bool = True):
119
116
  async def async_wrapper(*args, **kwargs):
120
117
  session_id = get_session_id()
121
118
  if require and session_id is None:
122
- raise RuntimeError(f"No active session for {fn.__name__}")
119
+ raise RuntimeError(
120
+ f"No active session for {getattr(fn, '__name__', 'unknown')}"
121
+ )
123
122
  return await fn(*args, **kwargs)
124
123
 
125
124
  return async_wrapper
@@ -129,7 +128,9 @@ def with_session(require: bool = True):
129
128
  def sync_wrapper(*args, **kwargs):
130
129
  session_id = get_session_id()
131
130
  if require and session_id is None:
132
- raise RuntimeError(f"No active session for {fn.__name__}")
131
+ raise RuntimeError(
132
+ f"No active session for {getattr(fn, '__name__', 'unknown')}"
133
+ )
133
134
  return fn(*args, **kwargs)
134
135
 
135
136
  return sync_wrapper
@@ -138,7 +139,7 @@ def with_session(require: bool = True):
138
139
 
139
140
 
140
141
  def trace_llm_call(
141
- model_name: str = None,
142
+ model_name: str | None = None,
142
143
  system_id: str = "llm",
143
144
  extract_tokens: bool = True,
144
145
  extract_cost: bool = True,
@@ -208,14 +209,16 @@ def trace_llm_call(
208
209
  input_tokens=input_tokens,
209
210
  output_tokens=output_tokens,
210
211
  total_tokens=total_tokens,
211
- cost_usd=calculate_cost(actual_model, input_tokens or 0, output_tokens or 0)
212
+ cost_usd=calculate_cost(
213
+ actual_model or "unknown", input_tokens or 0, output_tokens or 0
214
+ )
212
215
  if extract_cost
213
216
  else None,
214
217
  latency_ms=latency_ms,
215
218
  system_state_before=system_state_before,
216
219
  system_state_after=kwargs.get("state_after", {}),
217
220
  metadata={
218
- "function": fn.__name__,
221
+ "function": getattr(fn, "__name__", "unknown"),
219
222
  "step_id": kwargs.get("step_id"),
220
223
  },
221
224
  )
@@ -234,7 +237,7 @@ def trace_llm_call(
234
237
  provider=detect_provider(model_name),
235
238
  latency_ms=int((time.time() - start_time) * 1000),
236
239
  metadata={
237
- "function": fn.__name__,
240
+ "function": getattr(fn, "__name__", "unknown"),
238
241
  "error": str(e),
239
242
  "error_type": type(e).__name__,
240
243
  },
@@ -249,7 +252,7 @@ def trace_llm_call(
249
252
  return decorator
250
253
 
251
254
 
252
- def trace_method(event_type: str = "runtime", system_id: str = None):
255
+ def trace_method(event_type: str = "runtime", system_id: str | None = None):
253
256
  """Generic method tracing decorator.
254
257
 
255
258
  Traces any method call by recording it as a RuntimeEvent. Supports both
@@ -288,7 +291,7 @@ def trace_method(event_type: str = "runtime", system_id: str = None):
288
291
  time_record=TimeRecord(event_time=time.time()),
289
292
  actions=[], # Can be overridden in metadata
290
293
  metadata={
291
- "method": fn.__name__,
294
+ "method": getattr(fn, "__name__", "unknown"),
292
295
  "args": str(args)[:100], # Truncate for safety
293
296
  "step_id": kwargs.get("step_id"),
294
297
  },
@@ -166,8 +166,9 @@ async def main():
166
166
 
167
167
  tracer.hooks.register("event_recorded", count_events, name="event_counter")
168
168
 
169
- async with tracer.session(metadata={"example": "hooks"}) as session_id, tracer.timestep(
170
- "hook_test"
169
+ async with (
170
+ tracer.session(metadata={"example": "hooks"}) as session_id,
171
+ tracer.timestep("hook_test"),
171
172
  ):
172
173
  for i in range(3):
173
174
  event = RuntimeEvent(
@@ -1,4 +1,3 @@
1
- from __future__ import annotations
2
1
  """Hook system for extending tracing functionality.
3
2
 
4
3
  The hook system provides a flexible way to extend the tracing system without
@@ -33,6 +32,8 @@ Common Use Cases:
33
32
  - Custom filtering and sampling
34
33
  """
35
34
 
35
+ from __future__ import annotations
36
+
36
37
  import asyncio
37
38
  from collections.abc import Callable
38
39
  from dataclasses import dataclass
@@ -88,9 +89,9 @@ class HookManager:
88
89
  self,
89
90
  event: str,
90
91
  callback: Callable,
91
- name: str = None,
92
+ name: str | None = None,
92
93
  priority: int = 0,
93
- event_types: list[str] = None,
94
+ event_types: list[str] | None = None,
94
95
  ) -> Hook:
95
96
  """Register a new hook.
96
97
 
@@ -114,7 +115,7 @@ class HookManager:
114
115
  raise ValueError(f"Unknown hook event: {event}")
115
116
 
116
117
  hook = Hook(
117
- name=name or callback.__name__,
118
+ name=name or getattr(callback, "__name__", "unknown"),
118
119
  callback=callback,
119
120
  event_types=event_types,
120
121
  priority=priority,
@@ -202,6 +203,7 @@ def create_default_hooks() -> HookManager:
202
203
  # Example: Log session starts - useful for debugging and monitoring
203
204
  async def log_session_start(session_id: str, metadata: dict[str, Any]):
204
205
  import os
206
+
205
207
  if os.getenv("SYNTH_TRACE_VERBOSE", "0") in ("1", "true", "True"):
206
208
  print(f"Session started: {session_id}")
207
209
 
@@ -5,10 +5,9 @@ format and compute aggregates from call records.
5
5
  """
6
6
 
7
7
  import uuid
8
- from datetime import datetime
8
+ from datetime import UTC, datetime
9
9
  from typing import Any
10
10
 
11
- from synth_ai.lm.vendors.base import BaseLMResponse
12
11
  from synth_ai.tracing_v3.lm_call_record_abstractions import (
13
12
  LLMCallRecord,
14
13
  LLMChunk,
@@ -18,6 +17,7 @@ from synth_ai.tracing_v3.lm_call_record_abstractions import (
18
17
  LLMUsage,
19
18
  ToolCallSpec,
20
19
  )
20
+ from synth_ai.v0.lm.vendors.base import BaseLMResponse
21
21
 
22
22
 
23
23
  def create_llm_call_record_from_response(
@@ -161,8 +161,8 @@ def create_llm_call_record_from_response(
161
161
  api_type=api_type,
162
162
  provider=provider,
163
163
  model_name=model_name,
164
- started_at=started_at or datetime.utcnow(),
165
- completed_at=completed_at or datetime.utcnow(),
164
+ started_at=started_at or datetime.now(UTC),
165
+ completed_at=completed_at or datetime.now(UTC),
166
166
  latency_ms=latency_ms,
167
167
  request_params=params,
168
168
  input_messages=input_messages,
@@ -322,8 +322,8 @@ def create_llm_call_record_from_streaming(
322
322
  api_type="responses", # Streaming typically from Responses API
323
323
  provider=provider,
324
324
  model_name=model_name,
325
- started_at=started_at or datetime.utcnow(),
326
- completed_at=completed_at or datetime.utcnow(),
325
+ started_at=started_at or datetime.now(UTC),
326
+ completed_at=completed_at or datetime.now(UTC),
327
327
  latency_ms=latency_ms,
328
328
  request_params=params,
329
329
  input_messages=input_messages,
@@ -180,6 +180,7 @@ class ReplicaSync:
180
180
  # Request cancellation
181
181
  self._sync_task.cancel()
182
182
  import contextlib
183
+
183
184
  with contextlib.suppress(asyncio.CancelledError):
184
185
  # Wait for the task to finish
185
186
  await self._sync_task
@@ -1,14 +1,17 @@
1
- from __future__ import annotations
2
1
  """Main SessionTracer class for tracing v3."""
3
2
 
3
+ from __future__ import annotations
4
+
4
5
  import asyncio
6
+ import json
5
7
  from contextlib import asynccontextmanager
6
- from datetime import datetime
8
+ from datetime import UTC, datetime
7
9
  from typing import Any
8
10
 
9
11
  from .abstractions import (
10
12
  BaseEvent,
11
13
  SessionEventMarkovBlanketMessage,
14
+ SessionMessageContent,
12
15
  SessionTimeStep,
13
16
  SessionTrace,
14
17
  TimeRecord,
@@ -16,7 +19,9 @@ from .abstractions import (
16
19
  from .config import CONFIG
17
20
  from .decorators import set_session_id, set_session_tracer, set_turn_number
18
21
  from .hooks import GLOBAL_HOOKS, HookManager
19
- from .turso.manager import AsyncSQLTraceManager
22
+ from .storage.base import TraceStorage
23
+ from .storage.config import StorageConfig
24
+ from .storage.factory import create_storage
20
25
  from .utils import generate_session_id
21
26
 
22
27
 
@@ -28,6 +33,8 @@ class SessionTracer:
28
33
  hooks: HookManager | None = None,
29
34
  db_url: str | None = None,
30
35
  auto_save: bool = True,
36
+ storage: TraceStorage | None = None,
37
+ storage_config: StorageConfig | None = None,
31
38
  ):
32
39
  """Initialize session tracer.
33
40
 
@@ -40,7 +47,8 @@ class SessionTracer:
40
47
  self._current_trace: SessionTrace | None = None
41
48
  self._lock = asyncio.Lock()
42
49
  self.db_url = db_url or CONFIG.db_url
43
- self.db: AsyncSQLTraceManager | None = None
50
+ self._storage_config = storage_config
51
+ self.db: TraceStorage | None = storage
44
52
  self.auto_save = auto_save
45
53
  self._current_step: SessionTimeStep | None = None
46
54
 
@@ -57,7 +65,8 @@ class SessionTracer:
57
65
  async def initialize(self):
58
66
  """Initialize the database connection."""
59
67
  if self.db is None:
60
- self.db = AsyncSQLTraceManager(self.db_url)
68
+ config = self._storage_config or StorageConfig(connection_string=self.db_url)
69
+ self.db = create_storage(config)
61
70
  await self.db.initialize()
62
71
 
63
72
  async def start_session(
@@ -97,7 +106,7 @@ class SessionTracer:
97
106
 
98
107
  self._current_trace = SessionTrace(
99
108
  session_id=session_id,
100
- created_at=datetime.utcnow(),
109
+ created_at=datetime.now(UTC),
101
110
  session_time_steps=[],
102
111
  event_history=[],
103
112
  markov_blanket_message_history=[],
@@ -110,7 +119,9 @@ class SessionTracer:
110
119
 
111
120
  # Ensure session row exists for incremental writes
112
121
  if self.db:
113
- await self.db.ensure_session(session_id, created_at=self._current_trace.created_at, metadata=metadata or {})
122
+ await self.db.ensure_session(
123
+ session_id, created_at=self._current_trace.created_at, metadata=metadata or {}
124
+ )
114
125
 
115
126
  # Trigger hooks
116
127
  await self.hooks.trigger(
@@ -141,7 +152,7 @@ class SessionTracer:
141
152
  step = SessionTimeStep(
142
153
  step_id=step_id,
143
154
  step_index=len(self._current_trace.session_time_steps),
144
- timestamp=datetime.utcnow(),
155
+ timestamp=datetime.now(UTC),
145
156
  turn_number=turn_number,
146
157
  step_metadata=metadata or {},
147
158
  )
@@ -186,7 +197,7 @@ class SessionTracer:
186
197
  step = self._current_step
187
198
 
188
199
  if step and step.completed_at is None:
189
- step.completed_at = datetime.utcnow()
200
+ step.completed_at = datetime.now(UTC)
190
201
 
191
202
  # Trigger hooks
192
203
  await self.hooks.trigger(
@@ -259,7 +270,7 @@ class SessionTracer:
259
270
 
260
271
  async def record_message(
261
272
  self,
262
- content: str,
273
+ content: Any,
263
274
  message_type: str,
264
275
  event_time: float | None = None,
265
276
  message_time: int | None = None,
@@ -277,11 +288,13 @@ class SessionTracer:
277
288
  if self._current_trace is None:
278
289
  raise RuntimeError("No active session")
279
290
 
291
+ normalised_content, content_str = self._normalise_message_content(content)
292
+
280
293
  msg = SessionEventMarkovBlanketMessage(
281
- content=content,
294
+ content=normalised_content,
282
295
  message_type=message_type,
283
296
  time_record=TimeRecord(
284
- event_time=event_time or datetime.utcnow().timestamp(), message_time=message_time
297
+ event_time=event_time or datetime.now(UTC).timestamp(), message_time=message_time
285
298
  ),
286
299
  metadata=metadata or {},
287
300
  )
@@ -315,7 +328,7 @@ class SessionTracer:
315
328
  self._current_trace.session_id,
316
329
  timestep_db_id=timestep_db_id,
317
330
  message_type=message_type,
318
- content=content,
331
+ content=content_str,
319
332
  event_time=msg.time_record.event_time,
320
333
  message_time=msg.time_record.message_time,
321
334
  metadata=msg.metadata,
@@ -323,6 +336,22 @@ class SessionTracer:
323
336
  return message_id
324
337
  return None
325
338
 
339
+ @staticmethod
340
+ def _normalise_message_content(content: Any) -> tuple[SessionMessageContent, str]:
341
+ if isinstance(content, SessionMessageContent):
342
+ return content, content.as_text()
343
+ if isinstance(content, str):
344
+ payload = SessionMessageContent(text=content)
345
+ return payload, payload.as_text()
346
+ try:
347
+ serialized = json.dumps(content, ensure_ascii=False)
348
+ payload = SessionMessageContent(json_payload=serialized)
349
+ return payload, serialized
350
+ except (TypeError, ValueError):
351
+ text = str(content)
352
+ payload = SessionMessageContent(text=text)
353
+ return payload, text
354
+
326
355
  async def end_session(self, save: bool | None = None) -> SessionTrace:
327
356
  """End the current session.
328
357
 
@@ -339,7 +368,7 @@ class SessionTracer:
339
368
  # End any open timesteps
340
369
  for step in self._current_trace.session_time_steps:
341
370
  if step.completed_at is None:
342
- step.completed_at = datetime.utcnow()
371
+ step.completed_at = datetime.now(UTC)
343
372
 
344
373
  # Trigger pre-save hooks
345
374
  await self.hooks.trigger("before_save", session=self._current_trace)
@@ -435,7 +464,14 @@ class SessionTracer:
435
464
  # Reward recording helpers
436
465
  # -------------------------------
437
466
 
438
- async def record_outcome_reward(self, *, total_reward: int, achievements_count: int, total_steps: int, reward_metadata: dict[str, Any] | None = None) -> int | None:
467
+ async def record_outcome_reward(
468
+ self,
469
+ *,
470
+ total_reward: int,
471
+ achievements_count: int,
472
+ total_steps: int,
473
+ reward_metadata: dict[str, Any] | None = None,
474
+ ) -> int | None:
439
475
  """Record an episode-level outcome reward for the current session."""
440
476
  if self._current_trace is None:
441
477
  raise RuntimeError("No active session")
@@ -462,7 +498,18 @@ class SessionTracer:
462
498
 
463
499
  # StepMetrics removed in favor of event_rewards; use record_event_reward for per-turn shaped values
464
500
 
465
- async def record_event_reward(self, *, event_id: int, message_id: int | None = None, turn_number: int | None = None, reward_value: float = 0.0, reward_type: str | None = None, key: str | None = None, annotation: dict[str, Any] | None = None, source: str | None = None) -> int | None:
501
+ async def record_event_reward(
502
+ self,
503
+ *,
504
+ event_id: int,
505
+ message_id: int | None = None,
506
+ turn_number: int | None = None,
507
+ reward_value: float = 0.0,
508
+ reward_type: str | None = None,
509
+ key: str | None = None,
510
+ annotation: dict[str, Any] | None = None,
511
+ source: str | None = None,
512
+ ) -> int | None:
466
513
  """Record a first-class event-level reward with optional annotations."""
467
514
  if self._current_trace is None:
468
515
  raise RuntimeError("No active session")
@@ -54,7 +54,10 @@ class TraceStorage(ABC):
54
54
 
55
55
  @abstractmethod
56
56
  async def get_model_usage(
57
- self, start_date: datetime | None = None, end_date: datetime | None = None, model_name: str | None = None
57
+ self,
58
+ start_date: datetime | None = None,
59
+ end_date: datetime | None = None,
60
+ model_name: str | None = None,
58
61
  ) -> Any:
59
62
  """Get model usage statistics.
60
63
 
@@ -85,6 +88,91 @@ class TraceStorage(ABC):
85
88
  """Close the storage connection."""
86
89
  pass
87
90
 
91
+ # Incremental helpers -------------------------------------------------
92
+
93
+ @abstractmethod
94
+ async def ensure_session(
95
+ self,
96
+ session_id: str,
97
+ *,
98
+ created_at: datetime | None = None,
99
+ metadata: dict[str, Any] | None = None,
100
+ ) -> None:
101
+ """Ensure a session row exists for the given session id."""
102
+ pass
103
+
104
+ @abstractmethod
105
+ async def ensure_timestep(
106
+ self,
107
+ session_id: str,
108
+ *,
109
+ step_id: str,
110
+ step_index: int,
111
+ turn_number: int | None = None,
112
+ started_at: datetime | None = None,
113
+ completed_at: datetime | None = None,
114
+ metadata: dict[str, Any] | None = None,
115
+ ) -> int:
116
+ """Ensure a timestep row exists and return its database id."""
117
+ pass
118
+
119
+ @abstractmethod
120
+ async def insert_event_row(
121
+ self,
122
+ session_id: str,
123
+ *,
124
+ timestep_db_id: int | None,
125
+ event: Any,
126
+ metadata_override: dict[str, Any] | None = None,
127
+ ) -> int:
128
+ """Insert an event and return its database id."""
129
+ pass
130
+
131
+ @abstractmethod
132
+ async def insert_message_row(
133
+ self,
134
+ session_id: str,
135
+ *,
136
+ timestep_db_id: int | None,
137
+ message_type: str,
138
+ content: Any,
139
+ event_time: float | None = None,
140
+ message_time: int | None = None,
141
+ metadata: dict[str, Any] | None = None,
142
+ ) -> int:
143
+ """Insert a message row linked to a session/timestep."""
144
+ pass
145
+
146
+ @abstractmethod
147
+ async def insert_outcome_reward(
148
+ self,
149
+ session_id: str,
150
+ *,
151
+ total_reward: int,
152
+ achievements_count: int,
153
+ total_steps: int,
154
+ reward_metadata: dict | None = None,
155
+ ) -> int:
156
+ """Record an outcome reward for a session."""
157
+ pass
158
+
159
+ @abstractmethod
160
+ async def insert_event_reward(
161
+ self,
162
+ session_id: str,
163
+ *,
164
+ event_id: int,
165
+ message_id: int | None = None,
166
+ turn_number: int | None = None,
167
+ reward_value: float = 0.0,
168
+ reward_type: str | None = None,
169
+ key: str | None = None,
170
+ annotation: dict[str, Any] | None = None,
171
+ source: str | None = None,
172
+ ) -> int:
173
+ """Record a reward tied to a specific event."""
174
+ pass
175
+
88
176
  # Optional experiment management methods
89
177
  async def create_experiment(
90
178
  self,
@@ -9,16 +9,22 @@ from typing import Any
9
9
  class StorageBackend(str, Enum):
10
10
  """Supported storage backends."""
11
11
 
12
- TURSO = "turso"
12
+ TURSO_NATIVE = "turso_native"
13
13
  SQLITE = "sqlite"
14
14
  POSTGRES = "postgres" # Future support
15
15
 
16
16
 
17
+ def _is_enabled(value: str | None) -> bool:
18
+ if value is None:
19
+ return False
20
+ return value.lower() in {"1", "true", "yes", "on"}
21
+
22
+
17
23
  @dataclass
18
24
  class StorageConfig:
19
25
  """Configuration for storage backend."""
20
26
 
21
- backend: StorageBackend = StorageBackend.TURSO
27
+ backend: StorageBackend = StorageBackend.TURSO_NATIVE
22
28
  connection_string: str | None = None
23
29
 
24
30
  # Turso-specific settings
@@ -34,23 +40,30 @@ class StorageConfig:
34
40
  enable_compression: bool = os.getenv("STORAGE_COMPRESSION", "false").lower() == "true"
35
41
  max_content_length: int = int(os.getenv("STORAGE_MAX_CONTENT_LENGTH", "1000000")) # 1MB
36
42
 
43
+ def __post_init__(self):
44
+ # Allow legacy override while keeping compatibility with existing TURSO_NATIVE env flag
45
+ native_env = os.getenv("TURSO_NATIVE")
46
+ native_flag = _is_enabled(native_env) if native_env is not None else None
47
+
48
+ if native_flag is False:
49
+ self.backend = StorageBackend.SQLITE
50
+
37
51
  def get_connection_string(self) -> str:
38
52
  """Get the appropriate connection string for the backend."""
39
53
  if self.connection_string:
40
54
  return self.connection_string
41
55
 
42
- if self.backend == StorageBackend.TURSO:
56
+ if self.backend == StorageBackend.TURSO_NATIVE:
43
57
  return self.turso_url
44
- elif self.backend == StorageBackend.SQLITE:
58
+ if self.backend == StorageBackend.SQLITE:
45
59
  return "sqlite+aiosqlite:///traces.db"
46
- elif self.backend == StorageBackend.POSTGRES:
60
+ if self.backend == StorageBackend.POSTGRES:
47
61
  return os.getenv("POSTGRES_URL", "postgresql+asyncpg://localhost/traces")
48
- else:
49
- raise ValueError(f"Unknown backend: {self.backend}")
62
+ raise ValueError(f"Unknown backend: {self.backend}")
50
63
 
51
64
  def get_backend_config(self) -> dict[str, Any]:
52
65
  """Get backend-specific configuration."""
53
- if self.backend == StorageBackend.TURSO:
66
+ if self.backend == StorageBackend.TURSO_NATIVE:
54
67
  config = {}
55
68
  if self.turso_auth_token:
56
69
  config["auth_token"] = self.turso_auth_token