Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
File checkpoint records the state of local files modified by tools during a session. Once enabled, the host application can call QoderSDKClient.rewind_files(user_message_id, ...) to roll back tracked files to the state they were in when a particular user message started processing.
These two capabilities must be used together: without enable_file_checkpointing=True, rewind_files() has no file snapshots to use.
Enabling File Checkpoint
enable_file_checkpointing is a field on QoderAgentOptions. To enable subsequent rewinds, use QoderSDKClient to keep the same active session and turn on checkpointing in options:
from qoder_agent_sdk import QoderAgentOptions, QoderSDKClient
options = QoderAgentOptions(
cwd="/path/to/project",
enable_file_checkpointing=True,
extra_args={"replay-user-messages": None},
allowed_tools=["Read", "Edit", "Write"],
permission_mode="acceptEdits",
)
async with QoderSDKClient(options=options) as client:
await client.query("Refactor src/foo.py into a cleaner implementation.")
async for message in client.receive_response():
...
extra_args={"replay-user-messages": None} is not the switch that enables checkpointing. Its purpose is to replay UserMessage events in the response stream, carrying the uuid that can serve as a rewind anchor. If your application needs the user to click “go back to before this turn,” you typically set both.
Obtaining the Checkpoint ID
rewind_files() uses the user message ID as its anchor. The common pattern in the Python SDK is to capture this ID from UserMessage.uuid in the response stream:
from qoder_agent_sdk import (
QoderAgentOptions,
QoderSDKClient,
ResultMessage,
UserMessage,
)
options = QoderAgentOptions(
cwd="/path/to/project",
enable_file_checkpointing=True,
extra_args={"replay-user-messages": None},
allowed_tools=["Write"],
permission_mode="acceptEdits",
)
checkpoint_id: str | None = None
async with QoderSDKClient(options=options) as client:
await client.query("Rewrite notes.txt as a two-line summary.")
async for message in client.receive_response():
if (
isinstance(message, UserMessage)
and message.uuid
and message.parent_tool_use_id is None
):
checkpoint_id = message.uuid
if isinstance(message, ResultMessage) and message.is_error:
raise RuntimeError(message.result or "Query failed.")
if checkpoint_id is None:
raise RuntimeError("No user message UUID was returned.")
checkpoint_id is the user message’s uuid — not the session_id, and not the ResultMessage ID. It is only valid in the session context that produced the checkpoint; other sessions cannot use this ID to rewind directly.
Dry Run Preview
Before executing a rewind, it is recommended to preview the impact with dry_run=True. A dry run does not modify files, making it suitable for confirmation dialogs or audit logs.
preview = await client.rewind_files(checkpoint_id, dry_run=True)
if not preview["canRewind"]:
print(preview.get("error", "Unable to rewind files."))
else:
print(
{
"files": len(preview.get("filesChanged", [])),
"insertions": preview.get("insertions", 0),
"deletions": preview.get("deletions", 0),
}
)
for file_path in preview.get("filesChanged", []):
print(f"will be reverted: {file_path}")
RewindFilesResult contains these fields:
| Field | Type | Description |
|---|
canRewind | bool | Whether the rewind can be performed. Dry run does not throw on failure; this field signals the result |
error | str | Diagnostic message when canRewind=False |
filesChanged | list[str] | List of file paths that will be reverted, or have already been reverted |
insertions | int | Total inserted lines that the rewind will undo (aggregate) |
deletions | int | Total deleted lines that the rewind will undo (aggregate) |
The current return value only contains the list of affected files and aggregate line-level statistics — it does not return per-file diffs. When you need to display per-file differences, do a dry run first to obtain filesChanged, then combine it with your own workspace diff logic for display.
Executing Rewind
Once you have confirmed the impact scope, omit dry_run to execute the rewind:
result = await client.rewind_files(checkpoint_id)
if result["canRewind"]:
print(result.get("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 form | Behavior when rewind is not possible |
|---|
await client.rewind_files(id, dry_run=True) | Returns {"canRewind": False, "error": ...}, suitable for direct diagnostic display |
await client.rewind_files(id) | Raises an exception; the caller should catch it and display the failure reason |
try:
await client.rewind_files(checkpoint_id)
except Exception as exc:
print(str(exc))
Common failure causes include: enable_file_checkpointing is not enabled, the supplied ID is not a valid user message UUID, the ID does not belong to the current session, or the target message has no rewindable file snapshots.
Settings Relationship
QoderAgentOptions.settings can be used together with enable_file_checkpointing:
options = QoderAgentOptions(
cwd="/path/to/project",
settings={"theme": "dark"},
enable_file_checkpointing=True,
)
When enable_file_checkpointing=True, the SDK merges general.fileCheckpointing.enabled = True into the settings passed to the CLI. Other settings fields are preserved; if a fileCheckpointing configuration already exists, enabled is taken from the SDK option.
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 point | Type | Description | |
|---|
QoderAgentOptions.enable_file_checkpointing | `bool | None` | Enables file checkpoint for use with rewind_files() |
QoderAgentOptions.extra_args | `dict[str, str | None]` | Pass {"replay-user-messages": None} to receive UserMessage.uuid in the stream |
QoderSDKClient.rewind_files(user_message_id, dry_run=False) | async method | Preview or execute file rewind | |
Best Practices
- Save the user message UUID: Bind
UserMessage.uuid to your UI’s message records, instead of looking them up by text.
- Dry run before executing: Show the affected files and statistics first, then have the user confirm the rewind.
- Refresh UI after rewind: Reload relevant file state based on
filesChanged.
- Show
error on failure: The error returned by dry run is typically suitable as user-visible diagnostics.