agentcheckpoint
# AgentCheckpoint
**Atomic key-value state store for AI agent coordination.**
[](https://pypi.org/project/agentcheckpoint/)
[](https://pypi.org/project/agentcheckpoint/)
[](https://github.com/erniomaldo/agentcheckpoint/blob/main/LICENSE)
[](https://pypistats.org/packages/agentcheckpoint)
---
<p align="center">
<strong>An MCP server that stops your agents from working on stale state.</strong><br>
<em>~150 lines of Python, SQLite WAL-backed, zero infrastructure.</em>
</p>
---
π [πͺπΈ EspaΓ±ol](README.es.md) Β· [π«π· FranΓ§ais](README.fr.md) Β· [π§π· PortuguΓͺs](README.pt.md)
---
## π¦ Installation
```bash
pip install agentcheckpoint
```
Then add it to your MCP client of choice (jump to [Client Configuration](#-client-configuration)).
---
## π€¨ The Problem
Semantic memory stores βvector DBs, agentmemory, mem0, etc.β are designed for **facts and learning**, not **state coordination**. When multiple agents read and write shared state, here's what happens:
| Problem | What happens | Consequence |
|---------|-------------|-------------|
| `memory.save()` has no update | Each save creates a **new entry** | Dozens of stale versions pile up |
| `memory.recall()` uses similarity | Returns **semantically close** results, not the latest | Agents read outdated state |
| No concurrency control | Two agents read the same state, write without coordination | Changes overwrite each other, data loss |
| No version guard | One write can blindly overwrite another agent's work | **Corrupted workflows, re-executed work** |
**Bottom line:** your agents work with stale state, re-run tasks already completed, and burn compute on duplicated effort.
---
## β
The Solution
AgentCheckpoint is **not** a memory store β it's a **shared state store with atomic guarantees**. Think of it as a traffic light or shared memory for AI agents.
```
ββββββββββββββββββββββββ MCP stdio ββββββββββββββββββββββ SQLite WAL ββββββββββββββββ
β Agent A β βββββββββββββββββ β ββββββββββββββββ β
β Agent B β βββββββββββββββββ AgentCheckpoint β ββββββββββββββββ state.db β
β Cron Worker C β βββββββββββββββββ MCP Server β ββββββββββββββββ (1 file) β
β Pipeline D β ββββββββββββββββ β ββββββββββββββββ β
ββββββββββββββββββββββββ ββββββββββββββββββββββ ββββββββββββββββ
```
### How it compares
| Feature | AgentCheckpoint | agentmemory / vector DB | Redis | JSON file |
|---------|----------------|------------------------|-------|-----------|
| **Purpose** | State coordination | Facts, learning | Generic cache | Basic persistence |
| **Write** | Always replaces (UPSERT) | Always appends (INSERT) | Overwrites (no versioning) | Overwrites entire file |
| **Read** | `SELECT WHERE key=?` exact match | `ORDER BY distance` semantic | Direct key lookup | Parse & search |
| **Concurrency** | Optimistic Concurrency Control (OCC) | None | None native | None |
| **Persistence** | SQLite WAL (transactional, ACID) | Varies by backend | RAM / RDB / AOF | Filesystem-dependent |
| **Infrastructure** | Zero β single stdio process | Server, API, indices | Dedicated server | Zero |
| **MCP Tooling** | Native β auto-discovery of tools | No | No | No |
| **Lines of code** | ~150 | Thousands | ~50K+ | ~5 (no guarantees) |
**Use both together:** AgentCheckpoint for shared state, vector memory / agentmemory for facts, observations, and discoveries.
---
## π οΈ Tools (MCP API)
| Tool | Description | When to use |
|------|-------------|-------------|
| `get_state(key)` | Read the current value, version, and timestamp for a key | Before any modification |
| `set_state(key, value, expected_version?)` | Write with optional version guard (OCC) | When **multiple** agents write the same key |
| `force_set_state(key, value)` | Unconditional atomic write | When a **single** agent/worker owns the key |
| `list_state(pattern?)` | List keys matching a SQL LIKE pattern | Auditing, discovery, debugging |
| `delete_state(key)` | Remove a key permanently | Cleanup of completed state |
Each tool is auto-discovered through the MCP protocol β no extra configuration needed.
> **Note for MCP clients:** in some clients tools are prefixed as `mcp_checkpoint_get_state`, `mcp_checkpoint_set_state`, etc.
---
## π Quick Start
### 1. Install
```bash
pip install agentcheckpoint
# or with uv:
uv pip install agentcheckpoint
```
### 2. Add to your MCP client
Configuration varies by platform. After adding, **restart your client** or reload MCP servers.
#### π£ Claude Desktop
Edit `claude_desktop_config.json`:
```json
{
"mcpServers": {
"checkpoint": {
"command": "agentcheckpoint",
"timeout": 10
}
}
}
```
#### π΅ Claude Code
Add to `~/.claude/settings.json`:
```json
{
"mcpServers": {
"checkpoint": {
"command": "agentcheckpoint",
"timeout": 10
}
}
}
```
Or via CLI:
```bash
claude mcp add checkpoint -- python -m agentcheckpoint
```
#### π’ Cursor
Add to `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"checkpoint": {
"command": "agentcheckpoint",
"timeout": 10
}
}
}
```
#### π Windsurf
Add to `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"checkpoint": {
"command": "agentcheckpoint",
"timeout": 10
}
}
}
```
#### βͺ Continue.dev
Add to `~/.continue/config.json`:
```json
{
"experimental": {
"mcpServers": {
"checkpoint": {
"command": "agentcheckpoint",
"timeout": 10
}
}
}
}
```
#### πΆ Hermes Agent
Add to `~/.hermes/config.yaml`:
```yaml
mcp_servers:
checkpoint:
command: "agentcheckpoint"
timeout: 10
```
Then run `/reload-mcp` in-session, or restart the gateway.
#### π Any client with uvx support
```json
{
"mcpServers": {
"checkpoint": {
"command": "uvx",
"args": ["agentcheckpoint"],
"timeout": 10
}
}
}
```
### 3. Verify
Ask your agent:
> _"What tools do I have from the checkpoint MCP server?"_
You should see all 5 tools listed above.
### 4. First checkpoint
```python
# Save state
mcp_checkpoint_force_set_state(
key="project:build-status",
value='{"phase": "testing", "passed": 13, "failed": 2}'
)
# Read state later
status = mcp_checkpoint_get_state(key="project:build-status")
# β {status: "ok", key: "...", value: {...}, version: 1, updated_at: "2026-06-16T..."}
```
---
## π― Usage Patterns
### Pattern 1: Single Writer (cron jobs, solo agents)
Use `force_set_state` β always succeeds, always replaces:
```python
# Nightly worker: checkpoint progress
mcp_checkpoint_force_set_state(
key="checkpoint:nocturnal-2026-06-16",
value='{"status": "in-progress", "started_at": "2026-06-16T03:00:00Z"}'
)
# ... processing ...
mcp_checkpoint_force_set_state(
key="checkpoint:nocturnal-2026-06-16",
value='{"status": "completed", "records_processed": 1427, "finished_at": "..."}'
)
```
### Pattern 2: Multiple Agents with OCC (the important one)
Use `get_state` + `set_state` with the version guard (Optimistic Concurrency Control):
```python
# 1. READ with version
current = mcp_checkpoint_get_state(key="workflow:plan-today")
plan = json.loads(current["value"])
# plan.current_index = 5, version = 3
# 2. MODIFY
plan.current_index += 1
plan.current_task = "analysis"
# 3. WRITE with the version we read
result = mcp_checkpoint_set_state(
key="workflow:plan-today",
value=json.dumps(plan),
expected_version=current["version"] # β OCC guard
)
if result["status"] == "conflict":
# Another agent changed the state β re-read and retry
pass
elif result["status"] == "ok":
# Write succeeded, new version assigned
print(f"Checkpoint updated, version {result['version']}")
```
Each write carries the version observed at read time. If another agent changed the key in between, the write fails with `conflict` β you re-read and retry. This is standard **Optimistic Concurrency Control (OCC)**, the same pattern used by Elasticsearch, CouchDB, and Git.
### Pattern 3: Distributed Lock
```python
# Attempt to acquire a lock (create-only)
result = mcp_checkpoint_set_state(
key="lock:db-migration",
value=json.dumps({"owner": "agent-A", "acquired_at": "..."}),
expected_version=0 # β only works if it DOESN'T exist
)
if result["status"] == "ok":
# Lock acquired β run critical operation
run_migration()
# Release
mcp_checkpoint_delete_state(key="lock:db-migration")
else:
# Lock held by another β wait or abort
pass
```
### Pattern 4: Skip-if-done (idempotency guard)
```python
# Before starting: was this already completed?
state = mcp_checkpoint_get_state(key="checkpoint:generate-invoices")
if state["status"] != "not_found":
print("Work already completed, skipping")
return
# Claim + execute
mcp_checkpoint_force_set_state(
key="checkpoint:generate-invoices",
value='{"status": "started"}'
)
# ... do the work ...
```
---
## π Key Naming Convention
Keep your keys organized with this structure:
```
<domain>:<identifier>[:<attribute>]
```
| Example | Purpose |
|---------|---------|
| `workflow:daily-digest` | Multi-step workflow state |
| `project:agentcheckpoint:build-status` | Build state for a project |
| `lock:database-migration` | Mutex for critical operation |
| `plan:2026-06-16` | Daily execution plan |
| `checkpoint:nocturnal-pillar-1` | Nightly worker checkpoint |
| `cron:news-morning` | Cron job coordination |
**Best practices:**
- Use colons (`:`) as separators β readable and work with `SELECT LIKE`
- Keep keys under **200 characters**
- Values **must always be valid JSON**
- Use `list_state(pattern="project:%")` to find all keys in a domain
---
## π API Reference
### `get_state(key)`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `key` | `string` | β
| Key to read |
**Success response:**
```json
{"status": "ok", "key": "workflow:plan", "value": "...", "version": 3, "updated_at": "2026-06-16T..."}
```
**Key not found:**
```json
{"status": "not_found", "key": "workflow:plan"}
```
### `set_state(key, value, expected_version?)`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `key` | `string` | β
| Key to write |
| `value` | `string` | β
| Value (JSON string) |
| `expected_version` | `integer` | β | -1=unconditional (default), 0=create-only, N=versioned update |
**Version guard behavior:**
| `expected_version` | Result |
|--------------------|--------|
| `-1` (omitted) | Always writes (like `force_set_state`) |
| `0` | Creates only if it DOESN'T exist. Fails with `conflict` if it does |
| `N > 0` | Updates only if stored version matches N. Fails with `conflict` if it doesn't |
### `force_set_state(key, value)`
Unconditional. Always writes. No version guard.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `key` | `string` | β
| Key to write |
| `value` | `string` | β
| Value (JSON string) |
### `list_state(pattern?)`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `pattern` | `string` | β | SQL LIKE pattern (`%` = any text, `_` = single char). Default: `%` |
### `delete_state(key)`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `key` | `string` | β
| Key to delete |
---
## βοΈ Configuration
| Env var | Default | Description |
|---------|---------|-------------|
| `CHECKPOINT_DB_PATH` | `~/.hermes/checkpoints.db` | SQLite database file path |
Custom path example:
```bash
CHECKPOINT_DB_PATH=/tmp/my-state.db agentcheckpoint
```
---
## ποΈ Architecture
```
ββββββββββββββββββββββββ stdio (stdin/stdout) ββββββββββββββββββββββ
β β β β
β MCP Client β ββββββ JSON-RPC (MCP) ββββββββ β agentcheckpoint β
β (Claude, Cursor, β βββββββββββββββββββββββββββββββ β MCP Server β
β Windsurf, Hermes) β β β
β β β ββββββββββββββββ β
ββββββββββββββββββββββββ β β SQLite WAL β β
β β state.db β β
β β (1 file) β β
β ββββββββββββββββ β
ββββββββββββββββββββββ
```
### Technical details
- **Transport:** stdio (MCP subprocess) β no network ports, no containers
- **Database:** SQLite in WAL mode (Write-Ahead Logging) for concurrent reads without blocking
- **Concurrency:** `PRAGMA synchronous=NORMAL` β balance between durability and speed
- **Validation:** all values validated as JSON on write
- **Versioning:** every UPSERT atomically increments the version counter
- **Connection timeout:** 5 seconds in SQLite, 10 seconds recommended in MCP client
- **Atomicity:** writes are transactional β either fully persisted or not persisted at all
---
## β FAQ
**Q: Does AgentCheckpoint replace agentmemory?**
A: No. They're complementary. AgentCheckpoint coordinates state (who did what? which step are we on?). agentmemory stores facts and learnings (what did we discover? how does X work?). **Use both together.**
**Q: Can I run multiple instances pointing at the same file?**
A: SQLite WAL supports multiple concurrent readers, but for multiple writers it's best to use a single MCP server instance. For high availability, consider placing the `.db` on a shared volume.
**Q: What if the process crashes mid-write?**
A: SQLite WAL guarantees atomicity β either the full change is persisted or nothing is. No partial writes.
**Q: How large can a value be?**
A: Values are JSON strings. SQLite can theoretically handle up to ~1GB, but we recommend keeping values under **100KB**. For large data, store a reference (file path, URL) as the value.
**Q: How do I clean up old checkpoints?**
A: Use `delete_state` for individual keys or write a script that iterates with `list_state` and deletes based on `updated_at`.
**Q: Does it support TTL / auto-expiration?**
A: Not natively, but you can implement it in your agent: when reading, check `updated_at` and decide if the state is stale.
---
## π§βπ» Development
```bash
git clone https://github.com/erniomaldo/agentcheckpoint
cd agentcheckpoint
pip install -e ".[dev]"
```
Source code lives in `src/agentcheckpoint/`:
| File | What it does |
|------|-------------|
| `__init__.py` | Package version |
| `__main__.py` | Entry point (`python -m agentcheckpoint`) |
| `server.py` | Complete MCP server (~150 lines) |
### Contributing
1. Fork the repo
2. Create a branch (`git checkout -b feature/awesome-thing`)
3. Make your changes
4. Commit with clear messages
5. Push and open a Pull Request
---
## π License
MIT Β© [Ernesto Maldonado](https://github.com/erniomaldo)
---
## π Languages
| Language | File |
|----------|------|
| πΊπΈ English | `README.md` (this) |
| πͺπΈ EspaΓ±ol | [`README.es.md`](README.es.md) |
| π«π· FranΓ§ais | [`README.fr.md`](README.fr.md) |
| π§π· PortuguΓͺs | [`README.pt.md`](README.pt.md) |
---
<p align="center">
<sub>Made with β€οΈ to keep agents from stepping on each other's state.</sub><br>
<sub>Found it useful? Drop a β on <a href="https://github.com/erniomaldo/agentcheckpoint">GitHub</a></sub>
</p>
TDQS
Scored across 5 tools
set_state and force_set_state both perform writes, with set_state also supporting unconditional writes via omitted expected_version. The descriptions clarify intended use cases (concurrency-safe vs. simple writes), but the overlap creates a minor selection risk. Other tools are clearly distinct.
All tools follow a clear [verb]_state pattern: get_state, set_state, force_set_state, list_state, delete_state. The compound verb force_set is still consistent and predictable.
Five tools is well-scoped for a checkpoint store, covering read, write, list, delete, and a conditional-write variant without clutter or redundancy.
The set covers the full lifecycle: create (set_state with version 0), read (get_state), update (set_state with version), delete (delete_state), and listing (list_state). The force_set_state adds a convenience write, leaving no obvious gaps.