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 → assistantBoth branches are preserved:
main: Python learning pathmain-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
| Feature | checkpoint() + restore() | rewind() |
|---|---|---|
| Target | Named pointer | Message ID directly |
| Requires ID | No (uses name) | Yes |
| Persistence | Stored in database | N/A |
| Use case | Save decision points | Quick 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 directionCheckpoint 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: ... },
// ]