Rewind
Branch from a specific message to retry or explore alternatives
rewind() creates a new branch from a specific message ID. The original conversation is preserved; you continue on the new branch.
How Rewind Works
Before rewind('msg-2'):
main → msg-1 → msg-2 → msg-3 → msg-4
After rewind('msg-2'):
main → msg-1 → msg-2 → msg-3 → msg-4 (preserved)
main-v2 → msg-1 → msg-2 (new branch, active)Key insight: Rewind doesn't delete anything. It creates a new branch pointing to the target message. The original branch stays intact.
Basic Usage
import {
ContextEngine,
SqliteContextStore,
XmlRenderer,
role,
user,
assistant,
} from '@deepagents/context';
import { generateText } from 'ai';
import { groq } from '@ai-sdk/groq';
const store = new SqliteContextStore('./chat.db');
const context = new ContextEngine({
store,
chatId: 'chat-001',
userId: 'user-001',
}).set(role('You are helpful.'));
// Conversation with custom IDs for easy rewind
context.set(user('What is 2 + 2?', { id: 'q1' }));
const { systemPrompt, messages } = await context.resolve({
renderer: new XmlRenderer(),
});
const response = await generateText({
model: groq('gpt-oss-20b'),
system: systemPrompt,
messages,
});
context.set(assistant(response.text, { id: 'a1' }));
await context.save();
// Response was wrong - rewind to question
const newBranch = await context.rewind('q1');
console.log(newBranch);
// {
// id: 'uuid-xxx',
// name: 'main-v2',
// headMessageId: 'q1',
// isActive: true,
// messageCount: 1,
// createdAt: 1703123456789
// }
// Now on main-v2, try again with different prompt
context.set(hint('Double-check your arithmetic.'));
const { systemPrompt: sp2, messages: m2 } = await context.resolve({
renderer: new XmlRenderer(),
});
const retry = await generateText({
model: groq('gpt-oss-20b'),
system: sp2,
messages: m2,
});
context.set(assistant(retry.text));
await context.save();Message IDs
The user() and assistant() helpers auto-generate UUIDs. For easy rewind, provide custom IDs:
// Auto-generated (harder to rewind to)
context.set(user('Hello'));
// Custom ID (easy to rewind to)
context.set(user('Hello', { id: 'greeting' }));Common patterns:
// Turn-based
user('Question', { id: 'turn-1-user' });
assistant('Answer', { id: 'turn-1-assistant' });
// Semantic
user('What language?', { id: 'language-question' });
assistant('Python!', { id: 'language-answer' });
// Timestamp-based
user('Hello', { id: `msg-${Date.now()}` });Return Value
rewind() returns BranchInfo:
interface BranchInfo {
id: string; // Branch UUID
name: string; // e.g., 'main-v2'
headMessageId: string; // Message this branch points to
isActive: boolean; // Always true after rewind
messageCount: number; // Messages in this branch
createdAt: number; // Timestamp
}Branch Naming
Rewind auto-generates branch names based on the current branch:
main → rewind → main-v2
main → rewind → main-v3
main-v2 → rewind → main-v2-v2Error Handling
try {
await context.rewind('nonexistent-id');
} catch (error) {
// 'Message "nonexistent-id" not found'
}
try {
await context.rewind('msg-from-other-chat');
} catch (error) {
// 'Message "..." belongs to a different chat'
}Regenerate Response Pattern
Rewind to the user's question, regenerate the AI response:
async function regenerate(
context: ContextEngine,
questionId: string,
): Promise<string> {
// Rewind to the question
await context.rewind(questionId);
// Regenerate with higher temperature for variety
const { systemPrompt, messages } = await context.resolve({
renderer: new XmlRenderer(),
});
const response = await generateText({
model: groq('gpt-oss-20b'),
system: systemPrompt,
messages,
temperature: 0.9,
});
context.set(assistant(response.text));
await context.save();
return response.text;
}
// Usage
const newAnswer = await regenerate(context, 'q1');Edit and Continue Pattern
User wants to change their question:
async function editQuestion(
context: ContextEngine,
originalId: string,
newQuestion: string,
): Promise<void> {
// Find the message before the original question
const graph = await store.getGraph(context.chatId);
const original = graph.nodes.find(n => n.id === originalId);
if (original?.parentId) {
// Rewind to parent (message before the question)
await context.rewind(original.parentId);
} else {
// Question was first message - start fresh branch
await context.rewind(originalId);
}
// Add edited question
context.set(user(newQuestion));
await context.save();
}Multiple Retries Pattern
Try multiple times with different parameters:
async function retryUntilGood(
context: ContextEngine,
questionId: string,
maxAttempts = 3,
): Promise<string> {
for (let i = 0; i < maxAttempts; i++) {
// Always rewind to same question
await context.rewind(questionId);
const { systemPrompt, messages } = await context.resolve({
renderer: new XmlRenderer(),
});
const response = await generateText({
model: groq('gpt-oss-20b'),
system: systemPrompt,
messages,
temperature: 0.5 + i * 0.2,
});
if (isAcceptable(response.text)) {
context.set(assistant(response.text));
await context.save();
return response.text;
}
}
throw new Error('Could not generate acceptable response');
}Rewind vs Checkpoint
| Scenario | Use |
|---|---|
| Have message ID | rewind(messageId) |
| Want named restore point | checkpoint() + restore() |
| Quick retry | rewind() |
| Cross-session restore | checkpoint() |
Both create new branches. The difference:
rewind()takes a message ID directlyrestore()looks up a named checkpoint first
Internal: How Rewind Preserves Pending Messages
When you update an existing message (same ID), rewind happens automatically:
// Message exists with id 'a1'
context.set(assistant('Updated answer', { id: 'a1' }));
await context.save();
// Internally: rewinds to parent, creates new branch, saves with new IDThis preserves the original message on the original branch.
Best Practices
-
Always use custom IDs for messages you might rewind to:
user('Question', { id: 'q1' }) -
Track message IDs if building UI:
const messageIds: string[] = []; const id = `msg-${Date.now()}`; messageIds.push(id); context.set(user(text, { id })); -
Save before rewind - pending messages are cleared:
await context.save(); await context.rewind('q1'); -
Use switchBranch() to return to original:
await context.rewind('q1'); // Creates main-v2 // ... do work on main-v2 ... await context.switchBranch('main'); // Back to original
Next Steps
- Checkpoints - Named restore points
- Branching - Full branching guide
- Context Engine - Complete API reference