Deep Agents
AgentContextOrchestratorRetrievalText2SQLToolbox

Checkpoints

Named restore points for conversation branching

Checkpoints are named pointers to specific messages in the conversation graph. Create checkpoints before risky operations; restore to return to that point.

How Checkpoints Work

Conversation: msg-1 → msg-2 → msg-3 → msg-4

              checkpoint('before-choice')

Restore creates a new branch from the checkpoint:
    main → msg-4
    main-v2 → msg-2 (restored, now active)

Checkpoints persist in the database. Create them with checkpoint(); return to them with restore().

Creating Checkpoints

import {
  ContextEngine,
  SqliteContextStore,
  XmlRenderer,
  role,
  user,
  assistant,
} from '@deepagents/context';

const store = new SqliteContextStore('./chat.db');
const context = new ContextEngine({
  store,
  chatId: 'chat-001',
  userId: 'user-001',
}).set(role('You are helpful.'));

// Build conversation
context.set(user('I want to learn programming.'));
context.set(assistant('Great! Would you prefer Python or JavaScript?'));
await context.save();

// Save checkpoint before user's choice
const cp = await context.checkpoint('before-choice');
console.log(cp);
// {
//   id: 'uuid-xxx',
//   name: 'before-choice',
//   messageId: 'msg-xxx',  // points to the assistant message
//   createdAt: 1703123456789
// }

Note: Checkpoints require at least one saved message. You cannot checkpoint an empty conversation.

Restoring Checkpoints

restore() creates a new branch from the checkpoint's message:

// User chose Python
context.set(user('I want to learn Python.'));
context.set(assistant('Python is great for beginners!'));
await context.save();
// main → ... → assistant → user → assistant

// User wants to try JavaScript path instead
await context.restore('before-choice');
// Creates 'main-v2' branch from checkpoint
// Now at: main-v2 → ... → assistant (the checkpoint message)

context.set(user('I want to learn JavaScript.'));
context.set(assistant('JavaScript is excellent for web development!'));
await context.save();
// main-v2 → ... → assistant → user → assistant

Both branches are preserved:

  • main: Python learning path
  • main-v2: JavaScript learning path

Listing Checkpoints

const checkpoints = await store.listCheckpoints(context.chatId);
// [
//   { id: 'uuid-1', name: 'before-choice', messageId: 'msg-2', createdAt: ... },
//   { id: 'uuid-2', name: 'before-analysis', messageId: 'msg-5', createdAt: ... },
// ]

Deleting Checkpoints

await store.deleteCheckpoint(context.chatId, 'before-choice');

Checkpoints vs Rewind

Featurecheckpoint() + restore()rewind()
TargetNamed pointerMessage ID directly
Requires IDNo (uses name)Yes
PersistenceStored in databaseN/A
Use caseSave decision pointsQuick retry from known message

Use checkpoints when:

  • You want to name restore points semantically
  • You might restore from a different session
  • You're building undo/redo UI

Use rewind() when:

  • You have the message ID already
  • You want a quick one-off branch
  • You're retrying immediately

Retry Loop Pattern

Use checkpoints to retry until successful:

import { generateText } from 'ai';
import { groq } from '@ai-sdk/groq';

async function askWithRetry(
  context: ContextEngine,
  question: string,
  maxRetries = 3,
): Promise<string> {
  context.set(user(question));
  await context.save();

  // Checkpoint after saving question
  await context.checkpoint('before-response');

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const { systemPrompt, messages } = await context.resolve({
      renderer: new XmlRenderer(),
    });

    const response = await generateText({
      model: groq('gpt-oss-20b'),
      system: systemPrompt,
      messages,
      temperature: 0.7 + attempt * 0.1, // Increase temperature on retries
    });

    if (isGoodResponse(response.text)) {
      context.set(assistant(response.text));
      await context.save();
      return response.text;
    }

    // Bad response - restore and try again
    console.log(`Attempt ${attempt + 1} failed, retrying...`);
    await context.restore('before-response');
  }

  throw new Error('Max retries exceeded');
}

function isGoodResponse(text: string): boolean {
  return text.length > 10 && !text.includes('I cannot');
}

Multi-Stage Workflow

Create checkpoints at each stage of a complex workflow:

// Stage 1: Gather requirements
context.set(user('Help me build a todo app'));
context.set(assistant('What features do you need?'));
await context.save();
await context.checkpoint('requirements');

// Stage 2: Design
context.set(user('I need tasks with due dates and priorities'));
context.set(assistant('Here is the proposed architecture...'));
await context.save();
await context.checkpoint('design');

// Stage 3: Implementation
context.set(user('Start with the data model'));
context.set(assistant('Here is the schema...'));
await context.save();
await context.checkpoint('implementation');

// User wants to revisit design
await context.restore('design');
// Now back at design stage, can take different direction

Checkpoint Naming Conventions

Use descriptive names that indicate the decision point:

// Good
await context.checkpoint('before-language-choice');
await context.checkpoint('after-requirements-gathered');
await context.checkpoint('pre-final-review');

// Less clear
await context.checkpoint('checkpoint-1');
await context.checkpoint('save');

Error Handling

try {
  await context.checkpoint('my-checkpoint');
} catch (error) {
  // "Cannot create checkpoint: no messages in conversation"
}

try {
  await context.restore('nonexistent');
} catch (error) {
  // 'Checkpoint "nonexistent" not found in chat "chat-001"'
}

Checkpoints in the Graph

Checkpoints appear in the graph data:

const graph = await store.getGraph(context.chatId);
console.log(graph.checkpoints);
// [
//   { id: '...', name: 'before-choice', messageId: 'msg-2', createdAt: ... },
// ]

Next Steps