Skip to main content
Conversations & Sessions

File Checkpoint and Rewind

File checkpointing records the state of local files modified by tools within a session. With enableFileCheckpointing (TypeScript) / enable_file_checkpointing (Python) enabled, callers can use rewindFiles(userMessageId, ...) / rewind_files(user_message_id, ...) to roll files back to the state when a given user message started processing. The two capabilities work together: without checkpointing enabled, rewind has no file snapshots to use.

Enabling File Checkpoint

import { query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Refactor src/foo.ts into a cleaner implementation',
  options: {
    cwd: '/path/to/project',
    enableFileCheckpointing: true,
    allowedTools: ['Read', 'Edit', 'Write'],
    permissionMode: 'acceptEdits',
  },
});
In Python, keep the same active session with QoderSDKClient when you need to rewind later. The extra_args={"replay-user-messages": None} in the example is not the checkpoint switch; it makes the response stream replay UserMessages carrying the uuid usable as rewind anchors—if your app lets users pick "go back to before this turn", you usually want it as well.

Getting the rewind anchor: user message ID

Rewind is anchored on user message IDs, and the two SDKs obtain them differently:
  • TypeScript: for precise rewinds, use structured input and generate the uuid yourself so the UI can reliably map back to "before that message".
  • Python: the common approach is to capture the ID from UserMessage.uuid in the response stream (together with extra_args={"replay-user-messages": None}).
import { randomUUID } from 'node:crypto';
import { query } from '@qodercn-ai/qodercn-agent-sdk';

const userMessageId = randomUUID();

async function* input() {
  yield {
    type: 'user' as const,
    uuid: userMessageId,
    parent_tool_use_id: null,
    message: {
      role: 'user' as const,
      content: [
        {
          type: 'text' as const,
          text: 'Rewrite notes.txt as a two-line summary.',
        },
      ],
    },
  };

  // If your application needs to call rewind later within the same session,
  // keep yielding subsequent user inputs here instead of closing the stream.
}

const q = query({
  prompt: input(),
  options: {
    cwd: '/path/to/project',
    enableFileCheckpointing: true,
    allowedTools: ['Read', 'Write'],
    permissionMode: 'acceptEdits',
  },
});
The rewind anchor is the user message's uuid—not the session_id, and not the result message's ID. It is only valid within the session context that produced the checkpoint; other sessions cannot rewind with it directly.

Dry Run Preview

Before executing a rewind, dry run first to preview the impact: whether rewinding is possible, which files are affected, and the overall insertion/deletion stats. A dry run modifies no files—ideal for confirmation dialogs or audit logs. The returned RewindFilesResult contains these fields:
FieldTypeDescription
canRewindbooleanWhether rollback can be performed. In dry run mode failures don't throw — this field indicates the status
errorstring?Diagnostic text when canRewind is false; can be shown to users directly
filesChangedstring[]?Absolute paths of affected files — useful for listing each file that will be reverted in the UI
insertionsnumber?Total number of added lines the rewind would undo (aggregate)
deletionsnumber?Total number of deleted lines the rewind would undo (aggregate)
The SDK currently only returns the list of affected files and aggregate line-level statistics in RewindFilesResult — it does not return per-file diffs. If you need per-file differences, you can read the disk contents based on filesChanged after the dry run and compare them with the checkpoint, or use git/workspace diffing tools after executing the rewind.
const preview = await q.rewindFiles(userMessageId, { dryRun: true });

if (!preview.canRewind) {
  // Show the diagnostic message in the UI.
  console.error(preview.error);
  return;
}

// Overall stats across all affected files.
console.log({
  files: preview.filesChanged?.length ?? 0,
  insertions: preview.insertions ?? 0,
  deletions: preview.deletions ?? 0,
});

// Per-file listing — useful for a confirmation dialog.
for (const file of preview.filesChanged ?? []) {
  console.log(`will be reverted: ${file}`);
}

Executing Rewind

After confirming the impact, execute the rewind by omitting the dry-run parameter:
const result = await q.rewindFiles(userMessageId);
console.log(result.filesChanged);
Rewind only restores the local file state tracked by the checkpoint; it does not roll back conversation history. That is, the model still retains context from earlier turns; the UI must refresh the editor, file tree, or diff view itself based on filesChanged.

Failure Semantics

Call FormBehavior When Rewind Is Not Possible
Dry-run mode (rewindFiles(id, { dryRun: true }) / rewind_files(id, dry_run=True))Returns { canRewind: false, error }, convenient for showing diagnostics in the UI
Execute mode (rewindFiles(id) / rewind_files(id))The Promise rejects in TypeScript; Python raises. Callers should catch and surface the failure
try {
  await q.rewindFiles(userMessageId);
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
}
Common failure causes: file checkpointing not enabled, the ID is not a valid user-message UUID, the ID belongs to another session, or the target message has no rewindable file snapshot.

Settings Relationship

The settings field in options can be combined with the file-checkpoint switch. It accepts a Settings object or an absolute path string to a settings file:
  • With a settings object, the SDK automatically merges in general.fileCheckpointing.enabled = true—no need to write it by hand. Existing settings fields are preserved; an existing fileCheckpointing config has its enabled overridden by the SDK option.
  • With a settings file path string, the SDK does not rewrite the file; configure it yourself:
{
  "general": {
    "fileCheckpointing": {
      "enabled": true
    }
  }
}
Setting only the checkpoint switch without settings is enough for pure rewind scenarios.
options: {
  cwd: '/path/to/project',
  settings: { theme: 'dark' },
  enableFileCheckpointing: true,
}

Boundaries

  • Only local file checkpoints are rewound; external side effects from MCP tools, remote services, or databases are not undone.
  • File changes made by writing files directly through Bash are not treated as rewindable file snapshots.
  • File contents can be restored; directory-level side effects such as directory creation may not be undone.
  • The checkpoint ID is bound to the session. After resuming the same session, the corresponding ID can still be used; it cannot be mixed across different sessions.

Field Reference

Entry (TypeScript / Python)Description
enableFileCheckpointing / enable_file_checkpointingEnables file checkpointing for rewind
settings / settingsSettings passed to the CLI; with an object the SDK merges general.fileCheckpointing.enabled
extra_args (Python only)Pass {"replay-user-messages": None} to receive UserMessage.uuid in the stream
q.rewindFiles(userMessageId, { dryRun }) / client.rewind_files(user_message_id, dry_run=False)Preview or execute a file rewind

Return Value Reference

type RewindFilesResult = {
  canRewind: boolean;
  error?: string;
  filesChanged?: string[];
  insertions?: number;
  deletions?: number;
};

Best Practices

  • Save the user message ID: apps that need rewind should save the uuid when sending messages (in Python, bind UserMessage.uuid to your UI message records) instead of reverse-searching UI text.
  • Dry run before rewinding: Show the impact scope first, then let the user confirm the rollback.
  • Refresh UI after rollback: Rewind only changes files, not conversation history; the UI needs to reload relevant views based on filesChanged.
  • Show error to users on failure: the error text when canRewind is false is usually suitable for end-user diagnostics as-is.