ai-sdk-deepagent

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 items
    • files: 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' }),
});
Checkpoints are stored under [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 messages is 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

When using HITL tool approval, checkpoints save the interrupt state for seamless resumption
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

Tools configured with 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 clear

Known Limitations

Auto-Deny Behavior: Tools configured with interruptOn but no onApprovalRequest callback will be automatically denied (not executed).

Examples

See examples/checkpointer-demo.ts for comprehensive examples.

See Also

On this page