Persistent State & Session Management for AI Agents
⚠️ OpenClaw Integration Status: This project is not fully tested with OpenClaw yet. See integration status before using.
AI agents fail in production because they forget everything on restart. This engine gives agents durable memory that survives crashes.
What it prevents:
- Duplicate tool executions (any tool, not just emails)
- Lost progress on restart (agent forgets what it was doing)
- Untraceable actions ("why did the agent do that?")
- Corrupted state from partial failures
- A durability layer — State survives crashes, restarts, and failures
- A correctness layer — Idempotency guarantees, atomic transactions
- A recovery layer — Resume interrupted work, replay sessions
- An audit layer — Every action tracked, traceable, explainable
- ❌ A vector database — No embeddings, no similarity search
- ❌ An agent framework — No orchestration, no reasoning, no planning
- ❌ A memory summarizer — No compression, no forgetting, no prioritization
- ❌ A message queue — No pub/sub, no async delivery
- ❌ A workflow engine — No DAGs, no branching logic
If you need those things, use them alongside this engine.
This engine does ONE thing well: ensures agent state is durable and correct.
cd state-engine
cargo run -- serve --database openclaw_state.dbServer runs on http://127.0.0.1:3030
import { StateEngineClient } from '@openclaw/ts-client';
const client = new StateEngineClient({ url: 'http://127.0.0.1:3030' });
// Create user and session
const user = await client.createUser('my-agent');
const session = await client.createSession(user.id);
// Create a goal
const goal = await client.createGoal(user.id, session.id, 'Send email');
// Execute with idempotency - prevents duplicates!
const exec = await client.executeToolIdempotent(
user.id, session.id, goal.id,
'send_email',
{ to: 'user@example.com', subject: 'Hello' },
'email-user123-welcome' // Same key = same result, always
);The execute_tool_idempotent method guarantees no duplicate executions - without needing to provide a key:
// Auto-generated idempotency key (server creates one for you)
const exec = await client.executeToolIdempotent(
user.id, session.id, goal.id,
'any_tool',
{ input: 'value' }
// No idempotency_key needed - server generates:
// "auto:any_tool:20240315:abc123" from hash of inputs
);
// Same inputs = same auto-generated key = no duplicates!Or provide your own key:
await client.executeToolIdempotent(..., 'my-custom-key');This is atomic and race-condition safe. Tested with 100 concurrent calls — all returned the same execution ID.
openclaw-state-engine serve --database state.db --addr 127.0.0.1:3030openclaw-state-engine replay --database state.db --session <session-id>Output:
╔════════════════════════════════════════════════════════════════╗
║ SESSION REPLAY ║
╚════════════════════════════════════════════════════════════════╝
Session ID: eedf0c5c-83bb-4674-940d-25918c64fb93
User ID: adac540e-1edb-4b23-9a7a-a525394c69c5
Status: Active
Created: 2026-02-23 12:48:34 UTC
┌─────────────────────────────────────────────────────────────────┐
│ GOALS (1) │
└─────────────────────────────────────────────────────────────────┘
Goal #1: Send welcome email
Status: Pending
Priority: Medium
┌── TOOL EXECUTIONS (1) ──
│
│ Tool: send_email
│ Status: Pending
│ Input: {"subject":"Welcome!","to":"user@example.com"}
│ Idempotency Key: welcome-email-001
│ Time: 12:48:34
└───────────────────────────
══════════════════════════════════════════════════════════════════
Replay complete. 1 goal(s), 1 execution(s) total.
If you use this engine, these failures will not happen:
| Guarantee | What It Prevents |
|---|---|
| Tool executions are idempotent | Duplicate emails, double charges |
| Goals survive restart | Lost work, repeated effort |
| Duplicate actions detectable | Accidental retries |
| State transitions auditable | "Why did this happen?" debugging |
| Atomic writes | Corrupted state, orphan records |
Shows before/after comparison of agent failures:
cd examples/failure-demo
node demo.jsWhat you'll see:
- WITHOUT engine: Duplicate emails sent on retry
- WITH engine: Same key returns same result, no duplicates
Complete integration example:
cd examples/email-agent
npm install
npm startDrop-in adapter for OpenClaw agents — adds durability to any agent:
cd integrations/openclaw
npm installimport { OpenClawAgent } from './adapter';
const agent = new OpenClawAgent({
userId: 'my-agent',
engineUrl: 'http://127.0.0.1:3030'
});
await agent.init();
// Crash-safe, duplicate-safe tool execution - auto-tracked!
const result = await agent.execute('send_email', {
to: 'user@example.com',
subject: 'Hello'
});
// Every tool automatically tracked - no manual key needed
// Server auto-generates idempotency key from tool+inputs# Start server
cd state-engine && cargo run -- serve --database test.db
# Run stress test (100 concurrent calls)
cd tests/idempotency-stress
npm testResults:
✅ Test 1 (Concurrent Calls): PASS - 100 calls → 1 execution ID
✅ Test 2 (Different Keys): PASS - 10 keys → 10 executions
✅ Test 3 (Persist After Restart): PASS - Same result after restart
Visualize tool executions, sessions, and statistics with the web dashboard:
# 1. Start the engine
cd state-engine && cargo run -- serve --database openclaw_state.db
# 2. Open dashboard in browser
open dashboard/index.htmlFeatures:
- Session overview with status
- Tool execution timeline
- Execution details (input, output, errors)
- Auto-generated idempotency keys visible
| Document | Description |
|---|---|
| API Version | Frozen v0.1 API surface |
| Failure Contract | What failures this prevents |
| Dashboard | Web dashboard for visualization |
| Transactions | Atomic guarantees explained |
| Why This Exists | Non-technical explanation |
| Method | Description |
|---|---|
create_user |
Create a new user |
get_user |
Get user by ID |
create_session |
Start a new session |
get_session |
Get session by ID |
get_active_session |
Get active session for user |
update_session |
Update session |
create_goal |
Create a goal |
get_goal |
Get goal by ID |
update_goal |
Update goal |
list_pending_goals |
List pending goals |
create_tool_execution |
Log tool execution |
execute_tool_idempotent |
⭐ Idempotent execution |
get_tool_execution |
Get execution by ID |
find_by_idempotency_key |
Find by idempotency key |
update_tool_execution |
Update execution |
list_tool_executions |
List executions for goal |
create_memory |
Create a memory |
get_memory |
Get memory by ID |
update_memory |
Update memory |
list_memories |
List memories for user |
create_channel |
Create a channel |
get_channel |
Get channel by ID |
update_channel |
Update channel |
list_channels |
List channels for user |
┌─────────────┐
│ AI Agent │ ─────► ┌──────────────┐ ─────► ┌────────────────┐
└─────────────┘ │ ts-client │ │ State Engine │
│ (TypeScript) │ │ (Rust + SQLite)│
└──────────────┘ └────────────────┘
│
┌───────▼───────┐
│ SQLite WAL │
│ (durable) │
└───────────────┘
| Component | Technology |
|---|---|
| Backend | Rust |
| Database | SQLite with WAL mode |
| API | JSON-RPC 2.0 (jsonrpsee) |
| Client | TypeScript |
| Idempotency | UNIQUE constraint + atomic transactions |
open-engine/
├── state-engine/ # Rust backend
│ ├── src/
│ │ ├── models/ # Data models
│ │ ├── persistence/ # SQLite repositories
│ │ ├── api/ # JSON-RPC endpoints
│ │ └── main.rs # CLI entry point
│ └── Cargo.toml
├── ts-client/ # TypeScript client
│ ├── src/
│ │ ├── client.ts # API client
│ │ └── types.ts # TypeScript types
│ └── package.json
├── dashboard/ # Web dashboard (HTML/CSS/JS)
├── examples/
│ ├── email-agent/ # Complete integration example
│ └── failure-demo/ # Before/after failure demo
├── tests/
│ └── idempotency-stress/ # Stress test suite
├── docs/
│ ├── API_VERSION.md # Frozen API surface
│ ├── FAILURE_CONTRACT.md # Guarantees
│ ├── TRANSACTIONS.md # Atomic guarantees
│ ├── DASHBOARD.md # Dashboard docs
│ └── WHY_THIS_EXISTS.md # Non-technical explanation
└── README.md
MIT
