synth-ai 0.1.9__py3-none-any.whl → 0.2.1.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. synth_ai/__init__.py +28 -2
  2. synth_ai/core/system.py +4 -0
  3. synth_ai/environments/__init__.py +35 -0
  4. synth_ai/environments/environment/__init__.py +1 -0
  5. synth_ai/environments/environment/artifacts/__init__.py +1 -0
  6. synth_ai/environments/environment/artifacts/base.py +50 -0
  7. synth_ai/environments/environment/core.py +22 -0
  8. synth_ai/environments/environment/db/__init__.py +1 -0
  9. synth_ai/environments/environment/db/sqlite.py +45 -0
  10. synth_ai/environments/environment/registry.py +24 -0
  11. synth_ai/environments/environment/resources/sqlite.py +46 -0
  12. synth_ai/environments/environment/results.py +1 -0
  13. synth_ai/environments/environment/rewards/__init__.py +1 -0
  14. synth_ai/environments/environment/rewards/core.py +28 -0
  15. synth_ai/environments/environment/shared_engine.py +26 -0
  16. synth_ai/environments/environment/tools/__init__.py +34 -0
  17. synth_ai/environments/examples/__init__.py +1 -0
  18. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  19. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
  20. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
  26. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  27. synth_ai/environments/examples/crafter_classic/engine.py +502 -0
  28. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  29. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  30. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  31. synth_ai/environments/examples/crafter_classic/environment.py +255 -0
  32. synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
  33. synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
  34. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  35. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  36. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  37. synth_ai/environments/examples/enron/engine.py +291 -0
  38. synth_ai/environments/examples/enron/environment.py +165 -0
  39. synth_ai/environments/examples/enron/taskset.py +112 -0
  40. synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
  41. synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
  42. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  43. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  44. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
  45. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  46. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
  47. synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
  48. synth_ai/environments/examples/minigrid/engine.py +589 -0
  49. synth_ai/environments/examples/minigrid/environment.py +274 -0
  50. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  51. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  52. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  53. synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
  54. synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
  55. synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
  56. synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
  57. synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
  58. synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
  59. synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
  60. synth_ai/environments/examples/nethack/__init__.py +7 -0
  61. synth_ai/environments/examples/nethack/achievements.py +337 -0
  62. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  63. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  64. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
  65. synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
  66. synth_ai/environments/examples/nethack/engine.py +738 -0
  67. synth_ai/environments/examples/nethack/environment.py +255 -0
  68. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  69. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  70. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  71. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  72. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  73. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  74. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  75. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  76. synth_ai/environments/examples/nethack/taskset.py +323 -0
  77. synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
  78. synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
  79. synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
  80. synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
  81. synth_ai/environments/examples/red/__init__.py +7 -0
  82. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  83. synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
  84. synth_ai/environments/examples/red/config_logging.py +110 -0
  85. synth_ai/environments/examples/red/engine.py +693 -0
  86. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  87. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  88. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  89. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  90. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  91. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  92. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  93. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  94. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  95. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  96. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  97. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  98. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  99. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  100. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  101. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  102. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  103. synth_ai/environments/examples/red/environment.py +235 -0
  104. synth_ai/environments/examples/red/taskset.py +77 -0
  105. synth_ai/environments/examples/red/test_fixes.py +125 -0
  106. synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
  107. synth_ai/environments/examples/red/units/__init__.py +1 -0
  108. synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
  109. synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
  110. synth_ai/environments/examples/red/units/test_engine.py +192 -0
  111. synth_ai/environments/examples/red/units/test_environment.py +455 -0
  112. synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
  113. synth_ai/environments/examples/red/units/test_integration.py +217 -0
  114. synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
  115. synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
  116. synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
  117. synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
  118. synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
  119. synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
  120. synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
  121. synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
  122. synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
  123. synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
  124. synth_ai/environments/examples/red/units/test_taskset.py +116 -0
  125. synth_ai/environments/examples/red/units/test_tree.py +448 -0
  126. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  127. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
  128. synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
  129. synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
  130. synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
  131. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
  132. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
  133. synth_ai/environments/examples/sokoban/engine.py +675 -0
  134. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  135. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  136. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  137. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  138. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  139. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  140. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  141. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  142. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  143. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  144. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  145. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  146. synth_ai/environments/examples/sokoban/environment.py +228 -0
  147. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  148. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  149. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  150. synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
  151. synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
  152. synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
  153. synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
  154. synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
  155. synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
  156. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  157. synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
  158. synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
  159. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  160. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  161. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  162. synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
  163. synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
  164. synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
  165. synth_ai/environments/examples/verilog/__init__.py +10 -0
  166. synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
  167. synth_ai/environments/examples/verilog/engine.py +328 -0
  168. synth_ai/environments/examples/verilog/environment.py +349 -0
  169. synth_ai/environments/examples/verilog/taskset.py +418 -0
  170. synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
  171. synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
  172. synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
  173. synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
  174. synth_ai/environments/reproducibility/core.py +42 -0
  175. synth_ai/environments/reproducibility/tree.py +364 -0
  176. synth_ai/environments/service/app.py +78 -0
  177. synth_ai/environments/service/core_routes.py +775 -0
  178. synth_ai/environments/service/external_registry.py +57 -0
  179. synth_ai/environments/service/registry.py +9 -0
  180. synth_ai/environments/stateful/__init__.py +1 -0
  181. synth_ai/environments/stateful/core.py +28 -0
  182. synth_ai/environments/stateful/engine.py +21 -0
  183. synth_ai/environments/stateful/state.py +7 -0
  184. synth_ai/environments/tasks/api.py +19 -0
  185. synth_ai/environments/tasks/core.py +78 -0
  186. synth_ai/environments/tasks/filters.py +39 -0
  187. synth_ai/environments/tasks/utils.py +89 -0
  188. synth_ai/environments/v0_observability/history.py +3 -0
  189. synth_ai/environments/v0_observability/log.py +2 -0
  190. synth_ai/lm/caching/constants.py +1 -0
  191. synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
  192. synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
  193. synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
  194. synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
  195. synth_ai/{zyk/lms → lm}/config.py +2 -1
  196. synth_ai/{zyk/lms → lm}/constants.py +2 -2
  197. synth_ai/{zyk/lms → lm}/core/all.py +10 -10
  198. synth_ai/{zyk/lms → lm}/core/main.py +57 -33
  199. synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
  200. synth_ai/lm/cost/monitor.py +1 -0
  201. synth_ai/lm/cost/statefulness.py +1 -0
  202. synth_ai/lm/provider_support/__init__.py +8 -0
  203. synth_ai/lm/provider_support/anthropic.py +945 -0
  204. synth_ai/lm/provider_support/openai.py +1115 -0
  205. synth_ai/lm/provider_support/suppress_logging.py +31 -0
  206. synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
  207. synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
  208. synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
  209. synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
  210. synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +37 -32
  211. synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
  212. synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
  213. synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
  214. synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
  215. synth_ai/lm/vendors/supported/__init__.py +0 -0
  216. synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
  217. synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
  218. synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
  219. synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
  220. synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
  221. synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
  222. synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
  223. synth_ai/tracing/__init__.py +0 -0
  224. synth_ai/tracing/abstractions.py +224 -0
  225. synth_ai/tracing/base_client.py +91 -0
  226. synth_ai/tracing/client_manager.py +131 -0
  227. synth_ai/tracing/config.py +140 -0
  228. synth_ai/tracing/context.py +146 -0
  229. synth_ai/tracing/decorators.py +679 -0
  230. synth_ai/tracing/events/__init__.py +0 -0
  231. synth_ai/tracing/events/manage.py +147 -0
  232. synth_ai/tracing/events/scope.py +86 -0
  233. synth_ai/tracing/events/store.py +227 -0
  234. synth_ai/tracing/immediate_client.py +152 -0
  235. synth_ai/tracing/local.py +18 -0
  236. synth_ai/tracing/log_client_base.py +74 -0
  237. synth_ai/tracing/retry_queue.py +187 -0
  238. synth_ai/tracing/trackers.py +515 -0
  239. synth_ai/tracing/upload.py +504 -0
  240. synth_ai/tracing/utils.py +9 -0
  241. synth_ai/zyk/__init__.py +28 -2
  242. synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
  243. synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
  244. synth_ai/zyk/lms/caching/constants.py +0 -1
  245. synth_ai/zyk/lms/cost/monitor.py +0 -1
  246. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  247. synth_ai-0.1.9.dist-info/METADATA +0 -37
  248. synth_ai-0.1.9.dist-info/RECORD +0 -50
  249. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  250. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  251. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  253. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  254. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  255. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  256. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  259. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  260. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  261. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  264. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +0 -0
  265. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/licenses/LICENSE +0 -0
  266. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
1
+ """
2
+ NOTE - first pass was o3-generated. Mostly bc idrk what I 'want' from this yet ...
3
+ trajectory_tree_store.py
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~
5
+ A minimal search-tree wrapper that pairs
6
+
7
+ • an *in-memory* NetworkX DiGraph (parent ⇢ children edges)
8
+ • a *content-addressable* FilesystemSnapshotStore (heavy blobs)
9
+
10
+ so you can implement things like LATS / MCTS without bringing in the
11
+ big “backend.production” code-base.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import sqlite3
18
+ import gzip
19
+ import pickle
20
+ import logging
21
+ from pathlib import Path
22
+ from typing import Any, Dict, Optional, Tuple, Iterable
23
+
24
+ import networkx as nx
25
+
26
+ # from filesystem_snapshot_store import FilesystemSnapshotStore # ← your re-impl
27
+
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ # --------------------------------------------------------------------------- #
32
+ # lightweight metadata record #
33
+ # --------------------------------------------------------------------------- #
34
+ import os
35
+ import hashlib
36
+ import logging
37
+ from typing import Union
38
+
39
+ log = logging.getLogger(__name__)
40
+
41
+ # Default directory for storing snapshots relative to some base path
42
+ # This could be configured via environment variables or settings later.
43
+ DEFAULT_SNAPSHOT_DIR = Path(os.getenv("SNAPSHOT_STORE_PATH", "/tmp/agent_snapshots"))
44
+
45
+
46
+ class FilesystemSnapshotStore:
47
+ """
48
+ Stores and retrieves environment state snapshots on the filesystem.
49
+
50
+ Uses content-addressable storage: the key (ID) for a snapshot
51
+ is the SHA-256 hash of its compressed content.
52
+ """
53
+
54
+ def __init__(self, base_dir: Union[str, Path] = DEFAULT_SNAPSHOT_DIR):
55
+ self.base_dir = Path(base_dir)
56
+ try:
57
+ self.base_dir.mkdir(parents=True, exist_ok=True)
58
+ log.info(f"Initialized snapshot store at: {self.base_dir}")
59
+ except OSError as e:
60
+ log.error(
61
+ f"Failed to create snapshot directory {self.base_dir}: {e}",
62
+ exc_info=True,
63
+ )
64
+ raise
65
+
66
+ def _get_path(self, key: str) -> Path:
67
+ """Constructs the full path for a given snapshot key."""
68
+ # Maybe add subdirectories later for large numbers of files, e.g., key[:2]/key[2:]
69
+ filename = f"{key}.snapshot.gz"
70
+ return self.base_dir / filename
71
+
72
+ def write(self, blob: Union[bytes, Dict[str, Any]]) -> str:
73
+ """
74
+ Stores a snapshot blob (bytes or dict) and returns its SHA-256 key.
75
+
76
+ • Dicts → pickle → gzip
77
+ • Bytes already gzip-compressed (magic 0x1f 0x8b) are stored as-is
78
+ to avoid double compression.
79
+ """
80
+ try:
81
+ if isinstance(blob, dict):
82
+ compressed_blob = gzip.compress(pickle.dumps(blob))
83
+ elif isinstance(blob, bytes):
84
+ # Skip re-compression if data is already gzipped
85
+ compressed_blob = blob if blob[:2] == b"\x1f\x8b" else gzip.compress(blob)
86
+ else:
87
+ raise TypeError(f"Unsupported blob type for snapshot store: {type(blob)}")
88
+
89
+ key = hashlib.sha256(compressed_blob).hexdigest()
90
+ path = self._get_path(key)
91
+ if not path.exists():
92
+ path.write_bytes(compressed_blob)
93
+ return key
94
+ except Exception as e:
95
+ log.error(f"Failed to write snapshot: {e}", exc_info=True)
96
+ raise
97
+
98
+ def read(self, key: str) -> Optional[bytes]:
99
+ """
100
+ Retrieves the raw *compressed* snapshot bytes for a given key.
101
+
102
+ Returns None if the key is not found.
103
+ Deserialization (decompression, unpickling) is the responsibility
104
+ of the caller (e.g., ReproducibleResource.from_snapshot).
105
+ """
106
+ filepath = self._get_path(key)
107
+ if not filepath.exists():
108
+ log.warning(f"Snapshot key not found: {key}")
109
+ return None
110
+ try:
111
+ with open(filepath, "rb") as f:
112
+ compressed_blob = f.read()
113
+ return compressed_blob
114
+ except OSError as e:
115
+ log.error(f"Failed to read snapshot {key} from {filepath}: {e}", exc_info=True)
116
+ return None # Or re-raise? Returning None might be safer.
117
+
118
+ def exists(self, key: str) -> bool:
119
+ """Checks if a snapshot with the given key exists."""
120
+ return self._get_path(key).exists()
121
+
122
+
123
+ # Global instance (optional, could use dependency injection)
124
+ # snapshot_store = FilesystemSnapshotStore()
125
+
126
+
127
+ class TrajectorySnapshot:
128
+ """
129
+ A *metadata* header for one node in the search tree.
130
+ The heavy serialized-state bytes live only in the snapshot store.
131
+ """
132
+
133
+ __slots__ = (
134
+ "snap_id",
135
+ "parent_id",
136
+ "depth",
137
+ "action",
138
+ "reward",
139
+ "terminated",
140
+ "info",
141
+ )
142
+
143
+ def __init__(
144
+ self,
145
+ snap_id: str,
146
+ parent_id: Optional[str],
147
+ depth: int,
148
+ action: Optional[Any],
149
+ reward: float = 0.0,
150
+ terminated: bool = False,
151
+ info: Optional[Dict[str, Any]] = None,
152
+ ):
153
+ self.snap_id = snap_id
154
+ self.parent_id = parent_id
155
+ self.depth = depth
156
+ self.action = action
157
+ self.reward = reward
158
+ self.terminated = bool(terminated)
159
+ self.info = info or {}
160
+
161
+ # helpful for printing / debugging
162
+ def __repr__(self) -> str: # pragma: no cover
163
+ a = json.dumps(self.action) if self.action is not None else "∅"
164
+ return (
165
+ f"TrajSnap(id={self.snap_id[:7]}…, depth={self.depth}, "
166
+ f"action={a}, reward={self.reward}, term={self.terminated})"
167
+ )
168
+
169
+
170
+ # --------------------------------------------------------------------------- #
171
+ # tree manager #
172
+ # --------------------------------------------------------------------------- #
173
+
174
+
175
+ class TrajectoryTreeStore:
176
+ """
177
+ ❑ Adds snapshots (root / children) and keeps the DAG in-memory
178
+ ❑ Optionally mirrors headers to a tiny SQLite DB (so you can kill +
179
+ resume a long search)
180
+ ❑ Hands out raw snapshot **bytes**; decoding is up to the caller.
181
+ """
182
+
183
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
184
+ # construction #
185
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
186
+
187
+ def __init__(
188
+ self,
189
+ snapshot_store: Optional[FilesystemSnapshotStore] = None,
190
+ *,
191
+ db_path: Optional[Path | str] = None,
192
+ ):
193
+ self.snap_store = snapshot_store or FilesystemSnapshotStore()
194
+ self.graph: nx.DiGraph = nx.DiGraph()
195
+ self.db_path = Path(db_path).expanduser() if db_path else None
196
+ if self.db_path:
197
+ self._init_sqlite()
198
+
199
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
200
+ # public API #
201
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
202
+
203
+ # insertion -------------------------------------------------------------
204
+
205
+ def add_root(self, snapshot_blob: bytes, *, info: Dict[str, Any] | None = None) -> str:
206
+ """Insert the very first node and return its content-hash key."""
207
+ snap_id = self.snap_store.write(snapshot_blob)
208
+ self._add_node(TrajectorySnapshot(snap_id, None, 0, None, 0.0, False, info))
209
+ return snap_id
210
+
211
+ def add_child(
212
+ self,
213
+ parent_id: str,
214
+ snapshot_blob: bytes,
215
+ *,
216
+ action: Any,
217
+ reward: float,
218
+ terminated: bool = False,
219
+ info: Dict[str, Any] | None = None,
220
+ ) -> str:
221
+ """Attach `snapshot_blob` as a child reached by `action` from *parent_id*."""
222
+ if parent_id not in self.graph:
223
+ raise KeyError(f"Parent snapshot {parent_id[:8]}… not in tree")
224
+ depth = self.graph.nodes[parent_id]["meta"].depth + 1 # type: ignore[index]
225
+ snap_id = self.snap_store.write(snapshot_blob)
226
+ meta = TrajectorySnapshot(snap_id, parent_id, depth, action, reward, terminated, info)
227
+ self._add_node(meta) # records node + (maybe) SQLite
228
+ self.graph.add_edge(parent_id, snap_id, action=action, reward=reward) # NX edge attrs
229
+ return snap_id
230
+
231
+ # read-side helpers -----------------------------------------------------
232
+
233
+ def get_children(self, snap_id: str) -> Tuple[str, ...]:
234
+ return tuple(self.graph.successors(snap_id))
235
+
236
+ def get_parent(self, snap_id: str) -> Optional[str]:
237
+ preds = tuple(self.graph.predecessors(snap_id))
238
+ return preds[0] if preds else None
239
+
240
+ def is_leaf(self, snap_id: str) -> bool:
241
+ return self.graph.out_degree(snap_id) == 0
242
+
243
+ # simple enumerations useful for MCTS / LATS ---------------------------
244
+
245
+ def iter_leaves(self) -> Iterable[str]:
246
+ """Yield snapshot-ids that currently have no children."""
247
+ return (n for n in self.graph.nodes if self.is_leaf(n))
248
+
249
+ def path_to_root(self, snap_id: str) -> Tuple[str, ...]:
250
+ """Return (snap_id, …, root_id)"""
251
+ path = [snap_id]
252
+ while (p := self.get_parent(path[-1])) is not None:
253
+ path.append(p)
254
+ return tuple(path)
255
+
256
+ def reconstruct_actions(self, snap_id: str) -> Tuple[Any, ...]:
257
+ """Return the sequence of *actions* from the root → `snap_id`."""
258
+ actions = []
259
+ for child, parent in zip(self.path_to_root(snap_id)[:-1], self.path_to_root(snap_id)[1:]):
260
+ actions.append(self.graph.edges[parent, child]["action"])
261
+ return tuple(reversed(actions))
262
+
263
+ # snapshot access -------------------------------------------------------
264
+
265
+ def load_snapshot_blob(self, snap_id: str) -> bytes:
266
+ blob = self.snap_store.read(snap_id)
267
+ if blob is None:
268
+ raise FileNotFoundError(f"Snapshot {snap_id[:8]}… missing on disk")
269
+ return blob
270
+
271
+ def load_pickled_payload(self, snap_id: str) -> Any:
272
+ """Decompress + unpickle whatever you stored under this id."""
273
+ return pickle.loads(gzip.decompress(self.load_snapshot_blob(snap_id)))
274
+
275
+ # mutation --------------------------------------------------------------
276
+
277
+ def prune_subtree(self, root_id: str) -> None:
278
+ """Remove *root_id* and all its descendants from the in-mem graph and DB."""
279
+ doomed = list(nx.dfs_preorder_nodes(self.graph, root_id))
280
+ self.graph.remove_nodes_from(doomed)
281
+ if self.db_path:
282
+ with sqlite3.connect(self.db_path) as conn:
283
+ conn.executemany("DELETE FROM nodes WHERE snap_id = ?;", ((n,) for n in doomed))
284
+ conn.executemany(
285
+ "DELETE FROM edges WHERE parent_id = ? OR child_id = ?;",
286
+ ((n, n) for n in doomed),
287
+ )
288
+ conn.commit()
289
+
290
+ def wipe(self) -> None:
291
+ """Clear the *entire* tree (does **not** delete snapshot files)."""
292
+ self.graph.clear()
293
+ if self.db_path:
294
+ with sqlite3.connect(self.db_path) as conn:
295
+ conn.executescript("DELETE FROM nodes; DELETE FROM edges;")
296
+ conn.commit()
297
+
298
+ # ------------------------------------------------------------------- #
299
+ # internal helpers #
300
+ # ------------------------------------------------------------------- #
301
+
302
+ def _add_node(self, meta: TrajectorySnapshot) -> None:
303
+ self.graph.add_node(meta.snap_id, meta=meta)
304
+ if self.db_path:
305
+ self._sqlite_insert(meta)
306
+
307
+ # ------------------------------------------------------------------- #
308
+ # tiny SQLite backing store (optional) #
309
+ # ------------------------------------------------------------------- #
310
+
311
+ def _init_sqlite(self) -> None:
312
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
313
+ with sqlite3.connect(self.db_path) as conn:
314
+ conn.executescript(
315
+ """
316
+ CREATE TABLE IF NOT EXISTS nodes(
317
+ snap_id TEXT PRIMARY KEY,
318
+ parent_id TEXT,
319
+ depth INTEGER,
320
+ action TEXT,
321
+ reward REAL,
322
+ terminated INTEGER,
323
+ info TEXT
324
+ );
325
+ CREATE TABLE IF NOT EXISTS edges(
326
+ parent_id TEXT,
327
+ child_id TEXT,
328
+ action TEXT,
329
+ reward REAL,
330
+ PRIMARY KEY(parent_id, child_id)
331
+ );
332
+ """
333
+ )
334
+ conn.commit()
335
+
336
+ def _sqlite_insert(self, meta: TrajectorySnapshot) -> None:
337
+ with sqlite3.connect(self.db_path) as conn:
338
+ conn.execute(
339
+ """INSERT OR IGNORE INTO nodes
340
+ (snap_id, parent_id, depth, action, reward, terminated, info)
341
+ VALUES (?,?,?,?,?,?,?)""",
342
+ (
343
+ meta.snap_id,
344
+ meta.parent_id,
345
+ meta.depth,
346
+ json.dumps(meta.action),
347
+ meta.reward,
348
+ int(meta.terminated),
349
+ json.dumps(meta.info),
350
+ ),
351
+ )
352
+ if meta.parent_id:
353
+ conn.execute(
354
+ """INSERT OR IGNORE INTO edges
355
+ (parent_id, child_id, action, reward)
356
+ VALUES (?,?,?,?)""",
357
+ (
358
+ meta.parent_id,
359
+ meta.snap_id,
360
+ json.dumps(meta.action),
361
+ meta.reward,
362
+ ),
363
+ )
364
+ conn.commit()
@@ -0,0 +1,78 @@
1
+ import sys
2
+ import os # Added to ensure os is available before use
3
+
4
+ # Ensure local 'src' directory is on PYTHONPATH for dev installs
5
+ # Current file: <repo>/src/synth_env/service/app.py
6
+ # We want to add <repo>/src to sys.path (two levels up)
7
+ _src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
8
+ if _src_dir not in sys.path:
9
+ sys.path.insert(0, _src_dir)
10
+
11
+ print(f"SYS.PATH IN APP.PY: {sys.path}")
12
+ import logging
13
+
14
+ from fastapi import FastAPI
15
+ from synth_ai.environments.service.registry import list_supported_env_types, register_environment
16
+ from synth_ai.environments.service.core_routes import api_router
17
+ from synth_ai.environments.service.external_registry import (
18
+ ExternalRegistryConfig,
19
+ load_external_environments,
20
+ )
21
+
22
+ # Configure logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Register built-in environments at import time
27
+ import synth_ai.environments.examples.sokoban.environment as sok
28
+
29
+ register_environment("Sokoban", sok.SokobanEnvironment)
30
+ import synth_ai.environments.examples.crafter_classic.environment as cc
31
+
32
+ register_environment("CrafterClassic", cc.CrafterClassicEnvironment)
33
+ import synth_ai.environments.examples.verilog.environment as ve
34
+
35
+ register_environment("Verilog", ve.VerilogEnvironment)
36
+ import synth_ai.environments.examples.tictactoe.environment as ttt
37
+
38
+ register_environment("TicTacToe", ttt.TicTacToeEnvironment)
39
+ import synth_ai.environments.examples.nethack.environment as nh
40
+
41
+ register_environment("NetHack", nh.NetHackEnvironment)
42
+ # AlgoTune excluded from package due to size/complexity
43
+ # import synth_ai.environments.examples.algotune.environment as at
44
+ # register_environment("AlgoTune", at.AlgoTuneEnvironment)
45
+ import synth_ai.environments.examples.minigrid.environment as mg
46
+
47
+ register_environment("MiniGrid", mg.MiniGridEnvironment)
48
+ import synth_ai.environments.examples.enron.environment as enron
49
+
50
+ register_environment("Enron", enron.EnronEnvironment)
51
+
52
+ app = FastAPI(title="Environment Service")
53
+
54
+
55
+ @app.on_event("startup")
56
+ async def startup_event():
57
+ """Load external environments on startup."""
58
+ # Support configuration-based loading for external environments
59
+ # You can set EXTERNAL_ENVIRONMENTS env var with JSON config
60
+ external_config = os.getenv("EXTERNAL_ENVIRONMENTS")
61
+ if external_config:
62
+ try:
63
+ import json
64
+
65
+ config_data = json.loads(external_config)
66
+ config = ExternalRegistryConfig(
67
+ external_environments=config_data.get("external_environments", [])
68
+ )
69
+ load_external_environments(config)
70
+ except Exception as e:
71
+ logger.error(f"Failed to load external environment config: {e}")
72
+
73
+ # Log all registered environments
74
+ logger.info(f"Registered environments: {list_supported_env_types()}")
75
+
76
+
77
+ # Mount the main API router
78
+ app.include_router(api_router, tags=["environments"])