snapstate-sdk 1.0.0__tar.gz

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,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: snapstate-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the SnapState workflow state persistence service
5
+ License: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx>=0.25.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=7.0; extra == "dev"
14
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
15
+
16
+ # snapstate-sdk (Python)
17
+
18
+ Python SDK for the [Checkpoint Service](https://github.com/your-org/snapstate) — a workflow state persistence API that lets AI agents save and resume multi-step work across interruptions, crashes, and handoffs between agents.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install snapstate-sdk
24
+ ```
25
+
26
+ Requires Python 3.9+ and `httpx>=0.25.0` (installed automatically).
27
+
28
+ ## Quick start (sync)
29
+
30
+ ```python
31
+ from snapstate_sdk import SnapStateClient
32
+
33
+ client = SnapStateClient(
34
+ api_key="snp_your_key_here",
35
+ base_url="http://localhost:3000",
36
+ )
37
+
38
+ # Save state after each step
39
+ result = client.save(
40
+ workflow_id="wf_research_001",
41
+ step=1,
42
+ label="sources_gathered",
43
+ state={"sources": ["arxiv.org/123"], "progress": 0.25},
44
+ )
45
+ print(f"Saved: {result.checkpoint_id} (etag: {result.etag})")
46
+
47
+ # Resume a workflow — get the latest state
48
+ resumed = client.resume("wf_research_001")
49
+ print(f"Resuming from step {resumed.latest_checkpoint.step}")
50
+ state = resumed.latest_checkpoint.state
51
+
52
+ # Get full checkpoint history
53
+ history = client.replay("wf_research_001")
54
+ for cp in history.checkpoints:
55
+ print(f" Step {cp.step}: {cp.label}")
56
+
57
+ client.close()
58
+ ```
59
+
60
+ ## Async usage
61
+
62
+ Every method has an `async_` prefixed equivalent:
63
+
64
+ ```python
65
+ import asyncio
66
+ from snapstate_sdk import SnapStateClient
67
+
68
+ async def main():
69
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
70
+
71
+ result = await client.async_save(
72
+ workflow_id="wf_001",
73
+ step=1,
74
+ state={"status": "running"},
75
+ )
76
+ print(f"Saved: {result.checkpoint_id}")
77
+
78
+ resumed = await client.async_resume("wf_001")
79
+ print(f"Latest step: {resumed.latest_checkpoint.step}")
80
+
81
+ await client.async_close()
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ Context managers are supported for both sync and async usage:
87
+
88
+ ```python
89
+ # Sync
90
+ with SnapStateClient(api_key="snp_...") as client:
91
+ client.save(workflow_id="wf_001", step=1, state={})
92
+
93
+ # Async
94
+ async with SnapStateClient(api_key="snp_...") as client:
95
+ await client.async_save(workflow_id="wf_001", step=1, state={})
96
+ ```
97
+
98
+ ## Agent identity
99
+
100
+ Register your agent once at startup, then tag checkpoints with `agent_id`:
101
+
102
+ ```python
103
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
104
+
105
+ # Register agent identity
106
+ client.register_agent(
107
+ agent_id="research-bot",
108
+ name="Research Bot",
109
+ description="Searches and summarizes sources",
110
+ capabilities=["web_search", "summarization"],
111
+ metadata={"model": "claude-sonnet-4-6", "version": "2.0.0"},
112
+ )
113
+
114
+ # Tag checkpoints with agent identity
115
+ client.save(
116
+ workflow_id="wf_collab_001",
117
+ step=1,
118
+ state={"findings": [...]},
119
+ agent_id="research-bot", # identity tag
120
+ )
121
+
122
+ # Another agent picks up the workflow
123
+ resumed = client.resume("wf_collab_001")
124
+ prior_agent = resumed.latest_checkpoint.metadata.get("agent_id")
125
+ print(f"Picking up from: {prior_agent}")
126
+ ```
127
+
128
+ ## Error handling
129
+
130
+ ```python
131
+ from snapstate_sdk import SnapStateClient
132
+ from snapstate_sdk.errors import (
133
+ AuthError,
134
+ NotFoundError,
135
+ ConflictError,
136
+ RateLimitError,
137
+ PayloadTooLargeError,
138
+ ValidationError,
139
+ SnapStateError, # base class
140
+ )
141
+
142
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
143
+
144
+ try:
145
+ resumed = client.resume("wf_missing")
146
+ except NotFoundError:
147
+ print("No prior state — starting fresh")
148
+
149
+ try:
150
+ client.save(
151
+ workflow_id="wf_001",
152
+ step=2,
153
+ state={"data": "..."},
154
+ if_match="old-etag", # optimistic concurrency
155
+ )
156
+ except ConflictError:
157
+ print("State was modified by another agent — re-read and retry")
158
+
159
+ try:
160
+ client.save(workflow_id="wf_001", step=1, state={})
161
+ except AuthError:
162
+ print("Check your API key")
163
+ except RateLimitError as e:
164
+ print(f"Rate limited — retry after {e.retry_after}s")
165
+ except PayloadTooLargeError:
166
+ print("State exceeds 1 MB limit — consider compressing")
167
+ except SnapStateError as e:
168
+ print(f"Unexpected error: {e} (HTTP {e.status_code}, code={e.code})")
169
+ ```
170
+
171
+ ## API reference
172
+
173
+ ### `SnapStateClient(api_key, base_url, timeout, max_retries)`
174
+
175
+ | Parameter | Type | Default | Description |
176
+ |-----------|------|---------|-------------|
177
+ | `api_key` | `str` | required | API key starting with `snp_` |
178
+ | `base_url` | `str` | `http://localhost:3000` | Checkpoint Service base URL |
179
+ | `timeout` | `float` | `30.0` | Request timeout in seconds |
180
+ | `max_retries` | `int` | `3` | Retry attempts on 429 before raising `RateLimitError` |
181
+
182
+ ---
183
+
184
+ ### Checkpoint methods
185
+
186
+ #### `save(workflow_id, step, state, label, metadata, agent_id, ttl_seconds, if_match) → SaveResult`
187
+
188
+ Save state for a workflow step.
189
+
190
+ | Parameter | Type | Default | Description |
191
+ |-----------|------|---------|-------------|
192
+ | `workflow_id` | `str` | required | Unique workflow identifier |
193
+ | `step` | `int` | required | Sequential step number |
194
+ | `state` | `dict` | required | Full state to persist (max 1 MB) |
195
+ | `label` | `str` | `None` | Human-readable step label |
196
+ | `metadata` | `dict` | `None` | Arbitrary metadata |
197
+ | `agent_id` | `str` | `None` | Agent identity tag |
198
+ | `ttl_seconds` | `int` | `None` | Override default TTL |
199
+ | `if_match` | `str` | `None` | ETag for optimistic concurrency |
200
+
201
+ Returns `SaveResult(checkpoint_id, workflow_id, step, etag, created_at, diff_from_previous, size_bytes)`.
202
+
203
+ #### `get(checkpoint_id) → Checkpoint`
204
+
205
+ Fetch a specific checkpoint by ID.
206
+
207
+ #### `resume(workflow_id) → WorkflowResume`
208
+
209
+ Get the latest checkpoint for a workflow. Raises `NotFoundError` if no checkpoints exist.
210
+
211
+ Returns `WorkflowResume(workflow_id, latest_checkpoint, total_checkpoints, workflow_started_at, last_activity_at)`.
212
+
213
+ #### `replay(workflow_id, from_step, to_step, limit) → WorkflowReplay`
214
+
215
+ Get the full ordered checkpoint history.
216
+
217
+ Returns `WorkflowReplay(workflow_id, checkpoints, total, has_more)`.
218
+
219
+ ---
220
+
221
+ ### Agent methods
222
+
223
+ #### `register_agent(agent_id, name, description, capabilities, metadata) → Agent`
224
+
225
+ Register or update an agent (upsert). Safe to call on every startup.
226
+
227
+ #### `list_agents() → list[Agent]`
228
+
229
+ List all agents for this account.
230
+
231
+ ---
232
+
233
+ ### Webhook methods
234
+
235
+ #### `register_webhook(url, events, secret) → dict`
236
+
237
+ Register a webhook URL. `events` is a list of `checkpoint.saved`, `workflow.resumed`, `workflow.expired`.
238
+
239
+ ---
240
+
241
+ ### Lifecycle
242
+
243
+ #### `close()`
244
+
245
+ Close the sync HTTP client.
246
+
247
+ #### `async_close()`
248
+
249
+ Close the async HTTP client.
250
+
251
+ ---
252
+
253
+ ### Async equivalents
254
+
255
+ Every method above has an `async_` prefixed version:
256
+ `async_save`, `async_get`, `async_resume`, `async_replay`, `async_register_agent`, `async_list_agents`.
257
+
258
+ ---
259
+
260
+ ### Return types
261
+
262
+ | Type | Fields |
263
+ |------|--------|
264
+ | `Checkpoint` | `checkpoint_id, workflow_id, step, label, state, metadata, etag, created_at, diff_from_previous, size_bytes, agent_id` |
265
+ | `WorkflowResume` | `workflow_id, latest_checkpoint, total_checkpoints, workflow_started_at, last_activity_at` |
266
+ | `WorkflowReplay` | `workflow_id, checkpoints, total, has_more` |
267
+ | `Agent` | `agent_id, name, description, capabilities, metadata, last_seen_at, created_at` |
268
+ | `SaveResult` | `checkpoint_id, workflow_id, step, etag, created_at, diff_from_previous, size_bytes` |
269
+
270
+ ### Error types
271
+
272
+ | Exception | HTTP status | When raised |
273
+ |-----------|-------------|-------------|
274
+ | `AuthError` | 401 | Invalid or missing API key |
275
+ | `NotFoundError` | 404 | Resource not found |
276
+ | `ConflictError` | 409 | ETag mismatch (optimistic concurrency) |
277
+ | `PayloadTooLargeError` | 413 | State exceeds 1 MB |
278
+ | `RateLimitError` | 429 | Rate limit exceeded after all retries |
279
+ | `ValidationError` | 400 | Invalid input |
280
+ | `SnapStateError` | any | Base class for all SDK errors |
281
+
282
+ All exceptions expose `.status_code` (int) and `.code` (str, machine-readable error code).
283
+ `RateLimitError` additionally exposes `.retry_after` (int, seconds).
284
+
285
+ ## Running tests
286
+
287
+ ```bash
288
+ pip install "snapstate-sdk[dev]"
289
+ pytest tests/
290
+ ```
@@ -0,0 +1,275 @@
1
+ # snapstate-sdk (Python)
2
+
3
+ Python SDK for the [Checkpoint Service](https://github.com/your-org/snapstate) — a workflow state persistence API that lets AI agents save and resume multi-step work across interruptions, crashes, and handoffs between agents.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install snapstate-sdk
9
+ ```
10
+
11
+ Requires Python 3.9+ and `httpx>=0.25.0` (installed automatically).
12
+
13
+ ## Quick start (sync)
14
+
15
+ ```python
16
+ from snapstate_sdk import SnapStateClient
17
+
18
+ client = SnapStateClient(
19
+ api_key="snp_your_key_here",
20
+ base_url="http://localhost:3000",
21
+ )
22
+
23
+ # Save state after each step
24
+ result = client.save(
25
+ workflow_id="wf_research_001",
26
+ step=1,
27
+ label="sources_gathered",
28
+ state={"sources": ["arxiv.org/123"], "progress": 0.25},
29
+ )
30
+ print(f"Saved: {result.checkpoint_id} (etag: {result.etag})")
31
+
32
+ # Resume a workflow — get the latest state
33
+ resumed = client.resume("wf_research_001")
34
+ print(f"Resuming from step {resumed.latest_checkpoint.step}")
35
+ state = resumed.latest_checkpoint.state
36
+
37
+ # Get full checkpoint history
38
+ history = client.replay("wf_research_001")
39
+ for cp in history.checkpoints:
40
+ print(f" Step {cp.step}: {cp.label}")
41
+
42
+ client.close()
43
+ ```
44
+
45
+ ## Async usage
46
+
47
+ Every method has an `async_` prefixed equivalent:
48
+
49
+ ```python
50
+ import asyncio
51
+ from snapstate_sdk import SnapStateClient
52
+
53
+ async def main():
54
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
55
+
56
+ result = await client.async_save(
57
+ workflow_id="wf_001",
58
+ step=1,
59
+ state={"status": "running"},
60
+ )
61
+ print(f"Saved: {result.checkpoint_id}")
62
+
63
+ resumed = await client.async_resume("wf_001")
64
+ print(f"Latest step: {resumed.latest_checkpoint.step}")
65
+
66
+ await client.async_close()
67
+
68
+ asyncio.run(main())
69
+ ```
70
+
71
+ Context managers are supported for both sync and async usage:
72
+
73
+ ```python
74
+ # Sync
75
+ with SnapStateClient(api_key="snp_...") as client:
76
+ client.save(workflow_id="wf_001", step=1, state={})
77
+
78
+ # Async
79
+ async with SnapStateClient(api_key="snp_...") as client:
80
+ await client.async_save(workflow_id="wf_001", step=1, state={})
81
+ ```
82
+
83
+ ## Agent identity
84
+
85
+ Register your agent once at startup, then tag checkpoints with `agent_id`:
86
+
87
+ ```python
88
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
89
+
90
+ # Register agent identity
91
+ client.register_agent(
92
+ agent_id="research-bot",
93
+ name="Research Bot",
94
+ description="Searches and summarizes sources",
95
+ capabilities=["web_search", "summarization"],
96
+ metadata={"model": "claude-sonnet-4-6", "version": "2.0.0"},
97
+ )
98
+
99
+ # Tag checkpoints with agent identity
100
+ client.save(
101
+ workflow_id="wf_collab_001",
102
+ step=1,
103
+ state={"findings": [...]},
104
+ agent_id="research-bot", # identity tag
105
+ )
106
+
107
+ # Another agent picks up the workflow
108
+ resumed = client.resume("wf_collab_001")
109
+ prior_agent = resumed.latest_checkpoint.metadata.get("agent_id")
110
+ print(f"Picking up from: {prior_agent}")
111
+ ```
112
+
113
+ ## Error handling
114
+
115
+ ```python
116
+ from snapstate_sdk import SnapStateClient
117
+ from snapstate_sdk.errors import (
118
+ AuthError,
119
+ NotFoundError,
120
+ ConflictError,
121
+ RateLimitError,
122
+ PayloadTooLargeError,
123
+ ValidationError,
124
+ SnapStateError, # base class
125
+ )
126
+
127
+ client = SnapStateClient(api_key="snp_...", base_url="http://localhost:3000")
128
+
129
+ try:
130
+ resumed = client.resume("wf_missing")
131
+ except NotFoundError:
132
+ print("No prior state — starting fresh")
133
+
134
+ try:
135
+ client.save(
136
+ workflow_id="wf_001",
137
+ step=2,
138
+ state={"data": "..."},
139
+ if_match="old-etag", # optimistic concurrency
140
+ )
141
+ except ConflictError:
142
+ print("State was modified by another agent — re-read and retry")
143
+
144
+ try:
145
+ client.save(workflow_id="wf_001", step=1, state={})
146
+ except AuthError:
147
+ print("Check your API key")
148
+ except RateLimitError as e:
149
+ print(f"Rate limited — retry after {e.retry_after}s")
150
+ except PayloadTooLargeError:
151
+ print("State exceeds 1 MB limit — consider compressing")
152
+ except SnapStateError as e:
153
+ print(f"Unexpected error: {e} (HTTP {e.status_code}, code={e.code})")
154
+ ```
155
+
156
+ ## API reference
157
+
158
+ ### `SnapStateClient(api_key, base_url, timeout, max_retries)`
159
+
160
+ | Parameter | Type | Default | Description |
161
+ |-----------|------|---------|-------------|
162
+ | `api_key` | `str` | required | API key starting with `snp_` |
163
+ | `base_url` | `str` | `http://localhost:3000` | Checkpoint Service base URL |
164
+ | `timeout` | `float` | `30.0` | Request timeout in seconds |
165
+ | `max_retries` | `int` | `3` | Retry attempts on 429 before raising `RateLimitError` |
166
+
167
+ ---
168
+
169
+ ### Checkpoint methods
170
+
171
+ #### `save(workflow_id, step, state, label, metadata, agent_id, ttl_seconds, if_match) → SaveResult`
172
+
173
+ Save state for a workflow step.
174
+
175
+ | Parameter | Type | Default | Description |
176
+ |-----------|------|---------|-------------|
177
+ | `workflow_id` | `str` | required | Unique workflow identifier |
178
+ | `step` | `int` | required | Sequential step number |
179
+ | `state` | `dict` | required | Full state to persist (max 1 MB) |
180
+ | `label` | `str` | `None` | Human-readable step label |
181
+ | `metadata` | `dict` | `None` | Arbitrary metadata |
182
+ | `agent_id` | `str` | `None` | Agent identity tag |
183
+ | `ttl_seconds` | `int` | `None` | Override default TTL |
184
+ | `if_match` | `str` | `None` | ETag for optimistic concurrency |
185
+
186
+ Returns `SaveResult(checkpoint_id, workflow_id, step, etag, created_at, diff_from_previous, size_bytes)`.
187
+
188
+ #### `get(checkpoint_id) → Checkpoint`
189
+
190
+ Fetch a specific checkpoint by ID.
191
+
192
+ #### `resume(workflow_id) → WorkflowResume`
193
+
194
+ Get the latest checkpoint for a workflow. Raises `NotFoundError` if no checkpoints exist.
195
+
196
+ Returns `WorkflowResume(workflow_id, latest_checkpoint, total_checkpoints, workflow_started_at, last_activity_at)`.
197
+
198
+ #### `replay(workflow_id, from_step, to_step, limit) → WorkflowReplay`
199
+
200
+ Get the full ordered checkpoint history.
201
+
202
+ Returns `WorkflowReplay(workflow_id, checkpoints, total, has_more)`.
203
+
204
+ ---
205
+
206
+ ### Agent methods
207
+
208
+ #### `register_agent(agent_id, name, description, capabilities, metadata) → Agent`
209
+
210
+ Register or update an agent (upsert). Safe to call on every startup.
211
+
212
+ #### `list_agents() → list[Agent]`
213
+
214
+ List all agents for this account.
215
+
216
+ ---
217
+
218
+ ### Webhook methods
219
+
220
+ #### `register_webhook(url, events, secret) → dict`
221
+
222
+ Register a webhook URL. `events` is a list of `checkpoint.saved`, `workflow.resumed`, `workflow.expired`.
223
+
224
+ ---
225
+
226
+ ### Lifecycle
227
+
228
+ #### `close()`
229
+
230
+ Close the sync HTTP client.
231
+
232
+ #### `async_close()`
233
+
234
+ Close the async HTTP client.
235
+
236
+ ---
237
+
238
+ ### Async equivalents
239
+
240
+ Every method above has an `async_` prefixed version:
241
+ `async_save`, `async_get`, `async_resume`, `async_replay`, `async_register_agent`, `async_list_agents`.
242
+
243
+ ---
244
+
245
+ ### Return types
246
+
247
+ | Type | Fields |
248
+ |------|--------|
249
+ | `Checkpoint` | `checkpoint_id, workflow_id, step, label, state, metadata, etag, created_at, diff_from_previous, size_bytes, agent_id` |
250
+ | `WorkflowResume` | `workflow_id, latest_checkpoint, total_checkpoints, workflow_started_at, last_activity_at` |
251
+ | `WorkflowReplay` | `workflow_id, checkpoints, total, has_more` |
252
+ | `Agent` | `agent_id, name, description, capabilities, metadata, last_seen_at, created_at` |
253
+ | `SaveResult` | `checkpoint_id, workflow_id, step, etag, created_at, diff_from_previous, size_bytes` |
254
+
255
+ ### Error types
256
+
257
+ | Exception | HTTP status | When raised |
258
+ |-----------|-------------|-------------|
259
+ | `AuthError` | 401 | Invalid or missing API key |
260
+ | `NotFoundError` | 404 | Resource not found |
261
+ | `ConflictError` | 409 | ETag mismatch (optimistic concurrency) |
262
+ | `PayloadTooLargeError` | 413 | State exceeds 1 MB |
263
+ | `RateLimitError` | 429 | Rate limit exceeded after all retries |
264
+ | `ValidationError` | 400 | Invalid input |
265
+ | `SnapStateError` | any | Base class for all SDK errors |
266
+
267
+ All exceptions expose `.status_code` (int) and `.code` (str, machine-readable error code).
268
+ `RateLimitError` additionally exposes `.retry_after` (int, seconds).
269
+
270
+ ## Running tests
271
+
272
+ ```bash
273
+ pip install "snapstate-sdk[dev]"
274
+ pytest tests/
275
+ ```
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "snapstate-sdk"
3
+ version = "1.0.0"
4
+ description = "Python SDK for the SnapState workflow state persistence service"
5
+ readme = "README.md"
6
+ license = {text = "MIT"}
7
+ requires-python = ">=3.9"
8
+ dependencies = ["httpx>=0.25.0"]
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ dev = ["pytest>=7.0", "pytest-asyncio>=0.21.0"]
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=68.0"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+
25
+ [tool.pytest.ini_options]
26
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,45 @@
1
+ """
2
+ checkpoint-sdk — Python SDK for the Checkpoint workflow state persistence service.
3
+
4
+ Quick start::
5
+
6
+ from checkpoint_sdk import CheckpointClient
7
+
8
+ client = CheckpointClient(api_key="cpk_...", base_url="http://localhost:3000")
9
+
10
+ result = client.save(workflow_id="wf_001", step=1, state={"progress": 0})
11
+ resumed = client.resume("wf_001")
12
+ client.close()
13
+ """
14
+
15
+ from .client import CheckpointClient
16
+ from .errors import (
17
+ AuthError,
18
+ CheckpointError,
19
+ ConflictError,
20
+ NotFoundError,
21
+ PayloadTooLargeError,
22
+ RateLimitError,
23
+ ValidationError,
24
+ )
25
+ from .types import Agent, Checkpoint, SaveResult, WorkflowResume, WorkflowReplay
26
+
27
+ __version__ = "1.0.0"
28
+
29
+ __all__ = [
30
+ "CheckpointClient",
31
+ # Types
32
+ "Checkpoint",
33
+ "WorkflowResume",
34
+ "WorkflowReplay",
35
+ "Agent",
36
+ "SaveResult",
37
+ # Errors
38
+ "CheckpointError",
39
+ "AuthError",
40
+ "NotFoundError",
41
+ "ConflictError",
42
+ "RateLimitError",
43
+ "ValidationError",
44
+ "PayloadTooLargeError",
45
+ ]