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.

python
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.

Diagram

๐Ÿค– Agent run

๐Ÿ“ธ Auto checkpoint

๐Ÿ“ File edits

โช Restore / diff

Quick Start

  1. 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")
  2. 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()
  3. 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
  4. 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โ€ฆUseExample
Undo the very last set of changesrewind (default 1)praisonai checkpoint rewind
Step back N checkpoints from nowrewind Npraisonai 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 indexrestore --step Npraisonai checkpoint restore --step 3
Diagram

Undo last change

Step back N

Known checkpoint id

Tagged by step index

Need to rewind?

praisonai checkpoint rewind

praisonai checkpoint rewind N

praisonai checkpoint restore <id>

praisonai checkpoint restore –step N

Restore/rewind guarantees

SituationResult
File deleted before the checkpointStays deleted after restore
File added after the checkpointRemoved 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 calledAuto-saved as a [pre-restore] auto-save checkpoint; recoverable via restore(<pre_id>)
Uncommitted file above max_file_sizePreserved 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.

python
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 back
Diagram
sequenceDiagram
    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 git

How It Works

A user rewinds the workspace to a pre-turn state after a bad edit.

Diagram
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 cp

In-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.

bash
# 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 3

No 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.

Diagram

Interactive surface

Programmatic caller

not set

true / false

unset

on / off

Where does checkpointing
get its default from?

default = True
on by default

default = False
opt in required

checkpoints.auto in config?

keep the default

config wins over default

PRAISONAI_CHECKPOINTS env?

final decision

env wins over config

Diagram
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:

ModeWhat /undo does
Checkpointing off (default)Removes the last assistant + user message pair from history only. Workspace files are untouched.
Checkpointing on, session store not wiredRestores 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.

python
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

Diagram
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 store

One-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:

yaml
# agents.yaml
checkpoints:
  auto: true
  storage_dir: ./.praisonai/checkpoints   # optional

Env 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:

Diagram
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
bash
/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 start

The <file> filter matches a path when p == wanted or p.endswith("/" + wanted), so /diff src/foo.py matches <repo>/src/foo.py.

Diagram
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 engine

Requires 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

bash
# 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/project

Automatic Checkpoints with praisonai run

praisonai run snapshots your workspace automatically before every YAML-file run.

yaml
# 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.
bash
praisonai run agents.yaml            # auto-checkpoint, then run
praisonai run --restore last         # rewind workspace, exit
praisonai run agents.yaml --no-checkpoint   # skip auto-checkpoint

Configuration

python
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:

ParameterTypeDefaultDescription
workspace_dirstrโ€”Directory to track.
storage_dirstr~/.praisonai/checkpointsWhere the shadow-git store lives.
enabledboolTrueEnable / disable checkpoints.
auto_checkpointboolTrueAuto-checkpoint before file modifications.
max_checkpointsint100In-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_namestr"PraisonAI Checkpoints"Git user.name for shadow commits (env: PRAISONAI_GIT_USER_NAME).
user_emailstr"checkpoints@praison.ai"Git user.email for shadow commits (env: PRAISONAI_GIT_USER_EMAIL).
max_file_sizeint2 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.

python
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.Lock is created lazily and rebound if the running loop changes.
  • save(), restore(), rewind(), and working-directory diff() (no to_id) take the lock.
  • Checkpoint-to-checkpoint diff(from_id, to_id) stays lock-free โ€” it does not mutate state.
Diagram
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 shadow

Large-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.

Diagram

Yes

No

๐Ÿš€ Init CheckpointService

Inside a real
git repo?

๐Ÿ”— Write alternates
โ†’ project /objects

๐Ÿ“ฆ Standalone git init
unchanged path

โš™๏ธ Apply large-repo config
manyFiles / index.v4 / untrackedCache

โœ… Ready

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.

Diagram
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 shadow

Users often ask which files get snapshotted the moment they see the 2 MB default.

Diagram

Yes

No

Yes

No

Yes

No

๐Ÿ“ File staged by git add -A

Matches .gitignore
or built-in ignore?

โญ๏ธ Never snapshotted
never pruned by restore

Symlink?

โœ… Snapshotted
measured by lstat

Size > max_file_size?

โญ๏ธ Dropped from commit
stays on disk

โœ… Snapshotted

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.

python
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

python
@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: int

CheckpointDiff

python
@dataclass
class CheckpointDiff:
    from_checkpoint: str
    to_checkpoint: Optional[str]  # None = working directory
    files: List[FileDiff]
    total_additions: int
    total_deletions: int

Best 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

python
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:

python
success = await service.initialize()

save(message, allow_empty=False, step=None)

Save a checkpoint:

python
result = await service.save("Checkpoint message")

if result.success:
    print(f"Saved: {result.checkpoint.short_id}")
else:
    print(f"Error: {result.error}")
ParameterTypeDefaultDescription
messagestrโ€”Checkpoint message
allow_emptyboolFalseAllow saving even when no files changed
stepOptional[int]NoneTag 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.

python
result = await service.restore("abc123")
result = await service.restore(step=2)   # restore the checkpoint tagged step 2

if result.success:
    print("Restored successfully")
ParameterTypeDefaultDescription
checkpoint_idOptional[str]NoneCommit hash / short id
stepOptional[int]NoneRestore 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.

python
# 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}")
ParameterTypeDefaultDescription
stepsint1Number of checkpoints to step back. Must be >= 1.

Returns: CheckpointResult with the checkpoint the workspace was restored to.

Failure modes:

ErrorCause
"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.

python
cp = await service.get_checkpoint_by_step(2)
ParameterTypeDefaultDescription
stepintโ€”Step index to look up

diff(from_id=None, to_id=None)

Get diff between checkpoints:

python
# 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:

python
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:

python
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.

python
diff = manager.diff(turn_only=False, path=None)          # -> CheckpointDiff | None
body = SessionCheckpointManager.render_diff(diff, scope="session")
MethodSignatureReturnsNotes
diffdiff(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") -> strPlain textRenders 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-effort core.fsmonitor are 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 --auto runs 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.


Related