Deep Agents
AgentContextOrchestratorRetrievalText2SQLToolbox

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-v2

Error 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

ScenarioUse
Have message IDrewind(messageId)
Want named restore pointcheckpoint() + restore()
Quick retryrewind()
Cross-session restorecheckpoint()

Both create new branches. The difference:

  • rewind() takes a message ID directly
  • restore() 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 ID

This preserves the original message on the original branch.

Best Practices

  1. Always use custom IDs for messages you might rewind to:

    user('Question', { id: 'q1' })
  2. Track message IDs if building UI:

    const messageIds: string[] = [];
    const id = `msg-${Date.now()}`;
    messageIds.push(id);
    context.set(user(text, { id }));
  3. Save before rewind - pending messages are cleared:

    await context.save();
    await context.rewind('q1');
  4. 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