@evolvingmachines/evolve 0.0.55-project-sable.20260916.db0bc0bb → 0.0.56

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 (73) hide show
  1. package/dist/cli/index.cjs +32 -32
  2. package/dist/cli/index.js +27 -27
  3. package/package.json +4 -6
  4. package/skills/evolve/SKILL.md +4 -3
  5. package/skills/evolve-evals/SKILL.md +112 -0
  6. package/{docs-evals → skills/evolve-evals/references}/cli-reference/run.mdx +1 -1
  7. package/{docs-evals → skills/evolve-evals/references}/cli-reference/skills.mdx +4 -4
  8. package/{docs-evals → skills/evolve-evals/references}/core-concepts/agents.mdx +2 -2
  9. package/{docs-evals → skills/evolve-evals/references}/core-concepts/analyze.mdx +1 -1
  10. package/{docs-evals → skills/evolve-evals/references}/core-concepts/jobs.mdx +3 -3
  11. package/{docs-evals → skills/evolve-evals/references}/core-concepts/models.mdx +1 -1
  12. package/{docs-evals → skills/evolve-evals/references}/core-concepts/sandboxes.mdx +2 -2
  13. package/{docs-evals → skills/evolve-evals/references}/core-concepts/secrets.mdx +1 -1
  14. package/{docs-evals → skills/evolve-evals/references}/core-concepts/trial-outputs.mdx +1 -1
  15. package/{docs-evals → skills/evolve-evals/references}/getting-started/quick-start.mdx +1 -1
  16. package/{docs-evals → skills/evolve-evals/references}/index.mdx +1 -1
  17. package/{docs-evals → skills/evolve-evals/references}/sdk/python.mdx +1 -1
  18. package/{docs-evals → skills/evolve-evals/references}/sdk/typescript.mdx +1 -1
  19. package/{docs-evals → skills/evolve-evals/references}/sdk-reference/errors.mdx +2 -2
  20. package/{docs-evals → skills/evolve-evals/references}/sdk-reference/jobs.mdx +2 -2
  21. package/docs-agents/SKILL.md +0 -138
  22. package/docs-agents/index.md +0 -81
  23. package/docs-agents/python/01-getting-started.md +0 -511
  24. package/docs-agents/python/02-configuration.md +0 -855
  25. package/docs-agents/python/03-runtime.md +0 -899
  26. package/docs-agents/python/04-streaming.md +0 -451
  27. package/docs-agents/python/05-swarm-pipeline.md +0 -953
  28. package/docs-agents/python/index.md +0 -122
  29. package/docs-agents/typescript/01-getting-started.md +0 -494
  30. package/docs-agents/typescript/02-configuration.md +0 -841
  31. package/docs-agents/typescript/03-runtime.md +0 -864
  32. package/docs-agents/typescript/04-streaming.md +0 -478
  33. package/docs-agents/typescript/05-swarm-pipeline.md +0 -957
  34. package/docs-agents/typescript/index.md +0 -121
  35. package/docs-evals/SKILL.md +0 -112
  36. package/docs-evals/docs.json +0 -180
  37. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/agent.mdx +0 -0
  38. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/analysis.mdx +0 -0
  39. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/analyze.mdx +0 -0
  40. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/auth.mdx +0 -0
  41. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/check.mdx +0 -0
  42. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/dataset.mdx +0 -0
  43. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/index.mdx +0 -0
  44. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/job.mdx +0 -0
  45. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/secrets.mdx +0 -0
  46. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/skill.mdx +0 -0
  47. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/trial.mdx +0 -0
  48. /package/{docs-evals → skills/evolve-evals/references}/cli-reference/upload.mdx +0 -0
  49. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/check.mdx +0 -0
  50. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/datasets.mdx +0 -0
  51. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/index.mdx +0 -0
  52. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/skills.mdx +0 -0
  53. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/tasks.mdx +0 -0
  54. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/trials.mdx +0 -0
  55. /package/{docs-evals → skills/evolve-evals/references}/core-concepts/upload.mdx +0 -0
  56. /package/{docs-evals → skills/evolve-evals/references}/dashboard/checks.mdx +0 -0
  57. /package/{docs-evals → skills/evolve-evals/references}/dashboard/datasets.mdx +0 -0
  58. /package/{docs-evals → skills/evolve-evals/references}/dashboard/jobs.mdx +0 -0
  59. /package/{docs-evals → skills/evolve-evals/references}/dashboard/traces.mdx +0 -0
  60. /package/{docs-evals → skills/evolve-evals/references}/dashboard/trial-viewer.mdx +0 -0
  61. /package/{docs-evals → skills/evolve-evals/references}/getting-started/installation.mdx +0 -0
  62. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/agents.mdx +0 -0
  63. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/analyses.mdx +0 -0
  64. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/auth.mdx +0 -0
  65. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/checks.mdx +0 -0
  66. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/datasets.mdx +0 -0
  67. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/index.mdx +0 -0
  68. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/meta.mdx +0 -0
  69. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/skills.mdx +0 -0
  70. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/trials.mdx +0 -0
  71. /package/{docs-evals → skills/evolve-evals/references}/sdk-reference/types.mdx +0 -0
  72. /package/{docs-evals → skills/evolve-evals/references}/snippets/global-options.mdx +0 -0
  73. /package/{docs-evals → skills/evolve-evals/references}/snippets/list-flags.mdx +0 -0
@@ -1,899 +0,0 @@
1
- # Runtime
2
-
3
- ## Methods
4
-
5
- `run()` and `execute_command()` are async and return `AgentResponse`. `status()` is async and returns `SessionStatus`. `interrupt()` returns `bool`.
6
-
7
- ```python
8
- @dataclass
9
- class AgentResponse:
10
- sandbox_id: str
11
- exit_code: int
12
- stdout: str
13
- stderr: str
14
- session_id: str | None # Dashboard session ID for traces/replays, when known
15
- browser: dict[str, str] | None # live_url, when remote browser is configured
16
- checkpoint: CheckpointInfo | None # Present when storage= configured and run succeeded
17
-
18
- @dataclass
19
- class SessionStatus:
20
- sandbox_id: str | None
21
- sandbox: str
22
- agent: str
23
- active_process_id: str | None
24
- has_run: bool
25
- timestamp: str
26
- browser: dict[str, str] | None # live_url/session_id/session_tag, when available
27
- ```
28
-
29
- ### run
30
-
31
- Runs the agent with a given prompt.
32
-
33
- ```python
34
- result = await evolve.run(
35
- prompt='Analyze the data and create a report',
36
- timeout_ms=15 * 60 * 1000, # (optional) Default 1 hour
37
- background=False, # (optional) Run in background
38
- from_checkpoint='ckpt_abc123', # (optional) Restore from checkpoint ID or 'latest'
39
- checkpoint_comment='after analysis', # (optional) Label for the auto-checkpoint
40
- resume=False, # (optional) Force a fresh conversation
41
- )
42
-
43
- print(result.exit_code)
44
- print(result.stdout)
45
- print(result.checkpoint.id if result.checkpoint else None) # Checkpoint ID (if storage= configured)
46
- ```
47
-
48
- - If `timeout_ms` is omitted the agent uses the default of 3_600_000 ms (1 hour).
49
- - If `background` is `True`, the call returns immediately with a start handshake (`exit_code=0`), not final completion. Completion is delivered asynchronously via `lifecycle` events (`run_background_complete` or `run_background_failed`) or by polling `status()`.
50
- - If `from_checkpoint` is set, the SDK restores a checkpoint into a fresh sandbox before running. Pass a checkpoint ID or `'latest'` to restore the most recent. Requires `storage=`. Cannot be used with `sandbox_id=`.
51
- - If `checkpoint_comment` is set, the auto-checkpoint created after a successful run is labeled with this string. Requires `storage=`.
52
- - Calling `run()` multiple times maintains the agent context / history.
53
- - `resume` overrides that. Omitted, the first run in a sandbox starts a fresh conversation and every run after it continues the previous one (an attached `sandbox_id=` sandbox counts as "has already run"). Pass `resume=False` to force a fresh conversation in a sandbox the agent has already worked in — the shape a sequence of INDEPENDENT tasks against one shared sandbox needs, where the environment should persist but the context should not. Pass `resume=True` only when a previous run really happened; asking a CLI to continue a session that does not exist is its own kind of error.
54
- - Calling `run()` while another run or command is active throws immediately. Call `interrupt()` first or wait for the active operation to finish.
55
-
56
- ### execute_command
57
-
58
- Runs a direct shell command in the sandbox working directory.
59
-
60
- ```python
61
- # Run shell command directly in sandbox
62
- result = await evolve.execute_command(
63
- command='pytest',
64
- timeout_ms=10 * 60 * 1000, # (optional) Default 1 hour
65
- background=False, # (optional) Run in background
66
- )
67
- ```
68
-
69
- - If `background` is `True`, returns a start handshake (`exit_code=0`). Completion arrives via `lifecycle` events (`command_background_complete` or `command_background_failed`).
70
-
71
- ### Streaming Events
72
-
73
- Subscribe to real-time output from `run()` and `execute_command()`:
74
-
75
- ```python
76
- evolve.on('content', lambda event: print(event['update']['sessionUpdate']))
77
- evolve.on('lifecycle', lambda event: print(event['reason'], event['sandbox']))
78
- ```
79
-
80
- | Event | Type | Description |
81
- |-------|------|-------------|
82
- | `content` | `OutputEvent` | Parsed ACP-style events (text, tools, plans) |
83
- | `lifecycle` | `LifecycleEvent` | Sandbox and agent state transitions |
84
- | `stdout` | `str` | Raw JSONL output |
85
- | `stderr` | `str` | Error output |
86
-
87
- For full type definitions, all event interfaces, browser live view, replay, and UI integration example, see [Streaming Events](./04-streaming.md).
88
-
89
- ### Upload: Local → Sandbox
90
-
91
- **Format:** `{"destination": content}` — directories created automatically
92
-
93
- | Method | Destination |
94
- |--------|-------------|
95
- | `upload_context()` | `/home/user/workspace/context/{path}` |
96
- | `upload_files()` | `/home/user/workspace/{path}` |
97
-
98
- ```python
99
- # Single file
100
- await evolve.upload_context({'spec.json': json.dumps(data)})
101
-
102
- # Multiple files
103
- await evolve.upload_files({
104
- 'scripts/setup.sh': '#!/bin/bash\necho hello',
105
- 'data/input.csv': csv_bytes,
106
- })
107
-
108
- # From local directory (helper)
109
- from evolve import read_local_dir
110
- await evolve.upload_context(read_local_dir('./input', recursive=True))
111
- ```
112
-
113
- > **Setup alternative:** Constructor parameters `context` and `files` use the same format but upload on first `run()` instead of immediately.
114
-
115
- ### Download: Sandbox → Local
116
-
117
- **Flow:** `get_output_files()` → `save_local_dir()`
118
-
119
- ```python
120
- # Return type
121
- @dataclass
122
- class OutputResult:
123
- files: dict # All files from output/ folder
124
- data: Any | None # Parsed result.json (if schema was set via schema=)
125
- error: str | None # Validation error message (if schema validation failed)
126
- raw_data: str | None # Raw result.json content when parse/validation failed (for debugging)
127
- ```
128
-
129
- ```python
130
- from pydantic import BaseModel
131
- from evolve import Evolve, save_local_dir
132
-
133
- class ResultSchema(BaseModel):
134
- summary: str
135
- score: float
136
-
137
- evolve = Evolve(
138
- config=AgentConfig(...),
139
- schema=ResultSchema, # Agent will be prompted to write result.json
140
- )
141
-
142
- await evolve.run(prompt='Analyze and score the document')
143
-
144
- output = await evolve.get_output_files(recursive=True) # recursive=True for nested dirs
145
-
146
- # Access all fields
147
- save_local_dir('./output', output.files) # Save files locally
148
- print(output.data) # ResultSchema(summary='...', score=85.0)
149
- print(output.error) # None (or validation error message)
150
- ```
151
-
152
- - **`files`** — dict of all files from `output/` folder
153
- - **`data`** — Parsed `result.json` validated against schema (None if no schema or validation failed). For Pydantic schemas, returns a model instance.
154
- - **`error`** — Validation error message if schema validation failed (None otherwise)
155
- - **`raw_data`** — Raw result.json content when parse/validation failed (for debugging)
156
-
157
- Files created before the last `run()` or `execute_command()` are filtered out.
158
-
159
- `save_local_dir()` confines every entry to the target directory: a name whose resolved path escapes it (`../`, absolute) raises `ValueError` instead of writing outside the directory you chose — file names come from sandbox output and are not trusted.
160
-
161
- ### Session Controls
162
-
163
- ```python
164
- session_id = await evolve.get_session() # Returns sandbox ID (str) or None
165
-
166
- status = await evolve.status() # Runtime status snapshot
167
- # status.sandbox -> "stopped" | "booting" | "ready" | "running" | "paused" | "error"
168
- # status.agent -> "idle" | "running" | "interrupted" | "error"
169
- # status.has_run -> bool
170
- # status.sandbox_id -> str | None
171
- # status.active_process_id -> str | None
172
- # status.timestamp -> str (ISO 8601)
173
-
174
- ok = await evolve.interrupt() # Interrupts active run() or execute_command() process; keeps sandbox alive. Returns bool.
175
-
176
- # Steer a running task: interrupt, then reprompt in same session.
177
- # The next run() auto-continues conversation history/context for this sandbox session.
178
- await evolve.run(prompt='Do a full migration plan', background=True)
179
- await evolve.interrupt()
180
- await evolve.run(prompt='Change direction: only auth migration.')
181
-
182
- await evolve.pause() # Suspends sandbox (stops billing, preserves state)
183
- await evolve.resume() # Reactivates same sandbox
184
-
185
- await evolve.kill() # Destroys sandbox; next run() creates a new sandbox
186
-
187
- await evolve.set_session('existing-sandbox-id') # Sets sandbox ID; reconnection happens on next run()
188
-
189
- # Checkpointing (requires storage=)
190
- ckpt = await evolve.checkpoint(comment='before refactor') # Explicit snapshot of current sandbox
191
- checkpoints = await evolve.list_checkpoints(limit=10) # List checkpoints, newest first
192
- files = await evolve.storage().download_files('latest', glob=['workspace/**/*.py']) # Download specific files
193
- ```
194
-
195
- `sandbox_id` is a constructor parameter for initialization—it sets the sandbox ID before the first `run()`. `set_session()` is a runtime method that actively interrupts any running process, flushes the session log, resets checkpoint lineage, and switches to the new sandbox. They are **not** interchangeable: use `sandbox_id=` when constructing, `set_session()` when switching mid-session.
196
-
197
- **Provider caveats:**
198
- - **E2B / Daytona** — full support for `pause()`, `resume()`, `interrupt()`.
199
- - **Modal** — does not support `pause()`. `interrupt()` is effectively unsupported and returns `False` for active processes.
200
-
201
- ### get_host
202
-
203
- Expose a forwarded port:
204
-
205
- ```python
206
- url = await evolve.get_host(8000)
207
- print(f'Workspace service available at {url}')
208
- ```
209
- ---
210
-
211
- ## Workspace & Structured Output
212
-
213
- Calling `run` or `execute_command` for the first time provisions a sandbox with the following filesystem:
214
-
215
- ```
216
- /home/user/workspace/
217
- ├── context/ # Input files (read-only) provided by the user
218
- ├── scripts/ # Your code goes here
219
- ├── temp/ # Scratch space
220
- ├── output/ # Final deliverables
221
- └── CLAUDE.md # System prompt (or AGENT.md, GEMINI.md, QWEN.md depending on agent)
222
- ```
223
-
224
- Files passed to `context` are uploaded to `context/`. Files passed to `files` are uploaded relative to the working directory.
225
-
226
- ## Filesystem Instructions
227
- Evolve writes a default filesystem instructions to the agent's config file in the workspace (`CLAUDE.md`, `AGENT.md`, `GEMINI.md`, or `QWEN.md`):
228
-
229
- ```
230
- ## FILESYSTEM INSTRUCTIONS
231
-
232
- You are running in a sandbox environment.
233
-
234
- Present working directory: /home/user/workspace/
235
-
236
- IMPORTANT - Directory structure:
237
- /home/user/workspace/
238
- ├── context/ # Input files (read-only) provided by the user
239
- ├── scripts/ # Your code goes here
240
- ├── temp/ # Scratch space
241
- └── output/ # Final deliverables
242
-
243
- ## OUTPUT RESULTS (DELIVERABLES) MUST BE SAVED to `output/` as files.
244
- ```
245
-
246
- Any string passed to `system_prompt` is automatically appended to the agent's config file in the workspace (`CLAUDE.md`, `AGENT.md`, `GEMINI.md`, or `QWEN.md`) after this default.
247
-
248
- ## Structured Output
249
-
250
- When you provide a `schema`, Evolve instructs the agent to write structured JSON output.
251
-
252
- ```python
253
- from pydantic import BaseModel
254
-
255
- class CREData(BaseModel):
256
- property_name: str
257
- units: int
258
- total_rent: float
259
- occupancy_rate: float
260
-
261
- evolve = Evolve(
262
- schema=CREData,
263
- context={
264
- 'rent_roll.pdf': open('rent_roll.pdf', 'rb').read(),
265
- },
266
- )
267
-
268
- await evolve.run(prompt='Extract CRE data from the rent roll')
269
-
270
- output = await evolve.get_output_files()
271
- print(output.data) # CREData(property_name='...', units=120, ...)
272
- ```
273
-
274
- When a schema is provided, `get_output_files()` automatically validates `output/result.json` and returns `OutputResult` (see [Download: Sandbox → Local](#download-sandbox--local)).
275
-
276
- ```python
277
- # Type-safe access to validated data
278
- if output.data:
279
- print(output.data.property_name) # Pydantic model instance
280
- else:
281
- print(output.error) # "Schema validation failed: ..."
282
- print(output.raw_data) # Raw JSON for debugging
283
- ```
284
-
285
- The SDK automatically appends the following to the agent's config file in the workspace (`CLAUDE.md`, `AGENT.md`, `GEMINI.md`, or `QWEN.md`):
286
-
287
- ~~~
288
- ## STRUCTURED OUTPUT
289
-
290
- Your final result MUST be saved to `output/result.json` following this schema:
291
-
292
- ```json
293
- {
294
- "type": "object",
295
- "properties": {
296
- "property_name": { "type": "string" },
297
- "units": { "type": "integer" },
298
- "total_rent": { "type": "number" },
299
- "occupancy_rate": { "type": "number" }
300
- },
301
- "required": ["property_name", "units", "total_rent", "occupancy_rate"]
302
- }
303
- ```
304
-
305
- You are free to:
306
- - Reason through the problem step by step
307
- - Read and analyze context files
308
- - Use any available tools
309
- - Process incrementally
310
- - Create intermediate files in `temp/` or `scripts/`
311
-
312
- But your final `output/result.json` MUST conform to the schema above.
313
-
314
- ### OUTPUT RESULTS (DELIVERABLES) MUST BE WRITTEN to `output/result.json` as files.
315
- ### Never just state results as text.
316
- ~~~
317
-
318
- ---
319
-
320
- ## Session Management
321
-
322
- **Multi-turn conversations** (most common):
323
-
324
- ```python
325
- evolve = Evolve(
326
- config=AgentConfig(...),
327
- )
328
-
329
- await evolve.run(prompt='Analyze data.csv')
330
- output = await evolve.get_output_files()
331
-
332
- # Still same session, automatically maintains context / history
333
- await evolve.run(prompt='Now create visualization')
334
- output2 = await evolve.get_output_files()
335
-
336
- # Still same session, automatically maintains context / history
337
- await evolve.run(prompt='Export to PDF')
338
- output3 = await evolve.get_output_files()
339
-
340
- await evolve.kill() # When done
341
- ```
342
-
343
- **One-shot tasks** (automatic cleanup):
344
-
345
- ```python
346
- async with evolve:
347
- result = await evolve.run(prompt='...')
348
- output = await evolve.get_output_files()
349
- # Calls kill() automatically via __aexit__()
350
- ```
351
-
352
- **Pause and resume** (same instance):
353
-
354
- ```python
355
- evolve = Evolve(
356
- config=AgentConfig(...),
357
- )
358
-
359
- await evolve.run(prompt='Start analysis')
360
- await evolve.pause() # Suspend billing, keep state
361
- # Do other work...
362
- await evolve.resume() # Reactivate same sandbox
363
- await evolve.run(prompt='Continue analysis') # Session intact
364
-
365
- await evolve.kill() # Kill the Sandbox when done
366
- ```
367
-
368
- **Save and reconnect** (different script/session):
369
-
370
- ```python
371
- # Script 1: Save session for later
372
- evolve = Evolve(
373
- config=AgentConfig(...),
374
- )
375
-
376
- await evolve.run(prompt='Start analysis')
377
-
378
- session_id = await evolve.get_session()
379
- # Save to file, database, environment variable, etc.
380
- with open('session.txt', 'w') as session_file:
381
- session_file.write(session_id)
382
-
383
- # Script 2: Reconnect to saved session
384
- with open('session.txt') as session_file:
385
- saved_id = session_file.read()
386
-
387
- evolve2 = Evolve(
388
- config=AgentConfig(...),
389
- sandbox_id=saved_id # Reconnect
390
- )
391
-
392
- await evolve2.run(prompt='Continue analysis') # Session continues from Script 1
393
- ```
394
-
395
- **Switch between sandboxes** (same instance):
396
-
397
- ```python
398
- evolve = Evolve(
399
- config=AgentConfig(...),
400
- )
401
-
402
- # Work with first sandbox
403
- await evolve.run(prompt='Analyze dataset A')
404
- session_a = await evolve.get_session()
405
-
406
- # Switch to different sandbox
407
- await evolve.set_session('existing-sandbox-b-id')
408
- await evolve.run(prompt='Analyze dataset B') # Now working with sandbox B
409
-
410
- # Switch back to first sandbox
411
- await evolve.set_session(session_a)
412
- await evolve.run(prompt='Compare results') # Back to sandbox A
413
- ```
414
-
415
- ---
416
-
417
- ## Storage & Checkpointing
418
-
419
- > **Gateway feature** — requires `EVOLVE_API_KEY`. Storage is fully managed by Evolve; no S3 buckets or AWS credentials needed.
420
-
421
- Persist sandbox state beyond sandbox lifetime. Checkpoints archive specific directories under `/home/user/` to Evolve-managed storage and can be restored into a fresh sandbox.
422
-
423
- **What gets checkpointed:**
424
- - `/home/user/workspace/` — your project files
425
- - `/home/user/.<agent>/` — agent settings and session history (e.g. `.claude/`, `.codex/`, `.gemini/`, `.qwen/`, `.kimi-code/`, `.factory/`)
426
- - For OpenCode: XDG directories (`~/.local/share/opencode/`, `~/.config/opencode/`, `~/.local/state/opencode/`)
427
- - For Kimi Code: `.kimi-code/config.toml` is excluded because Evolve rewrites gateway credentials before each run; session history and MCP config are still included.
428
-
429
- **Key properties:**
430
- - **Auto-checkpoint:** Every successful `run()` with `storage=` creates a checkpoint automatically.
431
- - **Content-addressed dedup:** Archives are hashed (SHA-256). Same content = skip upload.
432
- - **Lineage tracking:** Each checkpoint records its `parent_id`, forming a chain across runs and restores.
433
-
434
- ### Configuration
435
-
436
- ```python
437
- evolve = Evolve(
438
- config=AgentConfig(type='claude'),
439
- storage=StorageConfig(), # Uses EVOLVE_API_KEY from env
440
- )
441
- ```
442
-
443
- ### Auto-Checkpoint (via `run()`)
444
-
445
- Every successful foreground `run()` auto-creates a checkpoint:
446
-
447
- ```python
448
- result = await evolve.run(
449
- prompt='Build the report',
450
- checkpoint_comment='initial draft',
451
- )
452
-
453
- print(result.checkpoint.id) # 'ckpt_m5abc_xyz123'
454
- print(result.checkpoint.hash) # SHA-256 of archive
455
- print(result.checkpoint.comment) # 'initial draft'
456
- ```
457
-
458
- **Behavior notes:**
459
-
460
- - **Non-fatal:** Auto-checkpoint failures are logged but never cause `run()` to throw. The run result will have `checkpoint` as `None`.
461
- - **Foreground only:** Background runs (`background=True`) skip auto-checkpointing entirely.
462
- - **Exclusions:** The archive excludes `node_modules/`, `__pycache__/`, `*.pyc`, `.cache/`, `.npm/`, `.pip/`, `.venv/`, `venv/`, and `{workspace}/temp/` to keep snapshots lean.
463
- - **Dedup:** Archives are content-addressed by SHA-256 hash. If the hash matches an existing archive in storage, the upload is skipped—only the metadata entry is written.
464
- - **`from_checkpoint='latest'` edge case:** If no checkpoints exist globally (across all sessions/tags), `from_checkpoint='latest'` throws an error. Note that `'latest'` resolves to the globally newest checkpoint, not scoped to the current session tag. Use `storage().list_checkpoints()` first to check availability.
465
-
466
- ### Explicit Checkpoint
467
-
468
- Snapshot at any point (between runs, after manual setup, etc.):
469
-
470
- ```python
471
- ckpt = await evolve.checkpoint(comment='before refactor')
472
- print(ckpt.id) # 'ckpt_m5def_abc456'
473
- ```
474
-
475
- Requires an active sandbox (`run()` must have been called first).
476
-
477
- ### Restore from Checkpoint
478
-
479
- Pass `from_checkpoint` to `run()` to restore a checkpoint into a fresh sandbox before running:
480
-
481
- ```python
482
- result = await evolve.run(
483
- prompt='Continue where we left off',
484
- from_checkpoint='ckpt_m5abc_xyz123',
485
- )
486
-
487
- # Or restore the most recent checkpoint
488
- latest = await evolve.run(
489
- prompt='Pick up from latest state',
490
- from_checkpoint='latest',
491
- )
492
- ```
493
-
494
- - `from_checkpoint` creates a fresh sandbox, downloads the archive, verifies hash integrity, and extracts it.
495
- - Cannot be used with `sandbox_id=` (restore requires a fresh sandbox).
496
- - The restored checkpoint becomes the `parent_id` for the next checkpoint, maintaining lineage.
497
- - Agent type and workspace mode must match the checkpoint (model changes are fine).
498
-
499
- ### Listing & Browsing Checkpoints
500
-
501
- **Instance method:**
502
-
503
- ```python
504
- checkpoints = await evolve.list_checkpoints(
505
- limit=10, # (optional) default: 100, max: 500
506
- tag='my-session-tag', # (optional) filter by session tag
507
- )
508
- ```
509
-
510
- **Standalone `storage()` client** (no Evolve instance needed):
511
-
512
- ```python
513
- from evolve import storage
514
-
515
- async with storage() as store: # Uses EVOLVE_API_KEY from env
516
- checkpoints = await store.list_checkpoints()
517
- ```
518
-
519
- The `storage()` factory returns a `StorageClient` with four methods:
520
-
521
- ```python
522
- # List checkpoints (newest first)
523
- checkpoints = await store.list_checkpoints(limit=10, tag='my-session')
524
-
525
- # Get a single checkpoint by ID
526
- info = await store.get_checkpoint('ckpt_m5abc_xyz123')
527
-
528
- # Download full checkpoint archive to a local directory
529
- output_dir = await store.download_checkpoint('ckpt_m5abc_xyz123',
530
- to='./restored', # (optional) default: cwd
531
- extract=True, # (optional) default: True — set False to keep raw .tar.gz
532
- )
533
-
534
- # Download specific files without extracting the full archive
535
- files = await store.download_files('ckpt_m5abc_xyz123',
536
- files=['workspace/output/result.json'], # (optional) exact paths
537
- glob=['workspace/**/*.py'], # (optional) glob patterns
538
- to='./output', # (optional) save to disk
539
- )
540
- # files is a dict[str, str | bytes] — relative path → file contents
541
- ```
542
-
543
- Pass `'latest'` instead of a checkpoint ID to any method to resolve the most recent checkpoint.
544
-
545
- **Downloading folders with glob patterns:**
546
-
547
- ```python
548
- output = await store.download_files(id, glob=['workspace/output/**'])
549
- all_files = await store.download_files(id, glob=['workspace/**'])
550
-
551
- # Save directly to disk
552
- await store.download_files(id, glob=['workspace/output/**'], to='./local-output')
553
- ```
554
-
555
- > **Paths are relative to `/home/user/`** — use `workspace/...` not `/home/user/workspace/...`.
556
-
557
- **Instance-bound `storage()` accessor:**
558
-
559
- When you already have an Evolve instance, `evolve.storage()` returns a `StorageClient` with credentials automatically bound:
560
-
561
- ```python
562
- store = evolve.storage()
563
- files = await store.download_files('latest', glob=['workspace/report.*'])
564
- ```
565
-
566
- **Standalone `list_checkpoints()`** (convenience shortcut for listing only):
567
-
568
- ```python
569
- from evolve import list_checkpoints, StorageConfig
570
-
571
- recent = await list_checkpoints(StorageConfig(), limit=5)
572
- ```
573
-
574
- ### Checkpoint Lineage
575
-
576
- Each checkpoint records `parent_id`. Consecutive runs build a chain:
577
-
578
- ```python
579
- r1 = await evolve.run(prompt='Step 1')
580
- # r1.checkpoint.parent_id → None (first)
581
-
582
- r2 = await evolve.run(prompt='Step 2')
583
- # r2.checkpoint.parent_id → r1.checkpoint.id
584
- ```
585
-
586
- Restoring from a checkpoint branches the lineage:
587
-
588
- ```python
589
- r4 = await evolve.run(prompt='Branch from step 1', from_checkpoint=r1.checkpoint.id)
590
- # r4.checkpoint.parent_id → r1.checkpoint.id (not r3)
591
- ```
592
-
593
- ### Type Reference
594
-
595
- ```python
596
- @dataclass
597
- class CheckpointInfo:
598
- id: str # Checkpoint ID — pass as from_checkpoint to restore
599
- hash: str # SHA-256 of tar.gz archive
600
- tag: str # Session tag at checkpoint time
601
- timestamp: str # ISO 8601
602
- size_bytes: int | None # Archive size in bytes
603
- agent_type: str | None # 'claude' | 'codex' | 'gemini' | 'qwen' | 'kimi' | 'opencode' | 'droid'
604
- model: str | None # Model used
605
- workspace_mode: str | None # 'knowledge' | 'swe'
606
- parent_id: str | None # Parent checkpoint ID (lineage)
607
- comment: str | None # User-provided label
608
-
609
- class StorageClient:
610
- async def list_checkpoints(limit=None, tag=None) -> list[CheckpointInfo]
611
- async def get_checkpoint(id: str) -> CheckpointInfo
612
- async def download_checkpoint(id: str, *, to=None, extract=True) -> str
613
- async def download_files(id: str, *, files=None, glob=None, to=None) -> dict[str, str | bytes]
614
-
615
- # download_checkpoint options
616
- to: str | None # Output directory (default: cwd)
617
- extract: bool # Extract archive (default: True)
618
-
619
- # download_files options
620
- files: list[str] | None # Exact file paths to extract
621
- glob: list[str] | None # Glob patterns to match files
622
- to: str | None # Save to disk (default: in-memory only)
623
- ```
624
-
625
- ### End-to-End Example
626
-
627
- ```python
628
- from evolve import Evolve, AgentConfig, StorageConfig, storage
629
-
630
- # 1. Create and checkpoint
631
- async with Evolve(
632
- config=AgentConfig(type='claude'),
633
- storage=StorageConfig(),
634
- ) as evolve:
635
- r1 = await evolve.run(
636
- prompt="Create a file called report.txt with 'Draft v1'",
637
- checkpoint_comment='initial draft',
638
- )
639
- print('Checkpoint 1:', r1.checkpoint.id)
640
-
641
- # 2. Second run — auto-chains parent_id
642
- r2 = await evolve.run(
643
- prompt="Append ' - reviewed' to report.txt",
644
- checkpoint_comment='reviewed',
645
- )
646
- print('Parent:', r2.checkpoint.parent_id) # → r1.checkpoint.id
647
-
648
- # 3. Restore into fresh sandbox
649
- async with Evolve(
650
- config=AgentConfig(type='claude'),
651
- storage=StorageConfig(),
652
- ) as evolve2:
653
- r3 = await evolve2.run(
654
- prompt='Read report.txt — what does it say?',
655
- from_checkpoint=r1.checkpoint.id,
656
- )
657
- # Agent sees 'Draft v1' (not the reviewed version)
658
-
659
- # 4. Browse checkpoints and download files (no Evolve instance needed)
660
- async with storage() as store:
661
- all_checkpoints = await store.list_checkpoints()
662
- print(f'{len(all_checkpoints)} checkpoints (newest first)')
663
-
664
- files = await store.download_files('latest', glob=['workspace/report.*'])
665
- for path, content in files.items():
666
- print(f'{path}: {content}')
667
- ```
668
-
669
- ---
670
-
671
- ## Observability
672
-
673
- Full execution traces—including tool calls, file operations (read/write/edit), text responses, and reasoning chunks—are logged to your Evolve dashboard at **https://dashboard.evolvingmachines.ai/traces** for debugging and replay.
674
-
675
- Additionally, every run and command is logged locally to structured JSON lines under `~/.evolve-sdk/observability/sessions`. File name format:
676
-
677
- ```
678
- {tag}_{provider}_{sandboxId}_{agent}_{timestamp}.jsonl
679
- ```
680
-
681
- - `{tag}` – `my-prefix-` + 16 random hex characters (e.g. `my-prefix-a1b2c3d4e5f6g7h8`)
682
- - `{provider}` – the sandbox provider (e.g. `e2b`)
683
- - `{sandboxId}` – the active sandbox ID
684
- - `{agent}` – the agent type (`codex`, `claude`, `gemini`, `qwen`, `kimi`, `opencode`, `droid`)
685
- - `{timestamp}` – ISO timestamp with `:` and `.` replaced by `-`
686
-
687
- Each file contains three entry types:
688
-
689
- ```json
690
- {"_meta":{"tag":"my-prefix-a1b2c3d4","provider":"e2b","agent":"qwen","model":"qwen-coder-plus-latest","sandbox_id":"sbx_123","timestamp":"2025-10-26T20:15:17.984Z"}}
691
- {"_prompt":{"text":"hello how are you?"}}
692
- {"jsonrpc":"2.0","method":"session/update", ...}
693
- ```
694
-
695
- - `_meta` – exactly one line per file (sandbox, agent, timestamp)
696
- - `_prompt` – one line per `run()` call with the prompt text
697
- - Raw JSON – every streamed payload (ACP notifications, stdout, etc.)
698
-
699
- Attach your own prefix to make logs easy to search:
700
-
701
- ```python
702
- evolve = Evolve(
703
- config=AgentConfig(...),
704
- session_tag_prefix='my-project'
705
- )
706
-
707
- await evolve.run(prompt='Kick off analysis')
708
-
709
- print(await evolve.get_session_tag()) # "my-project-ab12cd34"
710
- print(await evolve.get_session_timestamp()) # Timestamp for first log file
711
-
712
- await evolve.kill() # Destroys sandbox A
713
-
714
- await evolve.run(prompt='Start fresh') # New sandbox → new log file
715
-
716
- print(await evolve.get_session_tag()) # "my-project-f56789cd"
717
- print(await evolve.get_session_timestamp()) # Timestamp for second log file
718
- ```
719
-
720
- - `kill()` or `set_session()` flushes the current log; the next `run()` starts a
721
- fresh file with the new sandbox id.
722
- - Long-running sessions (pause/resume or ACP auto-resume) keep appending to the
723
- current file, so you always have the full timeline.
724
- - Logging is buffered inside the SDK, so it never blocks streaming output.
725
-
726
- Use the tag together with the sandbox id to correlate logs with files saved in
727
- `/output/`.
728
-
729
- ### Historical Sessions & Trace Download
730
-
731
- Gateway mode also exposes a standalone `sessions()` client for querying past
732
- sessions and downloading full trace files, even after the sandbox has ended.
733
-
734
- ```python
735
- from evolve import sessions
736
-
737
- async with sessions() as session:
738
- page = await session.list(limit=20, state='ended', tag_prefix='my-project')
739
-
740
- if page.items:
741
- info = await session.get(page.items[0].id)
742
- recent_events = await session.events(info.id, since=10)
743
- transcript = await session.transcript(info.id)
744
- path = await session.download(info.id, to='./traces')
745
- replay = await session.browser_replay(info.id)
746
-
747
- print(info.runtime_status) # 'alive' | 'dead' | 'unknown'
748
- print(len(recent_events)) # Parsed JSONL objects
749
- print(path) # ./traces/{tag}.jsonl
750
- print(replay.replay_url) # Browser replay URL
751
- ```
752
-
753
- The CLI wraps the same client headless — `evolve session list` (`--state live|ended`, `--agent`, `--tag-prefix`, paged with `--limit`/`--cursor`, `-q` for ids, `--json` for the page) and `evolve session show <id>` — with no Python code involved.
754
-
755
- The `sessions()` factory returns a `SessionsClient` with six methods:
756
-
757
- ```python
758
- page = await session.list(
759
- limit=20,
760
- cursor=None,
761
- state='all', # 'live' | 'ended' | 'all'
762
- agent='claude',
763
- tag_prefix='my-project',
764
- sort='newest', # 'newest' | 'oldest' | 'cost'
765
- )
766
-
767
- info = await session.get('session-id')
768
- events = await session.events('session-id', since=50)
769
- transcript = await session.transcript('session-id', since=50)
770
- path = await session.download('session-id', to='./traces')
771
- replay = await session.browser_replay(
772
- 'session-id',
773
- timeout_ms=600_000, # optional; default 10 minutes
774
- interval_ms=5_000, # optional; default 5 seconds
775
- )
776
- ```
777
-
778
- - `list()` returns `SessionPage(items, next_cursor, has_more)`
779
- - `get()` returns `SessionInfo` with snake_case fields such as `sandbox_id`,
780
- `runtime_status`, `created_at`, and `tool_stats` — plus `usage`, the
781
- one-home reading (spend so far + token breakdown from the same gateway
782
- records, `provisional` marking numbers that can still grow); it carries the
783
- same keys a trial's `usage` does, and `None` means the meter never answered
784
- - `events()` returns parsed JSONL objects for programmatic inspection
785
- - `transcript()` is the same read whole: `SessionTranscript(session, events, total, gateway_calls, stored_at=None)` — `total` counts every stored event (the next delta's `since`), and `gateway_calls` are the gateway meter's per-call lines (the spec's `GatewayUsageEvent`, its own camelCase keys: `call['update']['usage']` carries `promptTokens`, `completionTokens`, `cachedTokens`, `costUsd`), in time order, the same line a trial's trace carries; they ride beside `events`, never inside them, and are the only per-call tokens and money a client should show. `stored_at` is the server's write instant of each event's row, one per entry of `events` and index-aligned: present on every row-served page (an empty page carries an empty list), `None` when the transcript was served from its file. It places the gateway meter's calls under the harness's steps for harnesses whose lines carry no clock of their own (codex, kimi, qwen); a reader that does not place calls needs nothing from it. The session's total stays on `session.usage` / `session.cost`.
786
- - `download()` saves the raw `.jsonl` trace file to disk and returns the path
787
- - `browser_replay()` waits for the managed browser replay and returns
788
- `replay_url` plus `download_url`
789
- - Use `replay_url` in your UI for browser playback
790
- - Use `download_url` when users need the raw `.mp4` file
791
- - `suggested_start_seconds`, when present, is the recommended replay start time in seconds and is already applied to `replay_url`; keep the raw download unchanged
792
- - `size_bytes` and `ready_at`, when present, describe the raw recording size and replay readiness time
793
- - `status` is `'ready'` once the call returns
794
-
795
- For the full browser setup, live-view, cleanup, and replay flow, see
796
- [Configuration → Browser Automation](./02-configuration.md#browser-automation).
797
-
798
- This API is **gateway-only**. In Direct Provider Key Mode, historical traces remain
799
- available via local JSONL files in `~/.evolve-sdk/observability/sessions/`.
800
-
801
- ---
802
-
803
- ## Cost Tracking
804
-
805
- Query per-run and per-session LLM spend. Requires gateway mode (`EVOLVE_API_KEY`). Supported for Claude and Codex agents.
806
-
807
- Cost data may take 5–60s to appear while the gateway finishes metering (typically under 30s).
808
-
809
- ```python
810
- from evolve import Evolve
811
-
812
- evolve = Evolve()
813
-
814
- # Each run() returns a run_id for cost attribution
815
- r1 = await evolve.run(prompt="Analyze the data")
816
- r2 = await evolve.run(prompt="Write tests")
817
-
818
- # Session cost — all runs
819
- session = await evolve.get_session_cost()
820
- print(session.total_cost) # 0.42 (USD)
821
- print(session.total_tokens) # {'prompt': 5000, 'completion': 2000, 'cached': 3200}
822
- print(len(session.runs)) # 2
823
-
824
- # Run cost — by ID
825
- cost = await evolve.get_run_cost(run_id=r1.run_id)
826
- print(cost.cost, cost.model, cost.requests)
827
-
828
- # Run cost — by index (1-based, negative = from end)
829
- first = await evolve.get_run_cost(index=1)
830
- last = await evolve.get_run_cost(index=-1)
831
-
832
- await evolve.kill()
833
- ```
834
-
835
- After `kill()`, the bridge process is stopped. To query costs for a completed session, create a new Evolve instance — the TS bridge handles `previousSessionTag` fallback internally. After another `run()` cycle, the previous session's cost is no longer queryable.
836
-
837
- ### Types
838
-
839
- ```python
840
- @dataclass
841
- class RunCost:
842
- run_id: str # Matches AgentResponse.run_id
843
- index: int # 1-based chronological position
844
- cost: float # USD as billed to your Evolve account
845
- tokens: Dict[str, int] # {'prompt': N, 'completion': N, 'cached': N} — prompt INCLUDES cached
846
- model: str # Last observed model for this run
847
- requests: int # Number of LLM API requests
848
- as_of: str # ISO timestamp of query
849
- is_complete: bool # False if calls still batching (5–60s)
850
- truncated: bool # True if spend logs were capped
851
-
852
- @dataclass
853
- class SessionCost:
854
- session_tag: str # Matches get_session_tag()
855
- total_cost: float # USD across all runs
856
- total_tokens: Dict[str, int] # {'prompt': N, 'completion': N, 'cached': N}
857
- runs: List[RunCost] # Chronological order
858
- as_of: str
859
- is_complete: bool
860
- truncated: bool
861
- ```
862
-
863
- ---
864
-
865
- ## Error Handling
866
-
867
- Common errors and how to handle them:
868
-
869
- | Error | Cause | Fix |
870
- |-------|-------|-----|
871
- | `No API key configured` | No `EVOLVE_API_KEY` or provider key in env | Set `EVOLVE_API_KEY` or pass `api_key`/`provider_api_key` to `AgentConfig` |
872
- | `No sandbox provider configured` | No sandbox provider key in env | Set `E2B_API_KEY`, `MODAL_TOKEN_ID`+`SECRET`, or `DAYTONA_API_KEY` |
873
- | `Evolve agent config: "model" is empty` | `AgentConfig(model='')` — an empty string is not a model id | Pass a model id, or drop `model` to take the agent's default |
874
- | `run() requires a non-empty "prompt" string` | `run()` called with an empty prompt | Pass a non-empty `prompt` |
875
- | `Operation already active` | Calling `run()` while another run is in progress | `await evolve.interrupt()` first, or wait for the active operation |
876
- | `Cannot use 'from_checkpoint' with existing session` | `run(from_checkpoint=...)` with `sandbox_id=` | Checkpoint restore requires a fresh sandbox — remove `sandbox_id=` |
877
- | `No checkpoints found` | `run(from_checkpoint='latest')` with no prior checkpoints | Create a checkpoint first, or use `list_checkpoints()` to verify |
878
- | `Schema validation failed: ...` | Agent's `result.json` doesn't match schema | Check `output.raw_data` for the actual output; refine your prompt or schema |
879
- | `Storage requires EVOLVE_API_KEY` | `storage=StorageConfig()` without gateway credentials | Set `EVOLVE_API_KEY` in your environment |
880
- | Timeout (exit code -1) | Agent exceeded `timeout_ms` | Increase `timeout_ms` or simplify the prompt |
881
-
882
- ```python
883
- # Handling schema validation errors
884
- output = await evolve.get_output_files()
885
- if output.error:
886
- print(f'Validation failed: {output.error}')
887
- print(f'Raw output: {output.raw_data}') # Agent's actual JSON for debugging
888
-
889
- # Handling run errors
890
- try:
891
- result = await evolve.run(prompt='...')
892
- if result.exit_code != 0:
893
- print(f'Agent failed: {result.stderr}')
894
- except Exception as err:
895
- # Raised for: no API key, no sandbox, operation conflict, restore failure
896
- print(err)
897
- ```
898
-
899
- ---