evolvingmachines-evolve 0.0.55.dev1355__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.
@@ -0,0 +1,167 @@
1
+ """Standalone sessions() client for historical traces and past sessions."""
2
+
3
+ import asyncio
4
+ from typing import Any, Dict, List, Literal, Optional
5
+
6
+ from .results import BrowserReplay, SessionEvent, SessionInfo, SessionPage, SessionTranscript
7
+ from .utils import (
8
+ _filter_none,
9
+ _require_browser_replay,
10
+ _require_session_info,
11
+ _require_session_transcript,
12
+ )
13
+
14
+
15
+ class SessionsClient:
16
+ """Client for listing historical sessions and downloading traces.
17
+
18
+ Created via the standalone ``sessions()`` factory only.
19
+ Wraps bridge JSON-RPC calls to the TypeScript SDK's sessions() client.
20
+
21
+ Gateway-only: requires ``EVOLVE_API_KEY`` unless ``SessionsConfig(api_key=...)``
22
+ is provided.
23
+
24
+ Example::
25
+
26
+ from evolve import sessions
27
+
28
+ async with sessions() as client:
29
+ page = await client.list(limit=20, state='ended')
30
+ events = await client.events(page.items[0].id)
31
+ path = await client.download(page.items[0].id, to='./traces')
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ bridge: Any,
37
+ sessions_config: Any = None,
38
+ *,
39
+ _owns_bridge: bool = False,
40
+ ):
41
+ self._bridge = bridge
42
+ self._config = sessions_config
43
+ self._owns_bridge = _owns_bridge
44
+ self._started = False
45
+ self._init_lock = asyncio.Lock()
46
+
47
+ async def _ensure_ready(self) -> None:
48
+ """Ensure the bridge is started and ready to handle RPC calls."""
49
+ async with self._init_lock:
50
+ if self._started:
51
+ return
52
+ await self._bridge.start()
53
+ self._started = True
54
+
55
+ def _build_params(self, **kwargs: Any) -> Dict[str, Any]:
56
+ """Build RPC params, including standalone sessions config."""
57
+ params: Dict[str, Any] = {}
58
+ if self._config is not None:
59
+ params['sessions'] = self._config.to_dict()
60
+ params.update(_filter_none(kwargs))
61
+ return params
62
+
63
+ async def list(
64
+ self,
65
+ limit: Optional[int] = None,
66
+ cursor: Optional[str] = None,
67
+ state: Optional[Literal['live', 'ended', 'all']] = None,
68
+ agent: Optional[str] = None,
69
+ tag_prefix: Optional[str] = None,
70
+ sort: Optional[Literal['newest', 'oldest', 'cost']] = None,
71
+ ) -> SessionPage:
72
+ """List historical sessions with optional filtering and pagination."""
73
+ await self._ensure_ready()
74
+ params = self._build_params(
75
+ limit=limit,
76
+ cursor=cursor,
77
+ state=state,
78
+ agent=agent,
79
+ tag_prefix=tag_prefix,
80
+ sort=sort,
81
+ )
82
+ response = await self._bridge.call('sessions_list', params)
83
+ return SessionPage(
84
+ items=[_require_session_info(item) for item in response.get('items', [])],
85
+ next_cursor=response.get('next_cursor'),
86
+ has_more=bool(response.get('has_more', False)),
87
+ )
88
+
89
+ async def get(self, id: str) -> SessionInfo:
90
+ """Get a single session's metadata by ID."""
91
+ await self._ensure_ready()
92
+ response = await self._bridge.call('sessions_get', self._build_params(id=id))
93
+ return _require_session_info(response)
94
+
95
+ async def events(
96
+ self,
97
+ id: str,
98
+ *,
99
+ since: Optional[int] = None,
100
+ ) -> List[SessionEvent]:
101
+ """Fetch parsed JSONL events for a historical session."""
102
+ await self._ensure_ready()
103
+ response = await self._bridge.call('sessions_events', self._build_params(id=id, since=since))
104
+ return list(response.get('events', []))
105
+
106
+ async def transcript(
107
+ self,
108
+ id: str,
109
+ *,
110
+ since: Optional[int] = None,
111
+ ) -> SessionTranscript:
112
+ """The transcript feed in one read: the ``session``, its ``events``
113
+ after ``since``, the ``total`` stored, and the gateway meter's per-call
114
+ ``gateway_calls`` (see :class:`SessionTranscript`)."""
115
+ await self._ensure_ready()
116
+ response = await self._bridge.call('sessions_transcript', self._build_params(id=id, since=since))
117
+ return _require_session_transcript(response)
118
+
119
+ async def download(
120
+ self,
121
+ id: str,
122
+ *,
123
+ to: Optional[str] = None,
124
+ ) -> str:
125
+ """Download a session's raw JSONL trace file. Returns the local path."""
126
+ await self._ensure_ready()
127
+ response = await self._bridge.call('sessions_download', self._build_params(id=id, to=to))
128
+ return response['path']
129
+
130
+ async def browser_replay(
131
+ self,
132
+ id: str,
133
+ *,
134
+ timeout_ms: Optional[int] = None,
135
+ interval_ms: Optional[int] = None,
136
+ ) -> BrowserReplay:
137
+ """Wait for browser replay readiness and return replay/download URLs."""
138
+ await self._ensure_ready()
139
+ effective_timeout_ms = timeout_ms if timeout_ms is not None else 600_000
140
+ effective_interval_ms = interval_ms if interval_ms is not None else 5_000
141
+ if effective_timeout_ms <= 0:
142
+ raise ValueError('timeout_ms must be positive')
143
+ if effective_interval_ms <= 0:
144
+ raise ValueError('interval_ms must be positive')
145
+ rpc_timeout_s = max(1.0, (effective_timeout_ms / 1000) + 30)
146
+ response = await self._bridge.call(
147
+ 'sessions_browser_replay',
148
+ self._build_params(id=id, timeout_ms=timeout_ms, interval_ms=interval_ms),
149
+ timeout_s=rpc_timeout_s,
150
+ )
151
+ return _require_browser_replay(response)
152
+
153
+ async def close(self) -> None:
154
+ """Close the sessions client and release resources."""
155
+ if self._owns_bridge:
156
+ await self._bridge.stop()
157
+
158
+ async def __aenter__(self) -> 'SessionsClient':
159
+ try:
160
+ await self._ensure_ready()
161
+ return self
162
+ except Exception:
163
+ await self.close()
164
+ raise
165
+
166
+ async def __aexit__(self, *args: Any) -> None:
167
+ await self.close()
@@ -0,0 +1,178 @@
1
+ """StorageClient for browsing and downloading checkpoints."""
2
+
3
+ import asyncio
4
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
5
+
6
+ from .results import CheckpointInfo
7
+ from .utils import _decode_files_from_transport, _filter_none, _parse_checkpoint, _require_checkpoint
8
+
9
+
10
+ class StorageClient:
11
+ """Client for browsing and downloading checkpoints.
12
+
13
+ Created via the standalone ``storage()`` factory or ``Evolve.storage()`` accessor.
14
+ Wraps bridge JSON-RPC calls to the TypeScript SDK's StorageClient.
15
+
16
+ Two creation modes:
17
+
18
+ - **Standalone** (``storage(config)``): Owns its own bridge subprocess.
19
+ Use as an async context manager to ensure cleanup.
20
+ - **Bound** (``evolve.storage()``): Reuses the Evolve instance's bridge.
21
+ No cleanup needed — the Evolve instance manages the bridge.
22
+
23
+ Example (standalone)::
24
+
25
+ from evolve import storage, StorageConfig
26
+
27
+ async with storage(StorageConfig(url='s3://my-bucket/')) as store:
28
+ checkpoints = await store.list_checkpoints(limit=5)
29
+ files = await store.download_files(checkpoints[0].id)
30
+
31
+ Example (bound)::
32
+
33
+ async with Evolve(storage=StorageConfig(url='s3://bucket/')) as evolve:
34
+ store = evolve.storage()
35
+ info = await store.get_checkpoint('ckpt_abc123')
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ bridge: Any,
41
+ storage_config: Any = None,
42
+ *,
43
+ _owns_bridge: bool = False,
44
+ _init_fn: Optional[Callable[[], Awaitable[None]]] = None,
45
+ ):
46
+ self._bridge = bridge
47
+ self._config = storage_config # None = use Evolve's initialized config
48
+ self._owns_bridge = _owns_bridge
49
+ # Readiness callback — in bound mode this triggers Evolve._ensure_initialized
50
+ # so the bridge adapter has a live Evolve instance to delegate to.
51
+ self._init_fn = _init_fn
52
+ self._started = False
53
+ self._init_lock = asyncio.Lock()
54
+
55
+ async def _ensure_ready(self) -> None:
56
+ """Ensure the bridge is started and ready to handle RPC calls."""
57
+ async with self._init_lock:
58
+ if self._started:
59
+ return
60
+ if self._init_fn is not None:
61
+ await self._init_fn()
62
+ else:
63
+ await self._bridge.start()
64
+ self._started = True
65
+
66
+ def _build_params(self, **kwargs: Any) -> Dict[str, Any]:
67
+ """Build RPC params, including storage config if in standalone mode."""
68
+ params: Dict[str, Any] = {}
69
+ if self._config is not None:
70
+ params['storage'] = self._config.to_dict()
71
+ params.update(_filter_none(kwargs))
72
+ return params
73
+
74
+ async def list_checkpoints(
75
+ self,
76
+ limit: Optional[int] = None,
77
+ tag: Optional[str] = None,
78
+ ) -> List[CheckpointInfo]:
79
+ """List checkpoints sorted by newest first.
80
+
81
+ Args:
82
+ limit: Maximum number of checkpoints to return (default: 100, max: 500)
83
+ tag: Filter by session tag
84
+
85
+ Returns:
86
+ List of CheckpointInfo sorted by newest first
87
+ """
88
+ await self._ensure_ready()
89
+ params = self._build_params(limit=limit, tag=tag)
90
+ response = await self._bridge.call('storage_list_checkpoints', params)
91
+ return [_require_checkpoint(cp) for cp in response]
92
+
93
+ async def get_checkpoint(self, id: str) -> CheckpointInfo:
94
+ """Get checkpoint metadata by ID.
95
+
96
+ Args:
97
+ id: Checkpoint ID
98
+
99
+ Returns:
100
+ CheckpointInfo with full metadata
101
+
102
+ Raises:
103
+ Exception: If checkpoint not found
104
+ """
105
+ await self._ensure_ready()
106
+ params = self._build_params(id=id)
107
+ response = await self._bridge.call('storage_get_checkpoint', params)
108
+ return _require_checkpoint(response)
109
+
110
+ async def download_checkpoint(
111
+ self,
112
+ id: str,
113
+ *,
114
+ to: Optional[str] = None,
115
+ extract: bool = True,
116
+ ) -> str:
117
+ """Download a checkpoint archive.
118
+
119
+ Args:
120
+ id: Checkpoint ID or ``"latest"``
121
+ to: Target directory (default: current working directory)
122
+ extract: Extract the tar.gz archive (default: True).
123
+ If False, saves the raw .tar.gz file.
124
+
125
+ Returns:
126
+ Path to the extracted directory or saved archive file
127
+ """
128
+ await self._ensure_ready()
129
+ params = self._build_params(id=id, to=to, extract=extract if not extract else None)
130
+ response = await self._bridge.call('storage_download_checkpoint', params)
131
+ return response['path']
132
+
133
+ async def download_files(
134
+ self,
135
+ id: str,
136
+ *,
137
+ files: Optional[List[str]] = None,
138
+ glob: Optional[List[str]] = None,
139
+ to: Optional[str] = None,
140
+ ) -> Dict[str, Union[str, bytes]]:
141
+ """Download specific files from a checkpoint.
142
+
143
+ For large checkpoints, prefer :meth:`download_checkpoint` which streams
144
+ the archive to disk instead of loading all files into memory.
145
+
146
+ Args:
147
+ id: Checkpoint ID or ``"latest"``
148
+ files: Exact file paths to extract (e.g., ``["workspace/data.txt"]``)
149
+ glob: Glob patterns to match (e.g., ``["workspace/*.txt"]``)
150
+ to: Write files to this directory instead of returning in-memory
151
+
152
+ Returns:
153
+ Dict mapping file path to content (str for text, bytes for binary)
154
+ """
155
+ await self._ensure_ready()
156
+ params = self._build_params(id=id, files=files, glob=glob, to=to)
157
+ response = await self._bridge.call('storage_download_files', params)
158
+ return _decode_files_from_transport(response.get('files', {}))
159
+
160
+ async def close(self) -> None:
161
+ """Close the storage client and release resources.
162
+
163
+ Only needed for standalone clients (created via ``storage()``).
164
+ Bound clients (from ``Evolve.storage()``) are managed by the Evolve instance.
165
+ """
166
+ if self._owns_bridge:
167
+ await self._bridge.stop()
168
+
169
+ async def __aenter__(self) -> 'StorageClient':
170
+ try:
171
+ await self._ensure_ready()
172
+ return self
173
+ except Exception:
174
+ await self.close()
175
+ raise
176
+
177
+ async def __aexit__(self, *args: Any) -> None:
178
+ await self.close()
@@ -0,0 +1,75 @@
1
+ """Swarm abstractions for parallel AI agent execution."""
2
+
3
+ from .swarm import Swarm
4
+ from .types import (
5
+ FileMap,
6
+ SwarmConfig,
7
+ BestOfConfig,
8
+ VerifyConfig,
9
+ IndexedMeta,
10
+ ReduceMeta,
11
+ JudgeMeta,
12
+ VerifyMeta,
13
+ BaseMeta,
14
+ OperationType,
15
+ Prompt,
16
+ PromptFn,
17
+ ItemInput,
18
+ SchemaType,
19
+ JudgeDecision,
20
+ VerifyDecision,
21
+ # Callback types
22
+ OnCandidateCompleteCallback,
23
+ OnJudgeCompleteCallback,
24
+ OnWorkerCompleteCallback,
25
+ OnVerifierCompleteCallback,
26
+ )
27
+ from .results import (
28
+ SwarmResult,
29
+ SwarmResultList,
30
+ ReduceResult,
31
+ BestOfResult,
32
+ BestOfInfo,
33
+ VerifyInfo,
34
+ is_swarm_result,
35
+ SWARM_RESULT_BRAND,
36
+ )
37
+
38
+ __all__ = [
39
+ # Main class
40
+ 'Swarm',
41
+ # Config types
42
+ 'SwarmConfig',
43
+ 'BestOfConfig',
44
+ 'VerifyConfig',
45
+ # Result types
46
+ 'SwarmResult',
47
+ 'SwarmResultList',
48
+ 'ReduceResult',
49
+ 'BestOfResult',
50
+ 'BestOfInfo',
51
+ 'VerifyInfo',
52
+ # Meta types
53
+ 'IndexedMeta',
54
+ 'ReduceMeta',
55
+ 'JudgeMeta',
56
+ 'VerifyMeta',
57
+ 'BaseMeta',
58
+ 'OperationType',
59
+ # Other types
60
+ 'FileMap',
61
+ 'Prompt',
62
+ 'PromptFn',
63
+ 'ItemInput',
64
+ 'SchemaType',
65
+ 'JudgeDecision',
66
+ 'VerifyDecision',
67
+ # Callback types
68
+ 'OnCandidateCompleteCallback',
69
+ 'OnJudgeCompleteCallback',
70
+ 'OnWorkerCompleteCallback',
71
+ 'OnVerifierCompleteCallback',
72
+ # Helpers
73
+ 'is_swarm_result',
74
+ 'SWARM_RESULT_BRAND',
75
+ ]
@@ -0,0 +1,140 @@
1
+ """Result types for Swarm abstractions."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union
5
+
6
+ from .types import FileMap, IndexedMeta, ReduceMeta, JudgeMeta, VerifyMeta
7
+
8
+
9
+ T = TypeVar('T')
10
+
11
+ # Brand for runtime detection of SwarmResult (for chaining)
12
+ SWARM_RESULT_BRAND = "__swarm_result__"
13
+
14
+
15
+ # =============================================================================
16
+ # SWARM RESULT
17
+ # =============================================================================
18
+
19
+ @dataclass
20
+ class SwarmResult(Generic[T]):
21
+ """Result from a single worker (map, filter, bestof candidate).
22
+
23
+ Status meanings:
24
+ - "success": Positive outcome (agent succeeded / condition passed)
25
+ - "filtered": Neutral outcome (evaluated but didn't pass condition) - filter only
26
+ - "error": Negative outcome (agent error)
27
+ """
28
+ status: Literal["success", "filtered", "error"]
29
+ data: Optional[T]
30
+ files: FileMap
31
+ meta: IndexedMeta
32
+ error: Optional[str] = None
33
+ raw_data: Optional[str] = None
34
+ best_of: Optional['BestOfInfo'] = None
35
+ verify: Optional['VerifyInfo'] = None
36
+
37
+ # Brand for runtime detection (used by is_swarm_result() function)
38
+ __swarm_result__: bool = field(default=True, repr=False)
39
+
40
+
41
+ @dataclass
42
+ class BestOfInfo(Generic[T]):
43
+ """BestOf information attached to SwarmResult when map used bestOf option."""
44
+ winner_index: int
45
+ judge_reasoning: str
46
+ judge_meta: JudgeMeta
47
+ candidates: List[SwarmResult[T]]
48
+
49
+
50
+ @dataclass
51
+ class VerifyInfo:
52
+ """Verification info attached to results when verify option used."""
53
+ passed: bool
54
+ reasoning: str
55
+ verify_meta: VerifyMeta
56
+ attempts: int
57
+
58
+
59
+ # =============================================================================
60
+ # SWARM RESULT LIST
61
+ # =============================================================================
62
+
63
+ class SwarmResultList(List[SwarmResult[T]], Generic[T]):
64
+ """List of SwarmResults with helper properties.
65
+
66
+ Extends list so all normal list operations work.
67
+
68
+ Getters:
69
+ - `.success` - items with positive outcome
70
+ - `.filtered` - items that didn't pass condition (filter only)
71
+ - `.error` - items that encountered errors
72
+
73
+ Chaining examples:
74
+ - `swarm.reduce(results.success, ...)` - forward only successful
75
+ - `swarm.reduce([*results.success, *results.filtered], ...)` - forward all evaluated
76
+ """
77
+
78
+ @property
79
+ def success(self) -> List[SwarmResult[T]]:
80
+ """Returns items with status 'success'."""
81
+ return [r for r in self if r.status == "success"]
82
+
83
+ @property
84
+ def filtered(self) -> List[SwarmResult[T]]:
85
+ """Returns items with status 'filtered' (didn't pass condition)."""
86
+ return [r for r in self if r.status == "filtered"]
87
+
88
+ @property
89
+ def error(self) -> List[SwarmResult[T]]:
90
+ """Returns items with status 'error'."""
91
+ return [r for r in self if r.status == "error"]
92
+
93
+ @classmethod
94
+ def from_results(cls, results: List[SwarmResult[T]]) -> 'SwarmResultList[T]':
95
+ """Create SwarmResultList from a list of SwarmResults."""
96
+ result_list = cls()
97
+ result_list.extend(results)
98
+ return result_list
99
+
100
+
101
+ # =============================================================================
102
+ # REDUCE RESULT
103
+ # =============================================================================
104
+
105
+ @dataclass
106
+ class ReduceResult(Generic[T]):
107
+ """Result from reduce operation."""
108
+ status: Literal["success", "error"]
109
+ data: Optional[T]
110
+ files: FileMap
111
+ meta: ReduceMeta
112
+ error: Optional[str] = None
113
+ raw_data: Optional[str] = None
114
+ verify: Optional['VerifyInfo'] = None
115
+
116
+
117
+ # =============================================================================
118
+ # BESTOF RESULT
119
+ # =============================================================================
120
+
121
+ @dataclass
122
+ class BestOfResult(Generic[T]):
123
+ """Result from bestOf operation."""
124
+ winner: SwarmResult[T]
125
+ winner_index: int
126
+ judge_reasoning: str
127
+ judge_meta: JudgeMeta
128
+ candidates: List[SwarmResult[T]]
129
+
130
+
131
+ # =============================================================================
132
+ # HELPER FUNCTIONS
133
+ # =============================================================================
134
+
135
+ def is_swarm_result(obj: Any) -> bool:
136
+ """Check if an object is a SwarmResult (for chaining detection)."""
137
+ return (
138
+ isinstance(obj, SwarmResult) or
139
+ (hasattr(obj, SWARM_RESULT_BRAND) and getattr(obj, SWARM_RESULT_BRAND) is True)
140
+ )