Checkpointers
Session persistence patterns
Enable conversation persistence and pause/resume functionality with checkpointers.
Overview
Checkpointers allow agents to save and restore conversation state across sessions. They provide:
- ✅ Automatic checkpoint saving after each step
- ✅ Thread isolation - different threads don't interfere
- ✅ State preservation (todos, files, messages)
- ✅ Pluggable storage backends
- ✅ Namespace support for multi-tenancy
What Gets Stored
Each checkpoint saves:
- threadId: Unique identifier for the conversation thread
- step: Step number when checkpoint was created
- messages: Full conversation history (user and assistant messages)
- state: Agent state including:
todos: Array of todo itemsfiles: Virtual filesystem state
- interrupt: Pending tool approval data (if interrupted)
- createdAt: ISO 8601 timestamp
- updatedAt: ISO 8601 timestamp
Built-in Checkpoint Savers
MemorySaver
In-memory storage (ephemeral, lost on process exit)
Use for: Testing, single-session applications
FileSaver
JSON file storage (persists to disk)
Use for: Local development, simple persistence
KeyValueStoreSaver
Adapter for custom storage backends
Use for: Production deployments (Redis, databases)
MemorySaver
In-memory storage (ephemeral, lost on process exit).
Features: Fast, simple, namespace support
Options:
namespace?: string- Optional namespace prefix for isolation (default: "default")
import { anthropic } from '@ai-sdk/anthropic';
import { createDeepAgent, MemorySaver } from 'ai-sdk-deep-agent';
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new MemorySaver(),
});
// With namespace
const agent2 = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new MemorySaver({ namespace: 'my-app' }),
});FileSaver
JSON file storage (persists to disk).
Features: Human-readable, easy debugging, survives restarts
Options:
dir: string- Directory to store checkpoint files (required)
import { FileSaver } from 'ai-sdk-deep-agent';
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new FileSaver({ dir: './.checkpoints' }),
});KeyValueStoreSaver
Adapter for KeyValueStore interface.
Features: Scalable, distributed, custom storage backends
Options:
store: KeyValueStore- The KeyValueStore implementation to use (required)namespace?: string- Optional namespace prefix for isolation (default: "default")
import { KeyValueStoreSaver, InMemoryStore } from 'ai-sdk-deep-agent';
const store = new InMemoryStore(); // Replace with RedisStore, DatabaseStore, etc.
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new KeyValueStoreSaver({ store, namespace: 'my-app' }),
});[namespace]/checkpoints/[threadId] in the key-value store.Basic Usage
Saving and Loading Sessions
import { anthropic } from '@ai-sdk/anthropic';
import { createDeepAgent, FileSaver } from 'ai-sdk-deep-agent';
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new FileSaver({ dir: './.checkpoints' }),
});
const threadId = 'user-session-123';
// First interaction - checkpoint is automatically saved
for await (const event of agent.streamWithEvents({
messages: [{ role: "user", content: "Create a project plan" }],
threadId,
})) {
if (event.type === 'checkpoint-saved') {
console.log(`Checkpoint saved at step ${event.step}`);
}
}
// Later: Resume same thread - checkpoint is automatically loaded
for await (const event of agent.streamWithEvents({
messages: [{ role: "user", content: "Now implement the first task" }],
threadId, // Same threadId loads the checkpoint
})) {
if (event.type === 'checkpoint-loaded') {
console.log(`Loaded ${event.messagesCount} messages from checkpoint`);
}
// Agent has full context from previous interaction
}Message Priority Logic
When resuming sessions, the agent follows this priority for message handling:
Explicit messages array - Takes precedence over everything
- Non-empty: Uses provided messages, replaces checkpoint history
- Empty (
[]): Clears checkpoint history, starts fresh
prompt parameter (deprecated) - Converted to message format
- Used only if
messagesis not provided - Will show deprecation warning in non-production environments
Checkpoint history - Default when neither messages nor prompt provided
- Loads conversation history from checkpoint
- Applies summarization if enabled
Example: Starting fresh from checkpoint
// Clear checkpoint history and start new conversation
for await (const event of agent.streamWithEvents({
messages: [], // Empty array clears checkpoint history
threadId: 'existing-session',
})) {
// Agent starts fresh without previous context
}Checkpoint Events
The agent emits two checkpoint-related events:
CheckpointSavedEvent - Emitted after each step when checkpoint is saved
{
type: "checkpoint-saved",
threadId: string,
step: number,
}CheckpointLoadedEvent - Emitted when a checkpoint is loaded on resume
{
type: "checkpoint-loaded",
threadId: string,
step: number,
messagesCount: number,
}Example: Tracking checkpoints
for await (const event of agent.streamWithEvents({
messages: [{ role: "user", content: "Hello" }],
threadId: 'my-session',
})) {
switch (event.type) {
case 'checkpoint-loaded':
console.log(`Resumed from step ${event.step} with ${event.messagesCount} messages`);
break;
case 'checkpoint-saved':
console.log(`Checkpoint saved at step ${event.step}`);
break;
}
}Tool Approval (HITL)
Human-in-the-Loop
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new FileSaver({ dir: './.checkpoints' }),
interruptOn: {
write_file: true, // Require approval for file writes
},
});
// Tool approval with callback
for await (const event of agent.streamWithEvents({
messages: [{ role: "user", content: "Write a config file" }],
threadId: 'session-123',
onApprovalRequest: async (request) => {
console.log(`Approval requested for ${request.toolName}`);
console.log(`Args:`, request.args);
return true; // Approve
},
})) {
if (event.type === 'checkpoint-saved') {
console.log(`Checkpoint saved at step ${event.step}`);
}
}Auto-Deny Behavior
interruptOn but no onApprovalRequest callback will be automatically denied (not executed).// Auto-deny behavior (no callback provided)
const agent2 = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new FileSaver({ dir: './.checkpoints' }),
interruptOn: {
write_file: true,
},
// Note: No onApprovalRequest callback provided
// Tools requiring approval will be automatically denied
});
for await (const event of agent2.streamWithEvents({
messages: [{ role: "user", content: "Write a file" }],
threadId: 'session-456',
})) {
// File write will be automatically denied
}Custom Checkpoint Saver
Implement BaseCheckpointSaver interface for custom storage:
import type { BaseCheckpointSaver, Checkpoint } from 'ai-sdk-deep-agent';
import { createDeepAgent } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
class RedisCheckpointSaver implements BaseCheckpointSaver {
constructor(private redis: RedisClient) {}
async save(checkpoint: Checkpoint): Promise<void> {
await this.redis.set(
`checkpoint:${checkpoint.threadId}`,
JSON.stringify(checkpoint)
);
}
async load(threadId: string): Promise<Checkpoint | undefined> {
const data = await this.redis.get(`checkpoint:${threadId}`);
return data ? JSON.parse(data) : undefined;
}
async list(): Promise<string[]> {
const keys = await this.redis.keys('checkpoint:*');
return keys.map(k => k.replace('checkpoint:', ''));
}
async delete(threadId: string): Promise<void> {
await this.redis.del(`checkpoint:${threadId}`);
}
async exists(threadId: string): Promise<boolean> {
return await this.redis.exists(`checkpoint:${threadId}`) === 1;
}
}
// Usage
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
checkpointer: new RedisCheckpointSaver(redisClient),
});CLI Session Management
The CLI supports session persistence via the --session flag:
# Start CLI with session
$ bun run cli --session my-project
# Session is auto-saved after each response
# Session is auto-restored on restart
# List all sessions
> /sessions
# Clear current session
> /session clearKnown Limitations
interruptOn but no onApprovalRequest callback will be automatically denied (not executed).Examples
See examples/checkpointer-demo.ts for comprehensive examples.