synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev8__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 (256) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +25 -32
  4. synth_ai/cli/calc.py +2 -3
  5. synth_ai/cli/demo.py +3 -5
  6. synth_ai/cli/legacy_root_backup.py +58 -32
  7. synth_ai/cli/man.py +22 -19
  8. synth_ai/cli/recent.py +9 -8
  9. synth_ai/cli/root.py +58 -13
  10. synth_ai/cli/status.py +13 -6
  11. synth_ai/cli/traces.py +45 -21
  12. synth_ai/cli/watch.py +40 -37
  13. synth_ai/config/base_url.py +47 -2
  14. synth_ai/core/experiment.py +1 -2
  15. synth_ai/environments/__init__.py +2 -6
  16. synth_ai/environments/environment/artifacts/base.py +3 -1
  17. synth_ai/environments/environment/db/sqlite.py +1 -1
  18. synth_ai/environments/environment/registry.py +19 -20
  19. synth_ai/environments/environment/resources/sqlite.py +2 -3
  20. synth_ai/environments/environment/rewards/core.py +3 -2
  21. synth_ai/environments/environment/tools/__init__.py +6 -4
  22. synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
  23. synth_ai/environments/examples/crafter_classic/engine.py +13 -13
  24. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
  25. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
  26. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
  27. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
  28. synth_ai/environments/examples/crafter_classic/environment.py +16 -15
  29. synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
  30. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
  31. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
  32. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
  33. synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
  34. synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
  35. synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
  36. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
  37. synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
  38. synth_ai/environments/examples/crafter_custom/environment.py +13 -13
  39. synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
  40. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
  41. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
  42. synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
  43. synth_ai/environments/examples/enron/engine.py +18 -14
  44. synth_ai/environments/examples/enron/environment.py +12 -11
  45. synth_ai/environments/examples/enron/taskset.py +7 -7
  46. synth_ai/environments/examples/minigrid/__init__.py +6 -6
  47. synth_ai/environments/examples/minigrid/engine.py +6 -6
  48. synth_ai/environments/examples/minigrid/environment.py +6 -6
  49. synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
  50. synth_ai/environments/examples/minigrid/taskset.py +13 -13
  51. synth_ai/environments/examples/nethack/achievements.py +1 -1
  52. synth_ai/environments/examples/nethack/engine.py +8 -7
  53. synth_ai/environments/examples/nethack/environment.py +10 -9
  54. synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
  55. synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
  56. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
  57. synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
  58. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
  59. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
  60. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
  61. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
  62. synth_ai/environments/examples/nethack/taskset.py +5 -5
  63. synth_ai/environments/examples/red/engine.py +9 -8
  64. synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
  69. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
  70. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
  71. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
  72. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
  73. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
  74. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
  75. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
  76. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
  77. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
  78. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
  79. synth_ai/environments/examples/red/environment.py +18 -15
  80. synth_ai/environments/examples/red/taskset.py +5 -3
  81. synth_ai/environments/examples/sokoban/engine.py +16 -13
  82. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
  87. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
  88. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
  89. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
  90. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
  91. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
  92. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
  93. synth_ai/environments/examples/sokoban/environment.py +15 -14
  94. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
  95. synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
  96. synth_ai/environments/examples/sokoban/taskset.py +13 -10
  97. synth_ai/environments/examples/tictactoe/engine.py +6 -6
  98. synth_ai/environments/examples/tictactoe/environment.py +8 -7
  99. synth_ai/environments/examples/tictactoe/taskset.py +6 -5
  100. synth_ai/environments/examples/verilog/engine.py +4 -3
  101. synth_ai/environments/examples/verilog/environment.py +11 -10
  102. synth_ai/environments/examples/verilog/taskset.py +14 -12
  103. synth_ai/environments/examples/wordle/__init__.py +5 -5
  104. synth_ai/environments/examples/wordle/engine.py +32 -25
  105. synth_ai/environments/examples/wordle/environment.py +21 -16
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +6 -6
  107. synth_ai/environments/examples/wordle/taskset.py +20 -12
  108. synth_ai/environments/reproducibility/core.py +1 -1
  109. synth_ai/environments/reproducibility/tree.py +21 -21
  110. synth_ai/environments/service/app.py +3 -2
  111. synth_ai/environments/service/core_routes.py +104 -110
  112. synth_ai/environments/service/external_registry.py +1 -2
  113. synth_ai/environments/service/registry.py +1 -1
  114. synth_ai/environments/stateful/core.py +1 -2
  115. synth_ai/environments/stateful/engine.py +1 -1
  116. synth_ai/environments/tasks/api.py +4 -4
  117. synth_ai/environments/tasks/core.py +14 -12
  118. synth_ai/environments/tasks/filters.py +6 -4
  119. synth_ai/environments/tasks/utils.py +13 -11
  120. synth_ai/evals/base.py +2 -3
  121. synth_ai/experimental/synth_oss.py +4 -4
  122. synth_ai/http.py +102 -0
  123. synth_ai/inference/__init__.py +7 -0
  124. synth_ai/inference/client.py +20 -0
  125. synth_ai/jobs/client.py +246 -0
  126. synth_ai/learning/__init__.py +24 -0
  127. synth_ai/learning/client.py +149 -0
  128. synth_ai/learning/config.py +43 -0
  129. synth_ai/learning/constants.py +29 -0
  130. synth_ai/learning/ft_client.py +59 -0
  131. synth_ai/learning/gateway.py +1 -3
  132. synth_ai/learning/health.py +43 -0
  133. synth_ai/learning/jobs.py +205 -0
  134. synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
  135. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
  136. synth_ai/learning/prompts/mipro.py +61 -52
  137. synth_ai/learning/prompts/random_search.py +42 -43
  138. synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
  139. synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
  140. synth_ai/learning/rl_client.py +256 -0
  141. synth_ai/learning/sse.py +58 -0
  142. synth_ai/learning/validators.py +48 -0
  143. synth_ai/lm/__init__.py +5 -5
  144. synth_ai/lm/caching/ephemeral.py +9 -9
  145. synth_ai/lm/caching/handler.py +20 -20
  146. synth_ai/lm/caching/persistent.py +10 -10
  147. synth_ai/lm/config.py +3 -3
  148. synth_ai/lm/constants.py +7 -7
  149. synth_ai/lm/core/all.py +17 -3
  150. synth_ai/lm/core/exceptions.py +0 -2
  151. synth_ai/lm/core/main.py +26 -41
  152. synth_ai/lm/core/main_v3.py +33 -10
  153. synth_ai/lm/core/synth_models.py +48 -0
  154. synth_ai/lm/core/vendor_clients.py +26 -22
  155. synth_ai/lm/injection.py +7 -8
  156. synth_ai/lm/overrides.py +21 -19
  157. synth_ai/lm/provider_support/__init__.py +1 -1
  158. synth_ai/lm/provider_support/anthropic.py +15 -15
  159. synth_ai/lm/provider_support/openai.py +23 -21
  160. synth_ai/lm/structured_outputs/handler.py +34 -32
  161. synth_ai/lm/structured_outputs/inject.py +24 -27
  162. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  163. synth_ai/lm/tools/base.py +17 -16
  164. synth_ai/lm/unified_interface.py +17 -18
  165. synth_ai/lm/vendors/base.py +20 -18
  166. synth_ai/lm/vendors/core/anthropic_api.py +36 -27
  167. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  168. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  169. synth_ai/lm/vendors/core/openai_api.py +42 -13
  170. synth_ai/lm/vendors/openai_standard.py +158 -101
  171. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  172. synth_ai/lm/vendors/retries.py +9 -1
  173. synth_ai/lm/vendors/supported/custom_endpoint.py +38 -28
  174. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  175. synth_ai/lm/vendors/supported/grok.py +8 -8
  176. synth_ai/lm/vendors/supported/ollama.py +2 -1
  177. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  178. synth_ai/lm/vendors/synth_client.py +425 -75
  179. synth_ai/lm/warmup.py +8 -7
  180. synth_ai/rl/__init__.py +30 -0
  181. synth_ai/rl/contracts.py +32 -0
  182. synth_ai/rl/env_keys.py +137 -0
  183. synth_ai/rl/secrets.py +19 -0
  184. synth_ai/scripts/verify_rewards.py +100 -0
  185. synth_ai/task/__init__.py +10 -0
  186. synth_ai/task/contracts.py +120 -0
  187. synth_ai/task/health.py +28 -0
  188. synth_ai/task/validators.py +12 -0
  189. synth_ai/tracing/__init__.py +22 -10
  190. synth_ai/tracing_v1/__init__.py +22 -20
  191. synth_ai/tracing_v3/__init__.py +7 -7
  192. synth_ai/tracing_v3/abstractions.py +56 -52
  193. synth_ai/tracing_v3/config.py +4 -2
  194. synth_ai/tracing_v3/db_config.py +6 -8
  195. synth_ai/tracing_v3/decorators.py +29 -30
  196. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  197. synth_ai/tracing_v3/hooks.py +24 -22
  198. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  199. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  200. synth_ai/tracing_v3/migration_helper.py +3 -5
  201. synth_ai/tracing_v3/replica_sync.py +30 -32
  202. synth_ai/tracing_v3/session_tracer.py +158 -31
  203. synth_ai/tracing_v3/storage/__init__.py +1 -1
  204. synth_ai/tracing_v3/storage/base.py +8 -7
  205. synth_ai/tracing_v3/storage/config.py +4 -4
  206. synth_ai/tracing_v3/storage/factory.py +4 -4
  207. synth_ai/tracing_v3/storage/utils.py +9 -9
  208. synth_ai/tracing_v3/turso/__init__.py +3 -3
  209. synth_ai/tracing_v3/turso/daemon.py +9 -9
  210. synth_ai/tracing_v3/turso/manager.py +278 -48
  211. synth_ai/tracing_v3/turso/models.py +77 -19
  212. synth_ai/tracing_v3/utils.py +5 -5
  213. synth_ai/v0/tracing/abstractions.py +28 -28
  214. synth_ai/v0/tracing/base_client.py +9 -9
  215. synth_ai/v0/tracing/client_manager.py +7 -7
  216. synth_ai/v0/tracing/config.py +7 -7
  217. synth_ai/v0/tracing/context.py +6 -6
  218. synth_ai/v0/tracing/decorators.py +6 -5
  219. synth_ai/v0/tracing/events/manage.py +1 -1
  220. synth_ai/v0/tracing/events/store.py +5 -4
  221. synth_ai/v0/tracing/immediate_client.py +4 -5
  222. synth_ai/v0/tracing/local.py +3 -3
  223. synth_ai/v0/tracing/log_client_base.py +4 -5
  224. synth_ai/v0/tracing/retry_queue.py +5 -6
  225. synth_ai/v0/tracing/trackers.py +25 -25
  226. synth_ai/v0/tracing/upload.py +6 -0
  227. synth_ai/v0/tracing_v1/__init__.py +1 -1
  228. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  229. synth_ai/v0/tracing_v1/base_client.py +9 -9
  230. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  231. synth_ai/v0/tracing_v1/config.py +7 -7
  232. synth_ai/v0/tracing_v1/context.py +6 -6
  233. synth_ai/v0/tracing_v1/decorators.py +7 -6
  234. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  235. synth_ai/v0/tracing_v1/events/store.py +5 -4
  236. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  237. synth_ai/v0/tracing_v1/local.py +3 -3
  238. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  239. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  240. synth_ai/v0/tracing_v1/trackers.py +25 -25
  241. synth_ai/v0/tracing_v1/upload.py +25 -24
  242. synth_ai/zyk/__init__.py +1 -0
  243. synth_ai-0.2.4.dev8.dist-info/METADATA +635 -0
  244. synth_ai-0.2.4.dev8.dist-info/RECORD +317 -0
  245. synth_ai/tui/__init__.py +0 -1
  246. synth_ai/tui/__main__.py +0 -13
  247. synth_ai/tui/cli/__init__.py +0 -1
  248. synth_ai/tui/cli/query_experiments.py +0 -165
  249. synth_ai/tui/cli/query_experiments_v3.py +0 -165
  250. synth_ai/tui/dashboard.py +0 -329
  251. synth_ai-0.2.4.dev6.dist-info/METADATA +0 -203
  252. synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
  253. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/WHEEL +0 -0
  254. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/entry_points.txt +0 -0
  255. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/licenses/LICENSE +0 -0
  256. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, Tuple
5
- from collections import Counter
6
3
  import random
7
4
  import string
5
+ from collections import Counter
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
8
 
9
- from synth_ai.environments.stateful.engine import StatefulEngine, StatefulEngineSnapshot
10
- from synth_ai.environments.reproducibility.core import IReproducibleEngine
11
- from synth_ai.environments.environment.rewards.core import RewardStack, RewardComponent
9
+ from synth_ai.environments.environment.rewards.core import RewardComponent, RewardStack
12
10
  from synth_ai.environments.environment.shared_engine import (
13
11
  GetObservationCallable,
14
12
  InternalObservation,
15
13
  )
14
+ from synth_ai.environments.reproducibility.core import IReproducibleEngine
15
+ from synth_ai.environments.stateful.engine import StatefulEngine, StatefulEngineSnapshot
16
16
  from synth_ai.environments.tasks.core import TaskInstance
17
17
 
18
-
19
18
  DEFAULT_SOLUTIONS = [
20
19
  "cigar",
21
20
  "rebut",
@@ -98,10 +97,10 @@ class WordlePublicState:
98
97
  word_length: int
99
98
  remaining_guesses: int
100
99
  max_guesses: int
101
- guesses: List[str]
102
- feedback: List[str] # Parallel to guesses; strings of 'G/Y/B'
103
- last_feedback: Optional[str]
104
- last_guess: Optional[str]
100
+ guesses: list[str]
101
+ feedback: list[str] # Parallel to guesses; strings of 'G/Y/B'
102
+ last_feedback: str | None
103
+ last_guess: str | None
105
104
  terminated: bool
106
105
  status: str # "in_progress" | "won" | "lost"
107
106
 
@@ -110,7 +109,7 @@ class WordlePublicState:
110
109
  if not self.guesses:
111
110
  return "(no guesses yet)"
112
111
  lines = []
113
- for g, fb in zip(self.guesses, self.feedback):
112
+ for g, fb in zip(self.guesses, self.feedback, strict=False):
114
113
  spaced = " ".join(list(fb))
115
114
  lines.append(f"{g.upper()} | {spaced}")
116
115
  return "\n".join(lines)
@@ -126,8 +125,8 @@ class WordlePrivateState:
126
125
 
127
126
  @dataclass
128
127
  class WordleEngineSnapshot(StatefulEngineSnapshot):
129
- task_instance_dict: Dict
130
- engine_snapshot: Dict
128
+ task_instance_dict: dict
129
+ engine_snapshot: dict
131
130
 
132
131
 
133
132
  class WordleWinComponent(RewardComponent):
@@ -156,20 +155,26 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
156
155
  self.max_guesses: int = getattr(md, "max_guesses", 6) if md else 6
157
156
  self.enforce_wordlist: bool = getattr(md, "enforce_wordlist", False) if md else False
158
157
  # Toggle: whether invalid actions consume a turn (default True)
159
- self.consume_invalid_attempts: bool = getattr(md, "consume_invalid_attempts", True) if md else True
158
+ self.consume_invalid_attempts: bool = (
159
+ getattr(md, "consume_invalid_attempts", True) if md else True
160
+ )
160
161
 
161
- self.base_word_list: List[str] = [
162
+ self.base_word_list: list[str] = [
162
163
  w for w in DEFAULT_SOLUTIONS if len(w) == self.word_length
163
164
  ] or [w for w in DEFAULT_SOLUTIONS if len(w) == 5]
164
165
 
165
166
  # Target selection: prefer explicit target_word in metadata; else pick deterministically by seed
166
- self.fixed_target: Optional[str] = _sanitize(getattr(md, "target_word", "")) if md and getattr(md, "target_word", None) else None
167
- self.seed: Optional[int] = getattr(md, "seed", None) if md else None
167
+ self.fixed_target: str | None = (
168
+ _sanitize(getattr(md, "target_word", ""))
169
+ if md and getattr(md, "target_word", None)
170
+ else None
171
+ )
172
+ self.seed: int | None = getattr(md, "seed", None) if md else None
168
173
 
169
174
  # Runtime state
170
- self.target: Optional[str] = None
171
- self.guesses: List[str] = []
172
- self.feedback: List[str] = []
175
+ self.target: str | None = None
176
+ self.guesses: list[str] = []
177
+ self.feedback: list[str] = []
173
178
  self.remaining_guesses: int = self.max_guesses
174
179
  self.status: str = "in_progress"
175
180
  self.terminated: bool = False
@@ -179,7 +184,9 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
179
184
  self.invalid_component = WordleInvalidGuessComponent()
180
185
  self.reward_stack = RewardStack([WordleWinComponent(), self.invalid_component])
181
186
 
182
- async def _reset_engine(self, *, seed: int | None = None) -> Tuple[WordlePrivateState, WordlePublicState]:
187
+ async def _reset_engine(
188
+ self, *, seed: int | None = None
189
+ ) -> tuple[WordlePrivateState, WordlePublicState]:
183
190
  if seed is None:
184
191
  seed = self.seed
185
192
  if seed is not None and self.fixed_target is None:
@@ -211,7 +218,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
211
218
  )
212
219
  return priv, pub
213
220
 
214
- async def _step_engine(self, action: str) -> Tuple[WordlePrivateState, WordlePublicState]:
221
+ async def _step_engine(self, action: str) -> tuple[WordlePrivateState, WordlePublicState]:
215
222
  assert self.target is not None
216
223
  guess = _sanitize(action)
217
224
 
@@ -307,7 +314,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
307
314
  )
308
315
 
309
316
  @classmethod
310
- async def _deserialize_engine(cls, snapshot: WordleEngineSnapshot) -> "WordleEngine":
317
+ async def _deserialize_engine(cls, snapshot: WordleEngineSnapshot) -> WordleEngine:
311
318
  task_instance = await TaskInstance.deserialize(snapshot.task_instance_dict)
312
319
  engine = cls(task_instance)
313
320
  s = snapshot.engine_snapshot
@@ -327,7 +334,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
327
334
  engine.total_reward = s.get("total_reward", 0.0)
328
335
  return engine
329
336
 
330
- def get_current_states_for_observation(self) -> Tuple[WordlePrivateState, WordlePublicState]:
337
+ def get_current_states_for_observation(self) -> tuple[WordlePrivateState, WordlePublicState]:
331
338
  pub = WordlePublicState(
332
339
  word_length=self.word_length,
333
340
  remaining_guesses=self.remaining_guesses,
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional, Dict, Any, Union
3
+ from typing import Any
4
+
4
5
  from pydantic import BaseModel, Field
5
6
 
6
- from synth_ai.environments.stateful.core import StatefulEnvironment
7
- from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
8
7
  from synth_ai.environments.environment.shared_engine import (
9
8
  GetObservationCallable,
10
9
  InternalObservation,
@@ -14,15 +13,17 @@ from synth_ai.environments.environment.tools import (
14
13
  EnvToolCall,
15
14
  ToolResult,
16
15
  )
16
+ from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
17
+ from synth_ai.environments.stateful.core import StatefulEnvironment
17
18
  from synth_ai.environments.tasks.core import TaskInstance
18
19
 
19
20
  from .engine import (
21
+ SynthWordleCheckpointObservationCallable,
22
+ SynthWordleObservationCallable,
20
23
  WordleEngine,
21
- WordlePublicState,
22
- WordlePrivateState,
23
24
  WordleEngineSnapshot,
24
- SynthWordleObservationCallable,
25
- SynthWordleCheckpointObservationCallable,
25
+ WordlePrivateState,
26
+ WordlePublicState,
26
27
  )
27
28
 
28
29
 
@@ -47,15 +48,17 @@ class WordleInteractTool(AbstractTool):
47
48
  except Exception as e:
48
49
  # Return current state with error message
49
50
  priv, pub = self.engine.get_current_states_for_observation()
50
- return ToolResult(ok=False, error=str(e), payload={"public_state": pub, "private_state": priv})
51
+ return ToolResult(
52
+ ok=False, error=str(e), payload={"public_state": pub, "private_state": priv}
53
+ )
51
54
 
52
55
 
53
56
  class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngine]):
54
57
  def __init__(
55
58
  self,
56
59
  task_instance: TaskInstance,
57
- custom_step_obs: Optional[GetObservationCallable] = None,
58
- custom_ckpt_obs: Optional[GetObservationCallable] = None,
60
+ custom_step_obs: GetObservationCallable | None = None,
61
+ custom_ckpt_obs: GetObservationCallable | None = None,
59
62
  ) -> None:
60
63
  self.name = "Wordle"
61
64
  self.task_instance = task_instance
@@ -101,10 +104,13 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
101
104
  if "tool" in tool_calls:
102
105
  validated = EnvToolCall(tool=tool_calls["tool"], args=tool_calls.get("args", {}))
103
106
  elif "name" in tool_calls:
104
- validated = EnvToolCall(tool=tool_calls["name"], args=tool_calls.get("parameters", {}))
107
+ validated = EnvToolCall(
108
+ tool=tool_calls["name"], args=tool_calls.get("parameters", {})
109
+ )
105
110
  elif "function" in tool_calls:
106
111
  validated = EnvToolCall(
107
- tool=tool_calls["function"]["name"], args=tool_calls["function"].get("arguments", {})
112
+ tool=tool_calls["function"]["name"],
113
+ args=tool_calls["function"].get("arguments", {}),
108
114
  )
109
115
  else:
110
116
  # Treat remaining keys as args; default tool name
@@ -129,8 +135,8 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
129
135
  self,
130
136
  priv: WordlePrivateState,
131
137
  pub: WordlePublicState,
132
- obs_cb: Optional[GetObservationCallable],
133
- extra_obs: Optional[Dict[str, Any]] = None,
138
+ obs_cb: GetObservationCallable | None,
139
+ extra_obs: dict[str, Any] | None = None,
134
140
  ) -> InternalObservation:
135
141
  if obs_cb:
136
142
  obs = await obs_cb.get_observation(pub, priv)
@@ -146,9 +152,8 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
146
152
  @classmethod
147
153
  async def _deserialize_engine(
148
154
  cls, snapshot: WordleEngineSnapshot, task_instance: TaskInstance
149
- ) -> "WordleEnvironment":
155
+ ) -> WordleEnvironment:
150
156
  env = cls(task_instance)
151
157
  env.engine = await WordleEngine._deserialize_engine(snapshot)
152
158
  env._interact_tool = WordleInteractTool(env.engine)
153
159
  return env
154
-
@@ -10,24 +10,24 @@ Usage:
10
10
  This script writes a deterministic list of 5-letter English words ranked by frequency.
11
11
  Commit the resulting instances.json to remove runtime dependencies.
12
12
  """
13
+
13
14
  from __future__ import annotations
14
15
 
15
16
  import argparse
16
17
  import json
17
18
  import re
18
- from typing import List
19
19
 
20
- from wordfreq import zipf_frequency, top_n_list
20
+ from wordfreq import top_n_list, zipf_frequency
21
21
 
22
22
 
23
- def build_word_list(count: int, length: int, min_zipf: float, wordlist: str = "large") -> List[str]:
24
- N = max(count * 20, 5000)
25
- cands = [w.lower() for w in top_n_list("en", N, wordlist=wordlist)]
23
+ def build_word_list(count: int, length: int, min_zipf: float, wordlist: str = "large") -> list[str]:
24
+ n_candidates = max(count * 20, 5000)
25
+ cands = [w.lower() for w in top_n_list("en", n_candidates, wordlist=wordlist)]
26
26
  cands = [w for w in cands if len(w) == length and re.fullmatch(r"[a-z]+", w)]
27
27
  scored = [(w, zipf_frequency(w, "en")) for w in cands]
28
28
  scored = [p for p in scored if p[1] >= float(min_zipf)]
29
29
  scored.sort(key=lambda t: (-t[1], t[0]))
30
- out: List[str] = []
30
+ out: list[str] = []
31
31
  seen = set()
32
32
  for w, _ in scored:
33
33
  if w in seen:
@@ -1,19 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- import random
5
4
  from dataclasses import dataclass
6
5
  from pathlib import Path
7
- from typing import List, Tuple
8
- from uuid import uuid4, UUID
6
+ from uuid import UUID
9
7
 
10
8
  from synth_ai.environments.tasks.core import (
11
- TaskInstance,
12
- TaskInstanceMetadata,
13
- TaskInstanceSet,
14
9
  Impetus,
15
10
  Intent,
16
11
  SplitInfo,
12
+ TaskInstance,
13
+ TaskInstanceMetadata,
14
+ TaskInstanceSet,
17
15
  )
18
16
 
19
17
  from .engine import DEFAULT_SOLUTIONS
@@ -53,7 +51,7 @@ class WordleTaskInstance(TaskInstance):
53
51
  }
54
52
 
55
53
  @classmethod
56
- async def deserialize(cls, data: dict) -> "WordleTaskInstance":
54
+ async def deserialize(cls, data: dict) -> WordleTaskInstance:
57
55
  from uuid import UUID
58
56
 
59
57
  metadata = WordleTaskInstanceMetadata(
@@ -81,16 +79,18 @@ class WordleTaskInstance(TaskInstance):
81
79
 
82
80
  def _stable_uuid_for_instance(idx: int, target: str) -> UUID:
83
81
  import uuid
82
+
84
83
  return uuid.uuid5(uuid.NAMESPACE_URL, f"wordle-fixed-v1:{idx}:{target}")
85
84
 
86
85
 
87
- def _load_fixed_instances_json() -> tuple[List[dict], dict]:
86
+ def _load_fixed_instances_json() -> tuple[list[dict], dict]:
88
87
  """Load fixed instances definition from instances.json (if present).
89
88
 
90
89
  Returns a tuple (instances, defaults) where instances is a list of dicts with at least
91
90
  target_word fields, and defaults contains default params.
92
91
  """
93
92
  import os
93
+
94
94
  # Allow override via env var
95
95
  override = os.getenv("WORDLE_INSTANCES_JSON")
96
96
  p = Path(override) if override else Path(__file__).with_name("instances.json")
@@ -126,11 +126,17 @@ async def create_wordle_taskset(
126
126
 
127
127
  json_insts, json_defaults = _load_fixed_instances_json()
128
128
 
129
- instances: List[WordleTaskInstance] = []
129
+ instances: list[WordleTaskInstance] = []
130
130
  # Assemble fixed targets from JSON only (no runtime generation)
131
- fixed_targets: List[str] = []
131
+ fixed_targets: list[str] = []
132
132
  if json_insts:
133
- fixed_targets.extend([str(r.get("target_word", "")).strip().lower() for r in json_insts if r.get("target_word")])
133
+ fixed_targets.extend(
134
+ [
135
+ str(r.get("target_word", "")).strip().lower()
136
+ for r in json_insts
137
+ if r.get("target_word")
138
+ ]
139
+ )
134
140
 
135
141
  if fixed_targets:
136
142
  # Use fixed_targets, honoring defaults and slicing by sample_size
@@ -167,7 +173,9 @@ async def create_wordle_taskset(
167
173
  instances.append(inst)
168
174
  else:
169
175
  # Procedural fallback: stable ordering from DEFAULT_SOLUTIONS
170
- pool = [w for w in DEFAULT_SOLUTIONS if len(w) == word_length] or [w for w in DEFAULT_SOLUTIONS if len(w) == 5]
176
+ pool = [w for w in DEFAULT_SOLUTIONS if len(w) == word_length] or [
177
+ w for w in DEFAULT_SOLUTIONS if len(w) == 5
178
+ ]
171
179
  sample = pool[:sample_size]
172
180
  for i, target in enumerate(sample):
173
181
  seed = i
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import TypeVar, Generic, Any
2
+ from typing import Any, Generic, TypeVar
3
3
 
4
4
 
5
5
  class IReproducibleEngine(ABC):
@@ -13,13 +13,14 @@ big “backend.production” code-base.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
- import json
17
- import sqlite3
18
16
  import gzip
19
- import pickle
17
+ import json
20
18
  import logging
19
+ import pickle
20
+ import sqlite3
21
+ from collections.abc import Iterable
21
22
  from pathlib import Path
22
- from typing import Any, Dict, Optional, Tuple, Iterable
23
+ from typing import Any
23
24
 
24
25
  import networkx as nx
25
26
 
@@ -31,10 +32,9 @@ log = logging.getLogger(__name__)
31
32
  # --------------------------------------------------------------------------- #
32
33
  # lightweight metadata record #
33
34
  # --------------------------------------------------------------------------- #
34
- import os
35
35
  import hashlib
36
36
  import logging
37
- from typing import Union
37
+ import os
38
38
 
39
39
  log = logging.getLogger(__name__)
40
40
 
@@ -51,7 +51,7 @@ class FilesystemSnapshotStore:
51
51
  is the SHA-256 hash of its compressed content.
52
52
  """
53
53
 
54
- def __init__(self, base_dir: Union[str, Path] = DEFAULT_SNAPSHOT_DIR):
54
+ def __init__(self, base_dir: str | Path = DEFAULT_SNAPSHOT_DIR):
55
55
  self.base_dir = Path(base_dir)
56
56
  try:
57
57
  self.base_dir.mkdir(parents=True, exist_ok=True)
@@ -69,7 +69,7 @@ class FilesystemSnapshotStore:
69
69
  filename = f"{key}.snapshot.gz"
70
70
  return self.base_dir / filename
71
71
 
72
- def write(self, blob: Union[bytes, Dict[str, Any]]) -> str:
72
+ def write(self, blob: bytes | dict[str, Any]) -> str:
73
73
  """
74
74
  Stores a snapshot blob (bytes or dict) and returns its SHA-256 key.
75
75
 
@@ -95,7 +95,7 @@ class FilesystemSnapshotStore:
95
95
  log.error(f"Failed to write snapshot: {e}", exc_info=True)
96
96
  raise
97
97
 
98
- def read(self, key: str) -> Optional[bytes]:
98
+ def read(self, key: str) -> bytes | None:
99
99
  """
100
100
  Retrieves the raw *compressed* snapshot bytes for a given key.
101
101
 
@@ -143,12 +143,12 @@ class TrajectorySnapshot:
143
143
  def __init__(
144
144
  self,
145
145
  snap_id: str,
146
- parent_id: Optional[str],
146
+ parent_id: str | None,
147
147
  depth: int,
148
- action: Optional[Any],
148
+ action: Any | None,
149
149
  reward: float = 0.0,
150
150
  terminated: bool = False,
151
- info: Optional[Dict[str, Any]] = None,
151
+ info: dict[str, Any] | None = None,
152
152
  ):
153
153
  self.snap_id = snap_id
154
154
  self.parent_id = parent_id
@@ -186,9 +186,9 @@ class TrajectoryTreeStore:
186
186
 
187
187
  def __init__(
188
188
  self,
189
- snapshot_store: Optional[FilesystemSnapshotStore] = None,
189
+ snapshot_store: FilesystemSnapshotStore | None = None,
190
190
  *,
191
- db_path: Optional[Path | str] = None,
191
+ db_path: Path | str | None = None,
192
192
  ):
193
193
  self.snap_store = snapshot_store or FilesystemSnapshotStore()
194
194
  self.graph: nx.DiGraph = nx.DiGraph()
@@ -202,7 +202,7 @@ class TrajectoryTreeStore:
202
202
 
203
203
  # insertion -------------------------------------------------------------
204
204
 
205
- def add_root(self, snapshot_blob: bytes, *, info: Dict[str, Any] | None = None) -> str:
205
+ def add_root(self, snapshot_blob: bytes, *, info: dict[str, Any] | None = None) -> str:
206
206
  """Insert the very first node and return its content-hash key."""
207
207
  snap_id = self.snap_store.write(snapshot_blob)
208
208
  self._add_node(TrajectorySnapshot(snap_id, None, 0, None, 0.0, False, info))
@@ -216,7 +216,7 @@ class TrajectoryTreeStore:
216
216
  action: Any,
217
217
  reward: float,
218
218
  terminated: bool = False,
219
- info: Dict[str, Any] | None = None,
219
+ info: dict[str, Any] | None = None,
220
220
  ) -> str:
221
221
  """Attach `snapshot_blob` as a child reached by `action` from *parent_id*."""
222
222
  if parent_id not in self.graph:
@@ -230,10 +230,10 @@ class TrajectoryTreeStore:
230
230
 
231
231
  # read-side helpers -----------------------------------------------------
232
232
 
233
- def get_children(self, snap_id: str) -> Tuple[str, ...]:
233
+ def get_children(self, snap_id: str) -> tuple[str, ...]:
234
234
  return tuple(self.graph.successors(snap_id))
235
235
 
236
- def get_parent(self, snap_id: str) -> Optional[str]:
236
+ def get_parent(self, snap_id: str) -> str | None:
237
237
  preds = tuple(self.graph.predecessors(snap_id))
238
238
  return preds[0] if preds else None
239
239
 
@@ -246,17 +246,17 @@ class TrajectoryTreeStore:
246
246
  """Yield snapshot-ids that currently have no children."""
247
247
  return (n for n in self.graph.nodes if self.is_leaf(n))
248
248
 
249
- def path_to_root(self, snap_id: str) -> Tuple[str, ...]:
249
+ def path_to_root(self, snap_id: str) -> tuple[str, ...]:
250
250
  """Return (snap_id, …, root_id)"""
251
251
  path = [snap_id]
252
252
  while (p := self.get_parent(path[-1])) is not None:
253
253
  path.append(p)
254
254
  return tuple(path)
255
255
 
256
- def reconstruct_actions(self, snap_id: str) -> Tuple[Any, ...]:
256
+ def reconstruct_actions(self, snap_id: str) -> tuple[Any, ...]:
257
257
  """Return the sequence of *actions* from the root → `snap_id`."""
258
258
  actions = []
259
- for child, parent in zip(self.path_to_root(snap_id)[:-1], self.path_to_root(snap_id)[1:]):
259
+ for child, parent in zip(self.path_to_root(snap_id)[:-1], self.path_to_root(snap_id)[1:], strict=False):
260
260
  actions.append(self.graph.edges[parent, child]["action"])
261
261
  return tuple(reversed(actions))
262
262
 
@@ -1,5 +1,5 @@
1
- import sys
2
1
  import os # Added to ensure os is available before use
2
+ import sys
3
3
 
4
4
  # Ensure local 'src' directory is on PYTHONPATH for dev installs
5
5
  # Current file: <repo>/src/synth_env/service/app.py
@@ -12,12 +12,12 @@ print(f"SYS.PATH IN APP.PY: {sys.path}")
12
12
  import logging
13
13
 
14
14
  from fastapi import FastAPI
15
- from synth_ai.environments.service.registry import list_supported_env_types, register_environment
16
15
  from synth_ai.environments.service.core_routes import api_router
17
16
  from synth_ai.environments.service.external_registry import (
18
17
  ExternalRegistryConfig,
19
18
  load_external_environments,
20
19
  )
20
+ from synth_ai.environments.service.registry import list_supported_env_types, register_environment
21
21
 
22
22
  # Configure logging with more detail
23
23
  logging.basicConfig(
@@ -41,6 +41,7 @@ register_environment("CrafterCustom", ccustom.CrafterCustomEnvironment)
41
41
  # Register Wordle example environment
42
42
  try:
43
43
  import synth_ai.environments.examples.wordle.environment as wordle_mod
44
+
44
45
  register_environment("Wordle", wordle_mod.WordleEnvironment)
45
46
  except Exception as _e:
46
47
  # Keep service robust even if example env import fails