Checkpoints
File-level undo and restore using shadow git snapshots before agent edits
Save and rewind workspace snapshots before agents change files โ powered by a shadow git repository.
from praisonaiagents import Agent
agent = Agent(name="coder", instructions="Edit project files safely.")
agent.start("Refactor the auth module.")The user runs an agent that edits files; checkpoints snapshot the workspace so you can diff or restore before bad changes land.
Quick Start
- Attach checkpoints to an agent
python from praisonaiagents import Agent from praisonaiagents.checkpoints import CheckpointService checkpoints = CheckpointService(workspace_dir="./my_project") agent = Agent( name="RefactorBot", instructions="You are a code refactoring assistant.", checkpoints=checkpoints, ) agent.start("Refactor the codebase to improve readability") - Undo the last turn in one line
python from praisonaiagents import Agent from praisonaiagents.checkpoints import CheckpointService checkpoints = CheckpointService(workspace_dir="./my_project") agent = Agent(name="RefactorBot", instructions="Refactor code.", checkpoints=checkpoints) agent.start("Refactor the auth module") # Something went wrong โ undo the last turn's file edits in one line await checkpoints.rewind() - Save and restore from CLI
bash praisonai checkpoint save "Before major changes" praisonai checkpoint list praisonai checkpoint restore last praisonai checkpoint rewind # undo the most recent checkpoint in one command - One-liner undo after a run
bash praisonai run agents.yaml praisonai run --restore last
Overview
Checkpoints allow you to:
- Save snapshots of your workspace before changes
- Restore files to any previous checkpoint
- Rewind N checkpoints back from the latest
- Diff between checkpoints to see what changed
- Track all file modifications made by agents
Rewind vs Restore
Both rewind the workspace, but they target checkpoints differently.
| You want toโฆ | Use | Example | |
|---|---|---|---|
| Undo the very last set of changes | rewind (default 1) | praisonai checkpoint rewind | |
| Step back N checkpoints from now | rewind N | praisonai checkpoint rewind 3 | |
| Go to a specific checkpoint you remember | `restore <id\ | last>` | praisonai checkpoint restore abc1234 |
| Go to a checkpoint tagged with a step index | restore --step N | praisonai checkpoint restore --step 3 |
Restore/rewind guarantees
| Situation | Result |
|---|---|
| File deleted before the checkpoint | Stays deleted after restore |
| File added after the checkpoint | Removed by restore (no leak) |
Ignored file (.env, venv, secrets) | Preserved โ never snapshotted, never pruned |
Selective restore (files=[...] / partial) | Only listed files touched; no project prune |
Uncommitted work when restore() is called | Auto-saved as a [pre-restore] auto-save checkpoint; recoverable via restore(<pre_id>) |
Uncommitted file above max_file_size | Preserved on disk; not deleted by restore |
Recoverable restore
restore() and rewind() auto-save uncommitted work before touching the workspace. Restore to the wrong checkpoint? Restore the auto-save and your changes come back.
from praisonaiagents.checkpoints import CheckpointService, CheckpointEvent
service = CheckpointService(workspace_dir="./my_project")
await service.initialize()
# Remember the latest auto-save
pre_restore = []
service.on(
CheckpointEvent.CHECKPOINT_CREATED,
lambda cp: cp.message == "[pre-restore] auto-save" and pre_restore.append(cp.id),
)
with open("./my_project/a.txt", "w") as f:
f.write("a")
baseline = await service.save("base")
# New work, not checkpointed
with open("./my_project/unsaved.txt", "w") as f:
f.write("precious")
await service.restore(baseline.checkpoint.id) # unsaved.txt is gone from disk
await service.restore(pre_restore[-1]) # unsaved.txt is backsequenceDiagram
participant User
participant S as CheckpointService
participant G as Shadow git
User->>S: restore(baseline_id)
S->>G: save "[pre-restore] auto-save"
G-->>S: pre_id
S->>G: update-ref refs/pre-restore/pre_id
S->>G: reset --hard baseline_id
G-->>User: workspace at baseline
User->>S: restore(pre_id)
S->>G: reset --hard pre_id
G-->>User: unsaved work recovered
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef svc fill:#189AB4,stroke:#7C90A0,color:#fff
classDef git fill:#6366F1,stroke:#7C90A0,color:#fff
class User user
class S svc
class G gitHow It Works
A user rewinds the workspace to a pre-turn state after a bad edit.
sequenceDiagram
participant User
participant Agent
participant Checkpoints as CheckpointService
User->>Agent: "Refactor auth"
Agent->>Checkpoints: save("before turn 1")
Agent->>Agent: edit files
User->>Agent: "This broke tests"
User->>Checkpoints: rewind()
Checkpoints->>Checkpoints: save("[pre-restore] auto-save")
Checkpoints-->>User: workspace restored to pre-turn-1 state (unsaved work recoverable)
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef agent fill:#189AB4,stroke:#7C90A0,color:#fff
classDef cp fill:#F59E0B,stroke:#7C90A0,color:#fff
class User user
class Agent agent
class Checkpoints cpIn-session /undo and /revert
/undo and /revert [n] are first-class commands on the interactive surfaces โ the modern REPL used by praisonai code and the default async TUI used by bare praisonai. They roll back files + conversation together, and checkpointing is on by default on these surfaces. Programmatic callers of SessionCheckpointManager.from_config stay opt-in, so the zero-overhead contract is preserved.
# Start an interactive session โ checkpointing is on by default
praisonai code "Refactor the auth module"
# ... the agent edits several files ...
# Something went wrong โ undo the last turn (files + conversation)
> /undo
# Or roll back the last three turns
> /revert 3No config, no flags.
Precedence (highest last): default โ checkpoints.auto (config) โ PRAISONAI_CHECKPOINTS (env)
Interactive surfaces pass default=True, so /undo and /revert work out of the box. Opt out with checkpoints.auto: false in config or PRAISONAI_CHECKPOINTS=off.
sequenceDiagram
participant User
participant REPL as code REPL
participant CP as Checkpoint Manager
participant Store as Session Store
User->>REPL: [starts session with --checkpoints]
REPL->>CP: baseline checkpoint
User->>REPL: prompt โ edits files
REPL->>CP: checkpoint_turn(message_count=N)
User->>REPL: /undo
REPL->>CP: preview(1)
CP-->>REPL: diff + dropped_message_count
REPL->>CP: revert(1)
CP-->>REPL: workspace restored
CP->>Store: revert_to_message(N)
Store-->>CP: chat_history truncated
CP-->>REPL: ready
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef repl fill:#189AB4,stroke:#7C90A0,color:#fff
classDef cp fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef store fill:#6366F1,stroke:#7C90A0,color:#fff
class User user
class REPL repl
class CP cp
class Store store/undo mode comparison:
| Mode | What /undo does |
|---|---|
| Checkpointing off (default) | Removes the last assistant + user message pair from history only. Workspace files are untouched. |
| Checkpointing on, session store not wired | Restores workspace files to the previous turn’s snapshot. Conversation history is left intact. (Legacy / file-only fallback.) |
Checkpointing on, session store wired (default in praisonai code and bare praisonai) | Restores workspace files and rewinds chat_history to the message count captured at checkpoint_turn(), so the agent’s memory matches the workspace. Diff preview shown first. |
Preview reports dropped-message count. preview(n) now returns dropped_message_count alongside the file diff, so the CLI can show how many chat turns would be discarded before you confirm. If the session store is not wired in, dropped_message_count is None.
preview = manager.preview(1)
print(preview.dropped_message_count) # 3 โ messages that would be truncated/revert [n] rolls back n turns (default 1). After revert, the timeline drops the restored turns so the next /undo walks further back.
Now active in the async TUI too. The same wiring runs in praisonai tui. Previously the TUI’s /undo was a stub that printed “use git diff” โ it now shares the coding REPL’s engine, so Undo: Workspace restored to <short_id> (<label>). is reported on success. The TUI also gets /revert [n] (not just /undo), and read-only review turns (--plan) are skipped so the revert timeline stays meaningful.
/revert on the async TUI
sequenceDiagram
participant User
participant TUI as Async TUI / REPL
participant CP as SessionCheckpointManager
participant View as Visible history
participant Agent as Warm Agent (chat_history)
User->>TUI: prompt (turn N)
TUI->>CP: checkpoint_turn(label)
TUI->>Agent: run turn N (edits files, appends messages)
Agent-->>View: user + assistant messages
User->>TUI: /revert 2
TUI->>CP: preview(2) โ diff shown
TUI->>CP: revert(2)
CP-->>View: rewind by 2 user turns
CP-->>Agent: rewind chat_history to same boundary (user-count matched)
CP-->>User: "Reverted 2 turn(s) โ files + conversation restored"
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef ui fill:#189AB4,stroke:#7C90A0,color:#fff
classDef cp fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef store fill:#6366F1,stroke:#7C90A0,color:#fff
class User user
class TUI ui
class CP cp
class View,Agent storeOne-shot rewind from praisonai run
praisonai run --revert[=last|N] reverts files and the named --session‘s conversation together, showing the diff first. Distinct from the file-only --restore. See Run โ Checkpoint & Rewind.
Project config:
# agents.yaml
checkpoints:
auto: true
storage_dir: ./.praisonai/checkpoints # optionalEnv override: PRAISONAI_CHECKPOINTS=on (or off).
Precedence: PRAISONAI_CHECKPOINTS (env) > checkpoints.auto (config) > default. Interactive surfaces pass default=True (on by default); programmatic callers default to off.
In-session /diff (session file changes)
You have been editing files for an hour and want to see what changed โ type /diff.
/diff diffs the session-start baseline against the current working directory using the same CheckpointService engine that powers checkpoints and /undo. Pick a scope:
graph TB
Q{What do you want to see?} -->|Everything since I started| A[/diff]
Q -->|Just the last turn| B[/diff --turn]
Q -->|One file only| C[/diff path/to/file.py]
classDef q fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef opt fill:#189AB4,stroke:#7C90A0,color:#fff
class Q q
class A,B,C opt/diff # all changes since session start
/diff --turn # only the last turn's changes (alias: -t)
/diff <file> # changes to a single file since session startThe <file> filter matches a path when p == wanted or p.endswith("/" + wanted), so /diff src/foo.py matches <repo>/src/foo.py.
sequenceDiagram
participant User
participant CLI as REPL / TUI
participant CP as Checkpoint Manager
participant Engine as CheckpointService
CLI->>CP: baseline checkpoint (session start)
User->>CLI: prompt โ edits files
User->>CLI: /diff
CLI->>CP: diff(turn_only=False)
CP->>Engine: diff(baseline_id, working_dir)
Engine-->>CP: CheckpointDiff
CP-->>CLI: render_diff(diff, scope="session")
CLI-->>User: per-file summary
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef cli fill:#189AB4,stroke:#7C90A0,color:#fff
classDef cp fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef engine fill:#6366F1,stroke:#7C90A0,color:#fff
class User user
class CLI cli
class CP cp
class Engine engineRequires auto-checkpointing (checkpoints.auto: true or PRAISONAI_CHECKPOINTS=on). When disabled, /diff prints the enable hint verbatim rather than failing silently:
Workspace checkpointing is disabled, so /diff has no session baseline to compare against. Enable it with checkpoints.auto: true in config or PRAISONAI_CHECKPOINTS=on.When there are no changes, the REPL prints No file changes for <target>. and the TUI prints No file changes this <scope>.
TUI baseline + pre-turn checkpoints. The async TUI records a session-start baseline on on_mount and a pre-turn checkpoint before every submission (checkpoint_turn(content[:60])), so /diff and /undo both work turn-by-turn in the TUI just like the legacy REPL. This fixed a prior bug where the manager was built lazily on first /diff//undo โ /diff omitted prior edits and /undo restored the post-edit state.
CLI Commands
# Save a checkpoint (--allow-empty to snapshot even with no changes)
praisonai checkpoint save "Before major changes"
praisonai checkpoint save "Before major changes" --allow-empty
# List checkpoints (newest first; -n to limit)
praisonai checkpoint list
praisonai checkpoint list -n 10
# Show diff โ accepts last/latest, full id, short id, or unique prefix
praisonai checkpoint diff
praisonai checkpoint diff last
praisonai checkpoint diff abc12345 def67890
# Restore to a checkpoint
praisonai checkpoint restore last
praisonai checkpoint restore abc12345
# Restore by per-step index (checkpoints saved with save(step=N))
praisonai checkpoint restore --step 3
# Rewind N checkpoints back from the latest (default: 1 = undo last checkpoint)
praisonai checkpoint rewind
praisonai checkpoint rewind 3
# Delete all checkpoints (-y to skip confirm prompt)
praisonai checkpoint delete
praisonai checkpoint delete --yes
# All subcommands accept -w to target a specific workspace directory
praisonai checkpoint list -w /path/to/project
praisonai checkpoint restore last -w /path/to/projectAutomatic Checkpoints with praisonai run
praisonai run snapshots your workspace automatically before every YAML-file run.
# Project config โ opt out per project
checkpoints:
auto: false- Default:
trueโ automatic checkpoints are on for all YAML-file runs. - Scoped to YAML runs: plain-prompt runs (
praisonai run "โฆ") are skipped. - Workspace: the directory of the target YAML file, not the cwd.
- Label:
run:<run_id>(or"auto checkpoint before run"as a fallback). - Best-effort: failures are swallowed and never block the run.
- Per-run override:
praisonai run agents.yaml --no-checkpoint.
praisonai run agents.yaml # auto-checkpoint, then run
praisonai run --restore last # rewind workspace, exit
praisonai run agents.yaml --no-checkpoint # skip auto-checkpointConfiguration
from praisonaiagents.checkpoints import CheckpointService
service = CheckpointService(
workspace_dir="/path/to/project",
storage_dir="~/.praisonai/checkpoints",
enabled=True,
auto_checkpoint=True,
max_checkpoints=100,
max_file_size=2 * 1024 * 1024, # 2 MB; 0 disables the cap
)Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
workspace_dir | str | โ | Directory to track. |
storage_dir | str | ~/.praisonai/checkpoints | Where the shadow-git store lives. |
enabled | bool | True | Enable / disable checkpoints. |
auto_checkpoint | bool | True | Auto-checkpoint before file modifications. |
max_checkpoints | int | 100 | In-memory view limit for get_checkpoint() and the search depth of restore(step=N). Shadow git keeps every commit; delete_all() reclaims storage by wiping all checkpoints. |
user_name | str | "PraisonAI Checkpoints" | Git user.name for shadow commits (env: PRAISONAI_GIT_USER_NAME). |
user_email | str | "checkpoints@praison.ai" | Git user.email for shadow commits (env: PRAISONAI_GIT_USER_EMAIL). |
max_file_size | int | 2 1024 1024 (2 MB) | Skip files larger than this from the shadow commit (they stay on disk). 0 disables the cap. |
Concurrency safety
Call save(), restore(), rewind(), and diff() from parallel coroutines on one CheckpointService โ no locking needed. The service serialises every git operation that touches HEAD, the index, or the worktree.
import asyncio
from praisonaiagents.checkpoints import CheckpointService
service = CheckpointService(workspace_dir="./my_project")
await service.initialize()
async def one(i):
with open(f"./my_project/f{i}.txt", "w") as f:
f.write(str(i))
return await service.save(f"cp{i}")
# Six parallel saves โ none fail on git lock contention
results = await asyncio.gather(*[one(i) for i in range(6)])
assert all(r.success or "No changes" in r.error for r in results)- A per-event-loop
asyncio.Lockis created lazily and rebound if the running loop changes. save(),restore(),rewind(), and working-directorydiff()(noto_id) take the lock.- Checkpoint-to-checkpoint
diff(from_id, to_id)stays lock-free โ it does not mutate state.
sequenceDiagram
participant A as Agent 1
participant B as Agent 2
participant L as Op Lock
participant S as Shadow git
A->>L: save()
L-->>A: acquired
B->>L: save()
Note right of B: waits
A->>S: add -A โ commit
A-->>L: release
L-->>B: acquired
B->>S: add -A โ commit
classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
classDef lock fill:#F59E0B,stroke:#7C90A0,color:#fff
classDef shadow fill:#6366F1,stroke:#7C90A0,color:#fff
class A,B user
class L lock
class S shadowLarge-repo performance
On a large repository the first checkpoint used to hash and store every tracked file from scratch, even though identical objects already lived in the project’s real .git. Since PR #5310, the shadow store reuses those objects โ so the first checkpoint is proportional to changed files, not total files.
Object seeding via alternates. When the workspace is inside a real git repo, CheckpointService writes <shadow>/objects/info/alternates pointing at the project’s object store (resolved via git rev-parse --git-common-dir / --git-dir). New blobs resolve against existing objects instead of being re-hashed. A file is used (not the GIT_ALTERNATE_OBJECT_DIRECTORIES env var) because the checkpoint service already sanitises that variable out of its environment. With no real git repo, the standalone git init path is unchanged.
Large-repo git tuning. feature.manyFiles=true, index.version=4, core.untrackedCache=true, and best-effort core.fsmonitor=true are applied once at init. Older git versions that don’t support a key silently skip that setting โ nothing fails the turn.
Oversized-file skip. Files above max_file_size bytes (default 2 MB; 0 disables) are dropped from the index during staging but stay on disk. Symlinks are measured by the link itself (os.lstat), so a small link pointing at a large file still snapshots.
Bounded gc. Every 20 saves the service runs git repack -a -d --quiet followed by git gc --auto --quiet on the shadow store. The repack (without --local) copies borrowed objects into a local pack, so /undo / /revert stay valid even if the project repo later prunes or repacks. The gc --auto prunes loose objects. Both are best-effort โ a gc failure never breaks a turn.
sequenceDiagram
participant Agent
participant Service as CheckpointService
participant Shadow as Shadow git
Agent->>Service: save("before turn N")
Service->>Shadow: git add -A
Service->>Service: skip files > max_file_size<br/>(git rm --cached, keep on disk)
Service->>Shadow: git commit
Service->>Shadow: prune old checkpoints
alt every 20 saves
Service->>Shadow: git repack -a -d
Service->>Shadow: git gc --auto
end
Service-->>Agent: CheckpointResult
classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
classDef service fill:#189AB4,stroke:#7C90A0,color:#fff
classDef shadow fill:#6366F1,stroke:#7C90A0,color:#fff
class Agent agent
class Service service
class Shadow shadowUsers often ask which files get snapshotted the moment they see the 2 MB default.
Step-Indexed Checkpoints
Tag checkpoints with an agent step index so you can rewind back to a specific step by number instead of remembering a hash.
from praisonaiagents.checkpoints import CheckpointService
service = CheckpointService(workspace_dir="./my_project")
await service.initialize()
# Save one checkpoint per step
for step in range(5):
await service.save(f"After step {step}", step=step)
# Later โ jump straight back to step 2
await service.restore(step=2)The step index is stored in the shadow-git commit message as [step-N]; no extra storage. Checkpoint.step is surfaced by list_checkpoints() for filtering.
Data Types
Checkpoint
@dataclass
class Checkpoint:
id: str # Full commit hash
short_id: str # Short hash (8 chars)
message: str # Checkpoint message
timestamp: datetime
step: Optional[int] # Populated when save(step=N) was used
files_changed: int
insertions: int
deletions: intCheckpointDiff
@dataclass
class CheckpointDiff:
from_checkpoint: str
to_checkpoint: Optional[str] # None = working directory
files: List[FileDiff]
total_additions: int
total_deletions: intBest Practices
Checkpoint before major refactors
Save with a descriptive message before large edits so restore targets are obvious in checkpoint list.
Diff before restore
Run praisonai checkpoint diff last to confirm you are rewinding to the right snapshot.
max_checkpoints only trims the in-memory view
max_checkpoints caps the in-memory view (get_checkpoint()) and how far restore(step=N) searches โ the shadow git repo retains every commit. To reclaim disk, call service.delete_all() โ this wipes every checkpoint for the workspace, so call initialize() again before saving.
A wrong restore() is itself undoable
Every restore() and rewind() auto-saves current work as [pre-restore] auto-save and pins it under refs/pre-restore/<id>. Jumped to the wrong checkpoint? restore() the auto-save id and your edits come back.
Share one CheckpointService across concurrent agents
The op lock lives on the service instance. Parallel save() / restore() / diff() on one instance are safe; two instances on the same workspace are not โ pass the same CheckpointService to every agent that touches the workspace.
Use –no-checkpoint for throwaway runs
Skip auto-checkpoints on YAML runs when you know the workspace is disposable.
Prefer /undo over /clear after a bad edit
With the session store wired in, /undo rewinds files and chat history to the same turn boundary, so the agent’s memory stays consistent with the workspace. /clear only truncates history and leaves reverted edits invisible to the agent.
Reach for rewind first, tag steps for precise jumps
rewind is the shortest path to undo the last change. When an agent loop wants to jump back to a specific point (e.g. “back to step 2”), tag checkpoints with save(step=N) and use restore(step=N).
Ignored files survive restore/rewind
Checkpoint restore mirrors .gitignore + built-in ignore rules (.git, __pycache__, node_modules, venv, .venv, .pytest_cache, .mypy_cache). Ignored files are never snapshotted and never pruned by restore/rewind โ a .env sitting outside version control survives every praisonai checkpoint rewind / restore.
Snapshot large fixtures with max_file_size
By default, files above 2 MB are skipped from checkpoints (they stay on disk). If your project deliberately versions large fixtures inside the workspace and you want them snapshotted, pass max_file_size=0 (disabled) or a larger byte cap to CheckpointService. On projects with model weights or build outputs, the default keeps the shadow store small. Skipped files are also never deleted by restore() โ an uncommitted large fixture survives a rewind.
Long-running sessions don't leak disk
Every 20 saves, the shadow store runs git repack -a -d (which pulls borrowed objects into a local pack) followed by git gc --auto. That means the shadow repo becomes fully self-contained shortly after seeding โ even if the project repo is later pruned or repacked, /undo and /revert history stays readable.
Low-level API Reference
CheckpointService Direct Usage
from praisonaiagents.checkpoints import CheckpointService
# Create checkpoint service
service = CheckpointService(
workspace_dir="/path/to/project",
storage_dir="~/.praisonai/checkpoints"
)
# Initialize
await service.initialize()
# Save a checkpoint
result = await service.save("Before refactoring")
print(f"Saved: {result.checkpoint.short_id}")
# Make changes...
# Restore if needed
await service.restore(result.checkpoint.id)
# View diff
diff = await service.diff()Methods
initialize()
Initialize the checkpoint service:
success = await service.initialize()save(message, allow_empty=False, step=None)
Save a checkpoint:
result = await service.save("Checkpoint message")
if result.success:
print(f"Saved: {result.checkpoint.short_id}")
else:
print(f"Error: {result.error}")| Parameter | Type | Default | Description |
|---|---|---|---|
message | str | โ | Checkpoint message |
allow_empty | bool | False | Allow saving even when no files changed |
step | Optional[int] | None | Tag this checkpoint with an agent step index (must be >= 0) |
restore(checkpoint_id=None, step=None)
Restore the workspace to a checkpoint by id or step index. Providing both is an error.
result = await service.restore("abc123")
result = await service.restore(step=2) # restore the checkpoint tagged step 2
if result.success:
print("Restored successfully")| Parameter | Type | Default | Description |
|---|---|---|---|
checkpoint_id | Optional[str] | None | Commit hash / short id |
step | Optional[int] | None | Restore the checkpoint tagged with this step index |
rewind(steps=1)
Rewind the workspace back steps checkpoints from the latest. This is a convenience over list_checkpoints() + restore() โ you don’t need the id.
# Undo the last checkpoint
result = await service.rewind(1)
# Step back three checkpoints
result = await service.rewind(3)
if result.success:
print(f"Rewound to: {result.checkpoint.short_id}")
else:
print(f"Error: {result.error}")| Parameter | Type | Default | Description |
|---|---|---|---|
steps | int | 1 | Number of checkpoints to step back. Must be >= 1. |
Returns: CheckpointResult with the checkpoint the workspace was restored to.
Failure modes:
| Error | Cause |
|---|---|
"Service not initialized" | initialize() was not awaited |
"steps must be >= 1" | steps=0 or negative |
"Cannot rewind N step(s): only M checkpoint(s) available" | Not enough checkpoints |
get_checkpoint_by_step(step)
Return the most recent checkpoint tagged with the given step index, or None.
cp = await service.get_checkpoint_by_step(2)| Parameter | Type | Default | Description |
|---|---|---|---|
step | int | โ | Step index to look up |
diff(from_id=None, to_id=None)
Get diff between checkpoints:
# Diff from last checkpoint to current
diff = await service.diff()
# Diff between specific checkpoints
diff = await service.diff("abc123", "def456")
for file in diff.files:
print(f"{file.status}: {file.path} (+{file.additions}/-{file.deletions})")list_checkpoints(limit=50)
List all checkpoints:
checkpoints = await service.list_checkpoints(limit=20)
for cp in checkpoints:
print(f"{cp.short_id} - {cp.message} ({cp.timestamp})")Event Handlers
Subscribe to checkpoint events:
from praisonaiagents.checkpoints import CheckpointEvent
def on_checkpoint(checkpoint):
print(f"Checkpoint created: {checkpoint.short_id}")
service.on(CheckpointEvent.CHECKPOINT_CREATED, on_checkpoint)
service.on(CheckpointEvent.CHECKPOINT_RESTORED, lambda cp: print(f"Restored: {cp.short_id}"))
service.on(CheckpointEvent.ERROR, lambda e: print(f"Error: {e['error']}"))SessionCheckpointManager diff API
SessionCheckpointManager adds session-scoped diffing on top of CheckpointService, used by /diff in both the REPL and the TUI.
diff = manager.diff(turn_only=False, path=None) # -> CheckpointDiff | None
body = SessionCheckpointManager.render_diff(diff, scope="session")| Method | Signature | Returns | Notes | |
|---|---|---|---|---|
diff | diff(turn_only: bool = False, path: Optional[str] = None) | `CheckpointDiff \ | None` | None when checkpointing is disabled or there is no baseline. turn_only=True diffs from the last checkpoint; default diffs from the first (session-start). path filters files by p == path or p.endswith("/" + path). |
render_diff (static) | render_diff(diff, scope: str = "session") -> str | Plain text | Renders a CheckpointDiff as a compact per-file summary โ used by the TUI so no rich console is required. |
Prefer /diff –turn before /undo
Peek at just the last turn’s changes before rolling them back; the full-session /diff can be noisy in long sessions.
Zero Performance Impact
The checkpoint system is designed for minimal overhead:
- Lazy loading: All imports via
__getattr__ - Async operations: Non-blocking git operations
- Object reuse via
alternates: on a real git repo, the first checkpoint reuses existing objects โ proportional to changed files, not total files. - Large-repo tuning:
feature.manyFiles,index.version 4,core.untrackedCache, and best-effortcore.fsmonitorare applied once at init so index writes and untracked scans stay fast. - Oversized-file skip: files above
max_file_size(default 2 MB) are not committed into the shadow store, keeping build outputs and model weights out. - Bounded gc:
git repack -a -d+git gc --autoruns every 20 saves so the shadow store doesn’t grow unbounded and no longer depends on borrowed project objects. - Configurable limits: Control max checkpoints to manage storage
Using checkpoints with resumable runs
When a checkpoint is bound to a Run-State Journal entry via set_checkpoint(run_id, checkpoint_id), a resume can restore the workspace to that checkpoint before replaying journalled steps โ the prerequisite for safe replay of file-touching tool loops.