synth-ai 0.2.0__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.
- synth_ai/__init__.py +28 -2
- synth_ai/core/system.py +4 -0
- synth_ai/environments/__init__.py +35 -0
- synth_ai/environments/environment/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/base.py +50 -0
- synth_ai/environments/environment/core.py +22 -0
- synth_ai/environments/environment/db/__init__.py +1 -0
- synth_ai/environments/environment/db/sqlite.py +45 -0
- synth_ai/environments/environment/registry.py +24 -0
- synth_ai/environments/environment/resources/sqlite.py +46 -0
- synth_ai/environments/environment/results.py +1 -0
- synth_ai/environments/environment/rewards/__init__.py +1 -0
- synth_ai/environments/environment/rewards/core.py +28 -0
- synth_ai/environments/environment/shared_engine.py +26 -0
- synth_ai/environments/environment/tools/__init__.py +34 -0
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/engine.py +502 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/environment.py +255 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
- synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
- synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
- synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
- synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
- synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
- synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
- synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
- synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
- synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
- synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/red/test_fixes.py +125 -0
- synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
- synth_ai/environments/examples/red/units/__init__.py +1 -0
- synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
- synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
- synth_ai/environments/examples/red/units/test_engine.py +192 -0
- synth_ai/environments/examples/red/units/test_environment.py +455 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
- synth_ai/environments/examples/red/units/test_integration.py +217 -0
- synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
- synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
- synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
- synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
- synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
- synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
- synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
- synth_ai/environments/examples/red/units/test_taskset.py +116 -0
- synth_ai/environments/examples/red/units/test_tree.py +448 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
- synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
- synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
- synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
- synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
- synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
- synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
- synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
- synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
- synth_ai/environments/reproducibility/core.py +42 -0
- synth_ai/environments/reproducibility/tree.py +364 -0
- synth_ai/environments/service/app.py +78 -0
- synth_ai/environments/service/core_routes.py +775 -0
- synth_ai/environments/service/external_registry.py +57 -0
- synth_ai/environments/service/registry.py +9 -0
- synth_ai/environments/stateful/__init__.py +1 -0
- synth_ai/environments/stateful/core.py +28 -0
- synth_ai/environments/stateful/engine.py +21 -0
- synth_ai/environments/stateful/state.py +7 -0
- synth_ai/environments/tasks/api.py +19 -0
- synth_ai/environments/tasks/core.py +78 -0
- synth_ai/environments/tasks/filters.py +39 -0
- synth_ai/environments/tasks/utils.py +89 -0
- synth_ai/environments/v0_observability/history.py +3 -0
- synth_ai/environments/v0_observability/log.py +2 -0
- synth_ai/lm/caching/constants.py +1 -0
- synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
- synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
- synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
- synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
- synth_ai/{zyk/lms → lm}/config.py +2 -1
- synth_ai/{zyk/lms → lm}/constants.py +2 -2
- synth_ai/{zyk/lms → lm}/core/all.py +10 -10
- synth_ai/{zyk/lms → lm}/core/main.py +57 -33
- synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
- synth_ai/lm/cost/monitor.py +1 -0
- synth_ai/lm/cost/statefulness.py +1 -0
- synth_ai/lm/provider_support/__init__.py +8 -0
- synth_ai/lm/provider_support/anthropic.py +945 -0
- synth_ai/lm/provider_support/openai.py +1115 -0
- synth_ai/lm/provider_support/suppress_logging.py +31 -0
- synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
- synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
- synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
- synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
- synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +35 -32
- synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
- synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
- synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
- synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
- synth_ai/lm/vendors/supported/__init__.py +0 -0
- synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
- synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
- synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
- synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
- synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
- synth_ai/tracing/__init__.py +0 -0
- synth_ai/tracing/abstractions.py +224 -0
- synth_ai/tracing/base_client.py +91 -0
- synth_ai/tracing/client_manager.py +131 -0
- synth_ai/tracing/config.py +140 -0
- synth_ai/tracing/context.py +146 -0
- synth_ai/tracing/decorators.py +679 -0
- synth_ai/tracing/events/__init__.py +0 -0
- synth_ai/tracing/events/manage.py +147 -0
- synth_ai/tracing/events/scope.py +86 -0
- synth_ai/tracing/events/store.py +227 -0
- synth_ai/tracing/immediate_client.py +152 -0
- synth_ai/tracing/local.py +18 -0
- synth_ai/tracing/log_client_base.py +74 -0
- synth_ai/tracing/retry_queue.py +187 -0
- synth_ai/tracing/trackers.py +515 -0
- synth_ai/tracing/upload.py +504 -0
- synth_ai/tracing/utils.py +9 -0
- synth_ai/zyk/__init__.py +28 -2
- synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
- synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai-0.2.0.dist-info/METADATA +0 -36
- synth_ai-0.2.0.dist-info/RECORD +0 -50
- /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
- /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
- /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
- /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
- /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
- {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
- {synth_ai-0.2.0.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"])
|