synth-ai 0.2.4.dev5__py3-none-any.whl → 0.2.4.dev7__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 (229) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +22 -17
  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 +1 -3
  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 +21 -17
  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 +29 -0
  104. synth_ai/environments/examples/wordle/engine.py +398 -0
  105. synth_ai/environments/examples/wordle/environment.py +159 -0
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
  107. synth_ai/environments/examples/wordle/taskset.py +230 -0
  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 +11 -2
  111. synth_ai/environments/service/core_routes.py +137 -105
  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/learning/gateway.py +1 -3
  123. synth_ai/learning/prompts/banking77_injection_eval.py +168 -0
  124. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +213 -0
  125. synth_ai/learning/prompts/mipro.py +282 -1
  126. synth_ai/learning/prompts/random_search.py +246 -0
  127. synth_ai/learning/prompts/run_mipro_banking77.py +172 -0
  128. synth_ai/learning/prompts/run_random_search_banking77.py +324 -0
  129. synth_ai/lm/__init__.py +5 -5
  130. synth_ai/lm/caching/ephemeral.py +9 -9
  131. synth_ai/lm/caching/handler.py +20 -20
  132. synth_ai/lm/caching/persistent.py +10 -10
  133. synth_ai/lm/config.py +3 -3
  134. synth_ai/lm/constants.py +7 -7
  135. synth_ai/lm/core/all.py +17 -3
  136. synth_ai/lm/core/exceptions.py +0 -2
  137. synth_ai/lm/core/main.py +26 -41
  138. synth_ai/lm/core/main_v3.py +20 -10
  139. synth_ai/lm/core/vendor_clients.py +18 -17
  140. synth_ai/lm/injection.py +80 -0
  141. synth_ai/lm/overrides.py +206 -0
  142. synth_ai/lm/provider_support/__init__.py +1 -1
  143. synth_ai/lm/provider_support/anthropic.py +51 -24
  144. synth_ai/lm/provider_support/openai.py +51 -22
  145. synth_ai/lm/structured_outputs/handler.py +34 -32
  146. synth_ai/lm/structured_outputs/inject.py +24 -27
  147. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  148. synth_ai/lm/tools/base.py +17 -16
  149. synth_ai/lm/unified_interface.py +17 -18
  150. synth_ai/lm/vendors/base.py +20 -18
  151. synth_ai/lm/vendors/core/anthropic_api.py +50 -25
  152. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  153. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  154. synth_ai/lm/vendors/core/openai_api.py +11 -10
  155. synth_ai/lm/vendors/openai_standard.py +144 -88
  156. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  157. synth_ai/lm/vendors/retries.py +9 -1
  158. synth_ai/lm/vendors/supported/custom_endpoint.py +26 -26
  159. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  160. synth_ai/lm/vendors/supported/grok.py +8 -8
  161. synth_ai/lm/vendors/supported/ollama.py +2 -1
  162. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  163. synth_ai/lm/vendors/synth_client.py +69 -63
  164. synth_ai/lm/warmup.py +8 -7
  165. synth_ai/tracing/__init__.py +22 -10
  166. synth_ai/tracing_v1/__init__.py +22 -20
  167. synth_ai/tracing_v3/__init__.py +7 -7
  168. synth_ai/tracing_v3/abstractions.py +56 -52
  169. synth_ai/tracing_v3/config.py +4 -2
  170. synth_ai/tracing_v3/db_config.py +6 -8
  171. synth_ai/tracing_v3/decorators.py +29 -30
  172. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  173. synth_ai/tracing_v3/hooks.py +21 -21
  174. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  175. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  176. synth_ai/tracing_v3/migration_helper.py +3 -5
  177. synth_ai/tracing_v3/replica_sync.py +30 -32
  178. synth_ai/tracing_v3/session_tracer.py +35 -29
  179. synth_ai/tracing_v3/storage/__init__.py +1 -1
  180. synth_ai/tracing_v3/storage/base.py +8 -7
  181. synth_ai/tracing_v3/storage/config.py +4 -4
  182. synth_ai/tracing_v3/storage/factory.py +4 -4
  183. synth_ai/tracing_v3/storage/utils.py +9 -9
  184. synth_ai/tracing_v3/turso/__init__.py +3 -3
  185. synth_ai/tracing_v3/turso/daemon.py +9 -9
  186. synth_ai/tracing_v3/turso/manager.py +60 -48
  187. synth_ai/tracing_v3/turso/models.py +24 -19
  188. synth_ai/tracing_v3/utils.py +5 -5
  189. synth_ai/tui/__main__.py +1 -1
  190. synth_ai/tui/cli/query_experiments.py +2 -3
  191. synth_ai/tui/cli/query_experiments_v3.py +2 -3
  192. synth_ai/tui/dashboard.py +97 -86
  193. synth_ai/v0/tracing/abstractions.py +28 -28
  194. synth_ai/v0/tracing/base_client.py +9 -9
  195. synth_ai/v0/tracing/client_manager.py +7 -7
  196. synth_ai/v0/tracing/config.py +7 -7
  197. synth_ai/v0/tracing/context.py +6 -6
  198. synth_ai/v0/tracing/decorators.py +6 -5
  199. synth_ai/v0/tracing/events/manage.py +1 -1
  200. synth_ai/v0/tracing/events/store.py +5 -4
  201. synth_ai/v0/tracing/immediate_client.py +4 -5
  202. synth_ai/v0/tracing/local.py +3 -3
  203. synth_ai/v0/tracing/log_client_base.py +4 -5
  204. synth_ai/v0/tracing/retry_queue.py +5 -6
  205. synth_ai/v0/tracing/trackers.py +25 -25
  206. synth_ai/v0/tracing/upload.py +6 -0
  207. synth_ai/v0/tracing_v1/__init__.py +1 -1
  208. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  209. synth_ai/v0/tracing_v1/base_client.py +9 -9
  210. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  211. synth_ai/v0/tracing_v1/config.py +7 -7
  212. synth_ai/v0/tracing_v1/context.py +6 -6
  213. synth_ai/v0/tracing_v1/decorators.py +7 -6
  214. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  215. synth_ai/v0/tracing_v1/events/store.py +5 -4
  216. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  217. synth_ai/v0/tracing_v1/local.py +3 -3
  218. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  219. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  220. synth_ai/v0/tracing_v1/trackers.py +25 -25
  221. synth_ai/v0/tracing_v1/upload.py +25 -24
  222. synth_ai/zyk/__init__.py +1 -0
  223. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +2 -11
  224. synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
  225. synth_ai-0.2.4.dev5.dist-info/RECORD +0 -287
  226. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
  227. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
  228. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from uuid import UUID
7
+
8
+ from synth_ai.environments.tasks.core import (
9
+ Impetus,
10
+ Intent,
11
+ SplitInfo,
12
+ TaskInstance,
13
+ TaskInstanceMetadata,
14
+ TaskInstanceSet,
15
+ )
16
+
17
+ from .engine import DEFAULT_SOLUTIONS
18
+
19
+
20
+ @dataclass
21
+ class WordleTaskInstanceMetadata(TaskInstanceMetadata):
22
+ word_length: int
23
+ max_guesses: int
24
+ target_word: str
25
+ enforce_wordlist: bool
26
+ seed: int | None = None
27
+ consume_invalid_attempts: bool = True
28
+
29
+
30
+ @dataclass
31
+ class WordleTaskInstance(TaskInstance):
32
+ async def serialize(self) -> dict:
33
+ return {
34
+ "id": str(self.id),
35
+ "impetus": {"instructions": self.impetus.instructions},
36
+ "intent": {
37
+ "rubric": self.intent.rubric,
38
+ "gold_trajectories": self.intent.gold_trajectories,
39
+ "gold_state_diff": self.intent.gold_state_diff,
40
+ },
41
+ "metadata": {
42
+ "word_length": self.metadata.word_length,
43
+ "max_guesses": self.metadata.max_guesses,
44
+ "target_word": self.metadata.target_word,
45
+ "enforce_wordlist": self.metadata.enforce_wordlist,
46
+ "seed": self.metadata.seed,
47
+ "consume_invalid_attempts": self.metadata.consume_invalid_attempts,
48
+ },
49
+ "is_reproducible": self.is_reproducible,
50
+ "initial_engine_snapshot": self.initial_engine_snapshot,
51
+ }
52
+
53
+ @classmethod
54
+ async def deserialize(cls, data: dict) -> WordleTaskInstance:
55
+ from uuid import UUID
56
+
57
+ metadata = WordleTaskInstanceMetadata(
58
+ word_length=data["metadata"]["word_length"],
59
+ max_guesses=data["metadata"]["max_guesses"],
60
+ target_word=data["metadata"]["target_word"],
61
+ enforce_wordlist=data["metadata"]["enforce_wordlist"],
62
+ seed=data["metadata"].get("seed"),
63
+ consume_invalid_attempts=data["metadata"].get("consume_invalid_attempts", True),
64
+ )
65
+
66
+ return cls(
67
+ id=UUID(data["id"]),
68
+ impetus=Impetus(instructions=data["impetus"]["instructions"]),
69
+ intent=Intent(
70
+ rubric=data["intent"]["rubric"],
71
+ gold_trajectories=data["intent"]["gold_trajectories"],
72
+ gold_state_diff=data["intent"]["gold_state_diff"],
73
+ ),
74
+ metadata=metadata,
75
+ is_reproducible=data["is_reproducible"],
76
+ initial_engine_snapshot=data["initial_engine_snapshot"],
77
+ )
78
+
79
+
80
+ def _stable_uuid_for_instance(idx: int, target: str) -> UUID:
81
+ import uuid
82
+
83
+ return uuid.uuid5(uuid.NAMESPACE_URL, f"wordle-fixed-v1:{idx}:{target}")
84
+
85
+
86
+ def _load_fixed_instances_json() -> tuple[list[dict], dict]:
87
+ """Load fixed instances definition from instances.json (if present).
88
+
89
+ Returns a tuple (instances, defaults) where instances is a list of dicts with at least
90
+ target_word fields, and defaults contains default params.
91
+ """
92
+ import os
93
+
94
+ # Allow override via env var
95
+ override = os.getenv("WORDLE_INSTANCES_JSON")
96
+ p = Path(override) if override else Path(__file__).with_name("instances.json")
97
+ if not p.exists():
98
+ return [], {}
99
+ try:
100
+ data = json.loads(p.read_text())
101
+ defaults = data.get("defaults", {}) or {}
102
+ insts = data.get("instances", []) or []
103
+ return insts, defaults
104
+ except Exception:
105
+ return [], {}
106
+
107
+
108
+ # Note: generation helpers removed from runtime. Use the provided script in tools/
109
+ _ = None
110
+
111
+
112
+ async def create_wordle_taskset(
113
+ *,
114
+ word_length: int = 5,
115
+ max_guesses: int = 6,
116
+ enforce_wordlist: bool = False,
117
+ sample_size: int = 30,
118
+ consume_invalid_attempts: bool = True,
119
+ ) -> TaskInstanceSet:
120
+ """Create a Wordle taskset.
121
+
122
+ Priority:
123
+ 1) If instances.json exists, use it to produce a fixed, stable taskset with deterministic IDs.
124
+ 2) Otherwise, fall back to a procedural slice of DEFAULT_SOLUTIONS (stable ordering).
125
+ """
126
+
127
+ json_insts, json_defaults = _load_fixed_instances_json()
128
+
129
+ instances: list[WordleTaskInstance] = []
130
+ # Assemble fixed targets from JSON only (no runtime generation)
131
+ fixed_targets: list[str] = []
132
+ if json_insts:
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
+ )
140
+
141
+ if fixed_targets:
142
+ # Use fixed_targets, honoring defaults and slicing by sample_size
143
+ chosen = fixed_targets[:sample_size]
144
+ for i, tgt in enumerate(chosen):
145
+ md = WordleTaskInstanceMetadata(
146
+ word_length=int(word_length),
147
+ max_guesses=int(max_guesses),
148
+ target_word=tgt,
149
+ enforce_wordlist=bool(enforce_wordlist),
150
+ seed=i,
151
+ consume_invalid_attempts=bool(consume_invalid_attempts),
152
+ )
153
+ impetus = Impetus(
154
+ instructions=(
155
+ "Play Wordle. Submit one word per turn consisting only of letters. "
156
+ f"You have up to {md.max_guesses} guesses to find the {md.word_length}-letter target word. "
157
+ "Feedback per letter: G=correct position, Y=present elsewhere, B=absent."
158
+ )
159
+ )
160
+ intent = Intent(
161
+ rubric={"goal": "Guess the target word in as few moves as possible"},
162
+ gold_trajectories=None,
163
+ gold_state_diff={"target_known": False},
164
+ )
165
+ inst = WordleTaskInstance(
166
+ id=_stable_uuid_for_instance(i, md.target_word),
167
+ impetus=impetus,
168
+ intent=intent,
169
+ metadata=md,
170
+ is_reproducible=True,
171
+ initial_engine_snapshot=None,
172
+ )
173
+ instances.append(inst)
174
+ else:
175
+ # Procedural fallback: stable ordering from DEFAULT_SOLUTIONS
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
+ ]
179
+ sample = pool[:sample_size]
180
+ for i, target in enumerate(sample):
181
+ seed = i
182
+ md = WordleTaskInstanceMetadata(
183
+ word_length=word_length,
184
+ max_guesses=max_guesses,
185
+ target_word=target,
186
+ enforce_wordlist=enforce_wordlist,
187
+ seed=seed,
188
+ consume_invalid_attempts=consume_invalid_attempts,
189
+ )
190
+ impetus = Impetus(
191
+ instructions=(
192
+ "Play Wordle. Submit one word per turn consisting only of letters. "
193
+ f"You have up to {max_guesses} guesses to find the {word_length}-letter target word. "
194
+ "Feedback per letter: G=correct position, Y=present elsewhere, B=absent."
195
+ )
196
+ )
197
+ intent = Intent(
198
+ rubric={"goal": "Guess the target word in as few moves as possible"},
199
+ gold_trajectories=None,
200
+ gold_state_diff={"target_known": False},
201
+ )
202
+ inst = WordleTaskInstance(
203
+ id=_stable_uuid_for_instance(i, target),
204
+ impetus=impetus,
205
+ intent=intent,
206
+ metadata=md,
207
+ is_reproducible=True,
208
+ initial_engine_snapshot=None,
209
+ )
210
+ instances.append(inst)
211
+
212
+ # Deterministic split based on index positions
213
+ val_ids = {instances[i].id for i in range(0, len(instances), 5)}
214
+ test_ids = {instances[i].id for i in range(0, len(instances), 7)}
215
+ split = SplitInfo(val_instance_ids=val_ids, test_instance_ids=test_ids, _is_split_defined=True)
216
+
217
+ return TaskInstanceSet(
218
+ name="Wordle Fixed TaskSet" if json_insts else "Wordle Example TaskSet",
219
+ description=(
220
+ "Fixed set from instances.json (stable ordering)."
221
+ if json_insts
222
+ else "Lightweight Wordle tasks with fixed targets and seeds."
223
+ ),
224
+ instances=instances,
225
+ split_info=split,
226
+ )
227
+
228
+
229
+ # Alias
230
+ taskset = create_wordle_taskset
@@ -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(
@@ -38,6 +38,15 @@ import synth_ai.environments.examples.crafter_custom.environment as ccustom
38
38
 
39
39
  register_environment("CrafterCustom", ccustom.CrafterCustomEnvironment)
40
40
 
41
+ # Register Wordle example environment
42
+ try:
43
+ import synth_ai.environments.examples.wordle.environment as wordle_mod
44
+
45
+ register_environment("Wordle", wordle_mod.WordleEnvironment)
46
+ except Exception as _e:
47
+ # Keep service robust even if example env import fails
48
+ logging.getLogger(__name__).warning(f"Wordle env not registered: {_e}")
49
+
41
50
  app = FastAPI(title="Environment Service")
42
51
 
43
52