agentcheckpoint 1.0.0__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,3 @@
1
+ """AgentCheckpoint — Atomic key-value state store for AI agent coordination."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m agentcheckpoint` and CLI script `agentcheckpoint`."""
2
+
3
+ from agentcheckpoint.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,551 @@
1
+ """AgentCheckpoint — Atomic key-value state store for AI agent coordination.
2
+
3
+ MCP (Model Context Protocol) server backed by SQLite for checkpoint coordination
4
+ between AI agents, cron workers, or any multi-agent workflow. Each key holds a
5
+ JSON value. Reads always return the latest written value — no semantic ambiguity,
6
+ no stale entries, no race conditions.
7
+
8
+ Usage:
9
+ agentcheckpoint # stdio MCP server
10
+ CHECKPOINT_DB_PATH=/tmp/state.db agentcheckpoint # custom DB path
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sqlite3
18
+
19
+ from mcp.server import Server
20
+ from mcp.server.stdio import stdio_server
21
+ from mcp.types import (
22
+ Tool,
23
+ TextContent,
24
+ Resource,
25
+ ResourceTemplate,
26
+ TextResourceContents,
27
+ )
28
+
29
+ DB_PATH = os.environ.get(
30
+ "CHECKPOINT_DB_PATH",
31
+ os.path.expanduser("~/.hermes/checkpoints.db"),
32
+ )
33
+
34
+ server = Server("agentcheckpoint")
35
+
36
+
37
+ def get_db() -> sqlite3.Connection:
38
+ """Get a WAL-mode SQLite connection. Creates the table if missing."""
39
+ conn = sqlite3.connect(DB_PATH, timeout=5)
40
+ conn.execute("PRAGMA journal_mode=WAL")
41
+ conn.execute("PRAGMA synchronous=NORMAL")
42
+ conn.row_factory = sqlite3.Row
43
+ conn.execute(
44
+ """CREATE TABLE IF NOT EXISTS checkpoints (
45
+ key TEXT PRIMARY KEY,
46
+ value TEXT NOT NULL,
47
+ version INTEGER NOT NULL DEFAULT 1,
48
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
49
+ )"""
50
+ )
51
+ return conn
52
+
53
+
54
+ def _validate_json(value: str) -> str | None:
55
+ """Return error message if value is not valid JSON, else None."""
56
+ try:
57
+ json.loads(value)
58
+ except json.JSONDecodeError as e:
59
+ return f"Invalid JSON: {e}"
60
+ return None
61
+
62
+
63
+ def _upsert(conn: sqlite3.Connection, key: str, value: str) -> int:
64
+ """Insert or replace a key's value atomically, incrementing version."""
65
+ conn.execute(
66
+ """INSERT INTO checkpoints (key, value, version, updated_at)
67
+ VALUES (?, ?, 1, datetime('now'))
68
+ ON CONFLICT(key) DO UPDATE SET
69
+ value = excluded.value,
70
+ version = checkpoints.version + 1,
71
+ updated_at = datetime('now')""",
72
+ (key, value),
73
+ )
74
+ conn.commit()
75
+ row = conn.execute(
76
+ "SELECT version FROM checkpoints WHERE key = ?", (key,)
77
+ ).fetchone()
78
+ return row["version"]
79
+
80
+
81
+ # ── Embedded documentation resources ──────────────────────────────────────
82
+ # Agents can read these like files — no separate download needed.
83
+
84
+
85
+ DOCS = {
86
+ "usage": """# AgentCheckpoint — Usage Patterns
87
+
88
+ ## Single-Writer Pattern (cron jobs, solo agents)
89
+
90
+ Use `force_set_state` — it always succeeds and always replaces.
91
+
92
+ ```python
93
+ mcp_checkpoint_force_set_state(
94
+ key="workflow:daily-plan",
95
+ value='{"phase": "research", "progress": 0.3}'
96
+ )
97
+ ```
98
+
99
+ ## Multi-Agent Coordination Pattern
100
+
101
+ Use `get_state` + `set_state` with the version guard (OCC).
102
+
103
+ ```python
104
+ # 1. READ current state
105
+ state = mcp_checkpoint_get_state(key="workflow:plan-today")
106
+ plan = json.loads(state["value"])
107
+ # plan.current_index = 5
108
+
109
+ # 2. MODIFY — claim the next task
110
+ plan.current_index += 1
111
+ plan.current_task = "analisis"
112
+
113
+ # 3. WRITE with version guard
114
+ result = mcp_checkpoint_set_state(
115
+ key="workflow:plan-today",
116
+ value=json.dumps(plan),
117
+ expected_version=state["version"]
118
+ )
119
+
120
+ if result["status"] == "conflict":
121
+ # Another agent changed the state — retry from step 1
122
+ pass
123
+ elif result["status"] == "ok":
124
+ # We own this version now
125
+ pass
126
+ ```
127
+
128
+ Always pass `expected_version` when multiple agents write to the same key.
129
+ If you get a conflict, re-read and retry — that's the OCC pattern.
130
+
131
+ ## Key Naming Convention
132
+
133
+ ```
134
+ workflow:<id> — Multi-step workflow state
135
+ project:<name>:<attr> — Project-level attributes
136
+ lock:<resource> — Distributed locks
137
+ plan:<date> — Daily/periodic plans
138
+ checkpoint:<task> — Task checkpoints
139
+ cron:<job-name> — Cron job coordination
140
+ ```
141
+
142
+ Use colons as separators. Keep keys semantic but short.
143
+ Values must be valid JSON strings.
144
+
145
+ ## What NOT to Store
146
+
147
+ | ❌ Don't | ✅ Do |
148
+ |----------|-------|
149
+ | Facts, observations, learnings | agentmemory / vector store |
150
+ | Long text, documents | File system, RAG |
151
+ | Large JSON blobs (>100KB) | Split into multiple keys |
152
+ | Ephemeral task-local state | Keep in memory, write at boundaries |
153
+
154
+ AgentCheckpoint is for STATE COORDINATION, not memory.
155
+ Use both tools together: checkpoint for state, agentmemory for knowledge.
156
+
157
+ ## Integration with Cron / Workers
158
+
159
+ ```python
160
+ # Worker startup: check if work was already done
161
+ state = mcp_checkpoint_get_state(key="checkpoint:nocturno-2026-06-12")
162
+ if state["status"] != "not_found":
163
+ print("Work already completed, skipping")
164
+ return
165
+
166
+ # Worker claims and executes
167
+ mcp_checkpoint_force_set_state(
168
+ key="checkpoint:nocturno-2026-06-12",
169
+ value='{"status": "in-progress", "started_at": "..."}'
170
+ )
171
+
172
+ # ... do work ...
173
+
174
+ mcp_checkpoint_force_set_state(
175
+ key="checkpoint:nocturno-2026-06-12",
176
+ value='{"status": "completed", "finished_at": "..."}'
177
+ )
178
+ ```
179
+ """,
180
+ "coordination": """# Multi-Agent Coordination
181
+
182
+ AgentCheckpoint solves the "stale state" problem in multi-agent workflows.
183
+
184
+ ## Problem
185
+
186
+ Agent A reads state, Agent B writes new state, Agent A writes based on its
187
+ stale read — overwriting B's work. This is the classic read-modify-write race.
188
+
189
+ ## Solution: Optimistic Concurrency Control (OCC)
190
+
191
+ ```python
192
+ # Every agent follows this pattern:
193
+
194
+ def claim_and_work(key, agent_id):
195
+ while True:
196
+ # READ with version
197
+ current = mcp_checkpoint_get_state(key=key)
198
+ if current["status"] == "not_found":
199
+ # First claim
200
+ result = mcp_checkpoint_set_state(
201
+ key=key,
202
+ value=json.dumps({"owner": agent_id, "step": 0}),
203
+ expected_version=0 # create-only
204
+ )
205
+ else:
206
+ plan = json.loads(current["value"])
207
+ plan["owner"] = agent_id
208
+ plan["step"] += 1
209
+ # WRITE with version guard from the read
210
+ result = mcp_checkpoint_set_state(
211
+ key=key,
212
+ value=json.dumps(plan),
213
+ expected_version=current["version"]
214
+ )
215
+
216
+ if result["status"] == "ok":
217
+ return result["version"]
218
+ # conflict → re-read and retry
219
+ ```
220
+
221
+ Each write carries the version observed at read time. If another agent
222
+ changed the key in between, the write fails with "conflict" and you retry.
223
+
224
+ ## When to Skip Version Guard
225
+
226
+ Single-writer scenarios (cron jobs, solo agents, sequential workflows):
227
+ use `force_set_state` — no version check, always succeeds.
228
+
229
+ Multi-writer scenarios (multiple agents, parallel workers, distributed
230
+ systems): use `set_state` with `expected_version` — this is OCC.
231
+ """,
232
+ "keys": """# Key Naming Convention
233
+
234
+ ## Structure
235
+
236
+ ```
237
+ <domain>:<identifier>[:<attribute>]
238
+ ```
239
+
240
+ - **domain**: what kind of state (workflow, project, lock, plan, checkpoint, cron)
241
+ - **identifier**: unique name within that domain
242
+ - **attribute** (optional): sub-key for structured states
243
+
244
+ ## Examples
245
+
246
+ | Key | Purpose |
247
+ |-----|---------|
248
+ | `workflow:daily-digest` | Multi-step workflow state |
249
+ | `project:agentcheckpoint:build-status` | Build state for a project |
250
+ | `lock:database-migration` | Mutex for a critical operation |
251
+ | `plan:2026-06-12` | Daily execution plan |
252
+ | `checkpoint:nocturno-pilar-1` | Nightly worker checkpoint |
253
+ | `cron:noticias-mañana` | Cron job coordination |
254
+
255
+ ## Best Practices
256
+
257
+ - Use colons (`:`) as separators — they're readable and work with LIKE queries
258
+ - Keep keys under 200 chars
259
+ - Values must be valid JSON strings
260
+ - Use `list_state(pattern="project:%")` to find all project keys
261
+ - Group related keys with a common prefix for easy listing
262
+ """,
263
+ }
264
+
265
+
266
+ @server.list_resources()
267
+ async def handle_list_resources() -> list[Resource]:
268
+ return [
269
+ Resource(
270
+ uri="checkpoint://docs/usage",
271
+ name="Usage Patterns",
272
+ description="How to use agentcheckpoint: single-writer, multi-agent OCC, key naming, anti-patterns",
273
+ mimeType="text/markdown",
274
+ ),
275
+ Resource(
276
+ uri="checkpoint://docs/coordination",
277
+ name="Multi-Agent Coordination",
278
+ description="OCC pattern for multi-agent workflows with conflict detection",
279
+ mimeType="text/markdown",
280
+ ),
281
+ Resource(
282
+ uri="checkpoint://docs/keys",
283
+ name="Key Naming Convention",
284
+ description="Standard key structure and naming best practices",
285
+ mimeType="text/markdown",
286
+ ),
287
+ ]
288
+
289
+
290
+ @server.read_resource()
291
+ async def handle_read_resource(uri: str) -> TextResourceContents:
292
+ parts = uri.split("://", 1)
293
+ if len(parts) != 2 or parts[0] != "checkpoint":
294
+ raise ValueError(f"Unknown resource: {uri}")
295
+
296
+ doc_id = parts[1].removeprefix("docs/")
297
+ content = DOCS.get(doc_id)
298
+ if content is None:
299
+ raise ValueError(f"Unknown documentation section: {doc_id}")
300
+
301
+ return TextResourceContents(
302
+ uri=uri,
303
+ mimeType="text/markdown",
304
+ text=content,
305
+ )
306
+
307
+
308
+ # ── Tool definitions ──────────────────────────────────────────────────────
309
+
310
+
311
+ @server.list_tools()
312
+ async def handle_list_tools() -> list[Tool]:
313
+ return [
314
+ Tool(
315
+ name="get_state",
316
+ description=(
317
+ "Read the current value of a checkpoint by key. "
318
+ "Returns the latest stored JSON value, its version, and update timestamp. "
319
+ 'Returns {"status": "not_found"} if key doesn\'t exist.'
320
+ ),
321
+ inputSchema={
322
+ "type": "object",
323
+ "properties": {
324
+ "key": {
325
+ "type": "string",
326
+ "description": "Checkpoint key, e.g. 'workflow:plan-2026-06-12'",
327
+ }
328
+ },
329
+ "required": ["key"],
330
+ },
331
+ ),
332
+ Tool(
333
+ name="set_state",
334
+ description=(
335
+ "Atomically write a checkpoint with optional version guard. "
336
+ "Pass expected_version from a prior get_state call. "
337
+ "expected_version=0 → create-only (fails if key exists). "
338
+ "expected_version=N → update only if stored version matches (conflict-safe). "
339
+ "Omit expected_version or pass -1 → unconditional write. "
340
+ "Use force_set_state for simpler unconditional writes."
341
+ ),
342
+ inputSchema={
343
+ "type": "object",
344
+ "properties": {
345
+ "key": {"type": "string", "description": "Checkpoint key"},
346
+ "value": {
347
+ "type": "string",
348
+ "description": "Value to store (must be JSON-encoded string)",
349
+ },
350
+ "expected_version": {
351
+ "type": "integer",
352
+ "description": "Version guard: -1=unconditional, 0=create-only, N=versioned update",
353
+ "default": -1,
354
+ },
355
+ },
356
+ "required": ["key", "value"],
357
+ },
358
+ ),
359
+ Tool(
360
+ name="force_set_state",
361
+ description=(
362
+ "Unconditionally write a checkpoint value. "
363
+ "Always succeeds. Prefer for single-writer workflows. "
364
+ "For concurrent writers, use set_state with expected_version."
365
+ ),
366
+ inputSchema={
367
+ "type": "object",
368
+ "properties": {
369
+ "key": {"type": "string", "description": "Checkpoint key"},
370
+ "value": {
371
+ "type": "string",
372
+ "description": "Value to store (must be JSON-encoded string)",
373
+ },
374
+ },
375
+ "required": ["key", "value"],
376
+ },
377
+ ),
378
+ Tool(
379
+ name="list_state",
380
+ description=(
381
+ "List checkpoint keys matching a pattern (SQL LIKE syntax). "
382
+ "Pass '%' or omit for all keys. Returns key, version, and updated_at for each match."
383
+ ),
384
+ inputSchema={
385
+ "type": "object",
386
+ "properties": {
387
+ "pattern": {
388
+ "type": "string",
389
+ "description": "SQL LIKE pattern (default '%' = all keys)",
390
+ "default": "%",
391
+ }
392
+ },
393
+ "required": [],
394
+ },
395
+ ),
396
+ Tool(
397
+ name="delete_state",
398
+ description="Remove a checkpoint key and its value permanently.",
399
+ inputSchema={
400
+ "type": "object",
401
+ "properties": {
402
+ "key": {
403
+ "type": "string",
404
+ "description": "Checkpoint key to delete",
405
+ }
406
+ },
407
+ "required": ["key"],
408
+ },
409
+ ),
410
+ ]
411
+
412
+
413
+ # ── Tool call dispatcher ──────────────────────────────────────────────────
414
+
415
+
416
+ @server.call_tool()
417
+ async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]:
418
+ def ok(data: dict) -> list[TextContent]:
419
+ return [TextContent(type="text", text=json.dumps(data))]
420
+
421
+ conn = get_db()
422
+ try:
423
+ if name == "get_state":
424
+ key = arguments["key"]
425
+ row = conn.execute(
426
+ "SELECT value, version, updated_at FROM checkpoints WHERE key = ?",
427
+ (key,),
428
+ ).fetchone()
429
+ if row:
430
+ return ok({
431
+ "status": "ok",
432
+ "key": key,
433
+ "value": row["value"],
434
+ "version": row["version"],
435
+ "updated_at": row["updated_at"],
436
+ })
437
+ return ok({"status": "not_found", "key": key})
438
+
439
+ elif name == "set_state":
440
+ key = arguments["key"]
441
+ value = arguments["value"]
442
+ expected_version = arguments.get("expected_version", -1)
443
+
444
+ err = _validate_json(value)
445
+ if err:
446
+ return ok({"status": "error", "message": err})
447
+
448
+ if expected_version == -1:
449
+ version = _upsert(conn, key, value)
450
+ return ok({"status": "ok", "key": key, "version": version})
451
+
452
+ row = conn.execute(
453
+ "SELECT version FROM checkpoints WHERE key = ?", (key,)
454
+ ).fetchone()
455
+
456
+ if expected_version == 0:
457
+ if row is not None:
458
+ return ok({
459
+ "status": "conflict",
460
+ "key": key,
461
+ "message": f"Key already exists (version={row['version']}). Use expected_version=N to update, or force_set_state.",
462
+ "actual_version": row["version"],
463
+ })
464
+ conn.execute(
465
+ "INSERT INTO checkpoints (key, value, version, updated_at) VALUES (?, ?, 1, datetime('now'))",
466
+ (key, value),
467
+ )
468
+ conn.commit()
469
+ return ok({"status": "ok", "key": key, "version": 1})
470
+
471
+ if row is None:
472
+ return ok({
473
+ "status": "not_found",
474
+ "key": key,
475
+ "message": "Key does not exist. Use expected_version=0 to create, or force_set_state.",
476
+ })
477
+ if row["version"] != expected_version:
478
+ return ok({
479
+ "status": "conflict",
480
+ "key": key,
481
+ "expected_version": expected_version,
482
+ "actual_version": row["version"],
483
+ "message": f"Version mismatch: expected {expected_version}, actual {row['version']}. Call get_state and retry.",
484
+ })
485
+ conn.execute(
486
+ "UPDATE checkpoints SET value = ?, version = version + 1, updated_at = datetime('now') WHERE key = ? AND version = ?",
487
+ (value, key, expected_version),
488
+ )
489
+ conn.commit()
490
+ new_row = conn.execute(
491
+ "SELECT version FROM checkpoints WHERE key = ?", (key,)
492
+ ).fetchone()
493
+ return ok({"status": "ok", "key": key, "version": new_row["version"]})
494
+
495
+ elif name == "force_set_state":
496
+ key = arguments["key"]
497
+ value = arguments["value"]
498
+
499
+ err = _validate_json(value)
500
+ if err:
501
+ return ok({"status": "error", "message": err})
502
+
503
+ version = _upsert(conn, key, value)
504
+ return ok({"status": "ok", "key": key, "version": version})
505
+
506
+ elif name == "list_state":
507
+ pattern = arguments.get("pattern", "%")
508
+ rows = conn.execute(
509
+ "SELECT key, version, updated_at FROM checkpoints WHERE key LIKE ? ORDER BY key",
510
+ (pattern,),
511
+ ).fetchall()
512
+ return ok({
513
+ "status": "ok",
514
+ "keys": [
515
+ {"key": r["key"], "version": r["version"], "updated_at": r["updated_at"]}
516
+ for r in rows
517
+ ],
518
+ })
519
+
520
+ elif name == "delete_state":
521
+ key = arguments["key"]
522
+ conn.execute("DELETE FROM checkpoints WHERE key = ?", (key,))
523
+ conn.commit()
524
+ return ok({"status": "ok", "key": key, "deleted": True})
525
+
526
+ else:
527
+ return ok({"status": "error", "message": f"Unknown tool: {name}"})
528
+
529
+ finally:
530
+ conn.close()
531
+
532
+
533
+ # ── Entry point ───────────────────────────────────────────────────────────
534
+
535
+
536
+ async def main_async() -> None:
537
+ async with stdio_server() as (read_stream, write_stream):
538
+ await server.run(read_stream, write_stream, server.create_initialization_options())
539
+
540
+
541
+ def main() -> None:
542
+ """Run the agentcheckpoint MCP server over stdio."""
543
+ import asyncio
544
+ try:
545
+ asyncio.run(main_async())
546
+ except KeyboardInterrupt:
547
+ pass
548
+
549
+
550
+ if __name__ == "__main__":
551
+ main()
@@ -0,0 +1,320 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentcheckpoint
3
+ Version: 1.0.0
4
+ Summary: MCP checkpoint server: atomic state coordination for AI agents, cron workers, and multi-agent systems
5
+ Author-email: Ernesto Maldonado <erniomaldo@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/erniomaldo/agentcheckpoint
8
+ Project-URL: Source, https://github.com/erniomaldo/agentcheckpoint
9
+ Project-URL: Documentation, https://github.com/erniomaldo/agentcheckpoint#readme
10
+ Keywords: mcp,checkpoint,state,agent,coordination,hermes,sqlite
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: mcp>=1.0.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: build>=1.0; extra == "dev"
25
+ Requires-Dist: twine>=5.0; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # AgentCheckpoint
29
+
30
+ **Servidor MCP de almacenamiento atómico clave-valor para coordinación de agentes de IA.**
31
+
32
+ Evita que tus agentes de IA trabajen con estado obsoleto. AgentCheckpoint es un servidor
33
+ MCP (Model Context Protocol) minimalista respaldado por SQLite que brinda a tus agentes
34
+ un almacén de estado compartido y atómico — **siempre devolviendo el último valor escrito**.
35
+
36
+ ```bash
37
+ pip install agentcheckpoint
38
+ ```
39
+
40
+ Luego agrégalo a tu cliente MCP:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "checkpoint": {
46
+ "command": "agentcheckpoint",
47
+ "timeout": 10
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## El Problema
54
+
55
+ Los almacenes de memoria semántica (bases vectoriales, agentmemory, etc.) están diseñados
56
+ para hechos, no para coordinación de estado. Cuando múltiples agentes leen/escriben
57
+ estado compartido:
58
+
59
+ - `memory.save()` crea **nuevas entradas** en lugar de actualizar — se acumulan decenas
60
+ de versiones obsoletas
61
+ - `memory.recall()` devuelve resultados por **similitud semántica**, no por la última
62
+ marca de tiempo
63
+ - Los agentes leen estado desactualizado y **vuelven a ejecutar trabajo ya completado**
64
+
65
+ ## La Solución — 100 líneas de Python
66
+
67
+ AgentCheckpoint es un almacén clave-valor con escrituras atómicas, **diseñado para
68
+ coordinación de estado, no para memoria**.
69
+
70
+ - **Siempre lo último** — `get_state(key)` devuelve el único valor actual
71
+ - **Escrituras atómicas** — `force_set_state(key, value)` siempre actualiza, nunca añade
72
+ - **Seguro contra conflictos** — `set_state(key, value, expected_version)` detecta
73
+ conflictos de lectura-modificación-escritura
74
+ - **Respaldado por SQLite** — cero infraestructura, un solo archivo, modo WAL
75
+ - **Una tabla, cinco herramientas** — lo suficientemente simple para entenderlo en 5 minutos
76
+
77
+ ## Herramientas
78
+
79
+ | Herramienta | Descripción |
80
+ |-------------|-------------|
81
+ | `get_state` | Lee el valor actual, versión y timestamp de una clave |
82
+ | `set_state` | Escribe con guardia de versión opcional (detección OCC de conflictos) |
83
+ | `force_set_state` | Escritura atómica incondicional (para flujos de un solo escritor) |
84
+ | `list_state` | Lista claves que coinciden con un patrón SQL LIKE |
85
+ | `delete_state` | Elimina una clave permanentemente |
86
+
87
+ ## Inicio Rápido
88
+
89
+ ### 1. Instalación
90
+
91
+ ```bash
92
+ pip install agentcheckpoint
93
+ # o
94
+ uv pip install agentcheckpoint
95
+ ```
96
+
97
+ ### 2. Agregar a tu cliente MCP
98
+
99
+ Copia y pega la configuración para tu plataforma. Después de agregarla,
100
+ **reinicia tu cliente**.
101
+
102
+ #### 🟣 Claude Desktop
103
+
104
+ Edita `claude_desktop_config.json`:
105
+
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "checkpoint": {
110
+ "command": "agentcheckpoint",
111
+ "timeout": 10
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ #### 🔵 Claude Code
118
+
119
+ Agrega a `~/.claude/settings.json` bajo `mcpServers`:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "checkpoint": {
125
+ "command": "agentcheckpoint",
126
+ "timeout": 10
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ O usa la CLI:
133
+
134
+ ```bash
135
+ claude mcp add checkpoint -- python -m agentcheckpoint
136
+ ```
137
+
138
+ #### 🟢 Cursor
139
+
140
+ Agrega a `~/.cursor/mcp.json`:
141
+
142
+ ```json
143
+ {
144
+ "mcpServers": {
145
+ "checkpoint": {
146
+ "command": "agentcheckpoint",
147
+ "timeout": 10
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ #### 🟠 Windsurf
154
+
155
+ Agrega a `~/.codeium/windsurf/mcp_config.json`:
156
+
157
+ ```json
158
+ {
159
+ "mcpServers": {
160
+ "checkpoint": {
161
+ "command": "agentcheckpoint",
162
+ "timeout": 10
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ #### ⚪ Continue.dev
169
+
170
+ Agrega a `~/.continue/config.json` bajo `experimental.mcpServers` (o `mcpServers`
171
+ según la versión):
172
+
173
+ ```json
174
+ {
175
+ "experimental": {
176
+ "mcpServers": {
177
+ "checkpoint": {
178
+ "command": "agentcheckpoint",
179
+ "timeout": 10
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ #### 🔶 Hermes Agent
187
+
188
+ Agrega a `~/.hermes/config.yaml` bajo `mcp_servers`:
189
+
190
+ ```yaml
191
+ mcp_servers:
192
+ checkpoint:
193
+ command: "agentcheckpoint"
194
+ timeout: 10
195
+ ```
196
+
197
+ Luego ejecuta `/reload-mcp` en sesión, o reinicia el gateway.
198
+
199
+ #### 🐍 Cualquier cliente con soporte uvx
200
+
201
+ Si tu cliente soporta `uvx` (la mayoría modernos lo hacen):
202
+
203
+ ```json
204
+ {
205
+ "mcpServers": {
206
+ "checkpoint": {
207
+ "command": "uvx",
208
+ "args": ["agentcheckpoint"],
209
+ "timeout": 10
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### 3. Verificar que funciona
216
+
217
+ Una vez configurado, pregúntale a tu agente:
218
+
219
+ > "¿Qué herramientas tengo del servidor MCP checkpoint?"
220
+
221
+ Deberías ver cinco herramientas: `get_state`, `set_state`, `force_set_state`,
222
+ `list_state` y `delete_state` (prefijadas con `mcp_checkpoint_` en algunos clientes).
223
+
224
+ ### 4. Uso básico
225
+
226
+ ```python
227
+ # Ejemplo: guardar y leer un checkpoint
228
+ mcp_checkpoint_force_set_state(
229
+ key="proyecto:build-status",
230
+ value='{"phase": "testing", "passed": 13, "failed": 2}'
231
+ )
232
+
233
+ # Más tarde...
234
+ status = mcp_checkpoint_get_state(key="proyecto:build-status")
235
+ # → {value: {...}, version: 1, updated_at: "2026-06-12"}
236
+ ```
237
+
238
+ ## Ejemplo: Coordinación Multi-Agente
239
+
240
+ ```python
241
+ # Agente A lee el plan actual
242
+ state = client.call_tool("get_state", {"key": "workflow:plan-hoy"})
243
+ plan = json.loads(state.value)
244
+ # plan.current_index = 5, plan.current_status = "completada"
245
+
246
+ # Agente A toma la siguiente tarea
247
+ plan.current_index += 1 # ahora 6
248
+ plan.current_status = "en-progreso"
249
+ client.call_tool("force_set_state", {
250
+ "key": "workflow:plan-hoy",
251
+ "value": json.dumps(plan)
252
+ })
253
+
254
+ # ... El Agente A trabaja en la tarea ...
255
+
256
+ # Agente A la marca como completada
257
+ plan.tasks[6].status = "completada"
258
+ plan.current_status = "completada"
259
+ client.call_tool("force_set_state", {
260
+ "key": "workflow:plan-hoy",
261
+ "value": json.dumps(plan)
262
+ })
263
+
264
+ # Agente B (siguiente tick) lee — siempre obtiene lo último
265
+ ```
266
+
267
+ ## Configuración
268
+
269
+ | Variable de entorno | Por defecto | Descripción |
270
+ |---------------------|-------------|-------------|
271
+ | `CHECKPOINT_DB_PATH` | `~/.hermes/checkpoints.db` | Ruta de la base de datos SQLite |
272
+
273
+ ## Arquitectura
274
+
275
+ ```
276
+ ┌──────────────┐ MCP stdio ┌──────────────────┐ SQLite WAL ┌──────────┐
277
+ │ Agente / │ ────────────────→ │ agentcheckpoint │ ───────────────→ │ state.db │
278
+ │ Cron Worker │ ←──────────────── │ Servidor MCP │ ←─────────────── │ (1 file) │
279
+ └──────────────┘ └──────────────────┘ └──────────┘
280
+ ```
281
+
282
+ El servidor se ejecuta como un subproceso stdio. Las herramientas se autodescubren
283
+ a través del cliente MCP. Sin puertos de red, sin contenedor, sin configuración más
284
+ allá de agregarlo a tu `mcpServers`.
285
+
286
+ ## ¿Por qué no usar agentmemory / base vectorial?
287
+
288
+ AgentCheckpoint no reemplaza la memoria — es una herramienta diferente para un
289
+ trabajo diferente:
290
+
291
+ | | AgentCheckpoint | Memoria Vectorial/Semántica |
292
+ |---|---|---|
293
+ | **Propósito** | Coordinación de estado | Hechos, aprendizaje, recuperación |
294
+ | **Escritura** | Siempre reemplaza (UPDATE) | Siempre añade (INSERT) |
295
+ | **Lectura** | Coincidencia exacta de clave (`SELECT WHERE key=?`) | Similitud semántica (`ORDER BY distance`) |
296
+ | **Concurrencia** | Guardia de versión (OCC) | Ninguna |
297
+ | **Persistencia** | SQLite WAL (transaccional) | Varía según el backend |
298
+
299
+ **Úsalos juntos**: AgentCheckpoint para estado compartido (planes, checkpoints,
300
+ bloqueos), memoria vectorial para descubrimientos, observaciones y hechos.
301
+
302
+ ## Desarrollo
303
+
304
+ ```bash
305
+ git clone https://github.com/erniomaldo/agentcheckpoint
306
+ cd agentcheckpoint
307
+ pip install -e ".[dev]"
308
+ ```
309
+
310
+ ## Licencia
311
+
312
+ MIT
313
+
314
+ ---
315
+
316
+ ## Idiomas
317
+
318
+ - [English](README.en.md)
319
+ - [Português](README.pt.md)
320
+ - [Français](README.fr.md)
@@ -0,0 +1,9 @@
1
+ agentcheckpoint/__init__.py,sha256=RCMN1roPF-EuEh3mBRggASK0ewTJS1TQ5FW-z5ulC2c,105
2
+ agentcheckpoint/__main__.py,sha256=H55AJ9EBkEVXAKifHQDLGrWb-YOnx0SBRImj59E8H_Q,164
3
+ agentcheckpoint/server.py,sha256=keGnxccTC9PMUAdu4A7_nIJGHZS2MqZynSKsa2M1N1g,18609
4
+ agentcheckpoint-1.0.0.dist-info/licenses/LICENSE,sha256=cz33kz5I0OzyStQkeeP2yjpNjET6B0P0XP4JM-fGdYk,1074
5
+ agentcheckpoint-1.0.0.dist-info/METADATA,sha256=xgRzeh3CStU0qDDRfrW2z_lcCSA6SUVyR_e_0iaSqi4,8870
6
+ agentcheckpoint-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ agentcheckpoint-1.0.0.dist-info/entry_points.txt,sha256=bHlJZCDuGvG0CMpbJ2o1lfu73S8bKuC6osS7n0T_7xg,66
8
+ agentcheckpoint-1.0.0.dist-info/top_level.txt,sha256=wDaTvLBsjdUQ-dJVXEtP6Q29YnmLZqVx8Ivt1MOp1HQ,16
9
+ agentcheckpoint-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agentcheckpoint = agentcheckpoint.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ernesto Maldonado
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ agentcheckpoint