Skip to content

Repository files navigation

agent-rollback

Golang native — the undo button for AI agent workspaces.

agent-rollback captures content-addressed snapshots of your workspace before, during, and after any AI agent run, so you can restore any file — or the entire project — in one command. It works with or without a Git repo, is 100% local, and purpose-built for AI coding agent safety.

A Go CLI (agent-rollback) and a Go library (github.com/superops-team/agent-rollback/rollback). MIT-licensed, 100% local — no telemetry, no cloud sync.

Features

  • Content-addressed storage — identical files share one blob via SHA-256, ~zero disk overhead
  • Git-aware scanning — uses git ls-files in Git repos, falls back to filesystem walk
  • State deduplication — identical workspace states are not stored twice
  • Atomic writes — temp file + rename for crash safety
  • Unified diff — LCS-based line-level diff with context hunks; oversized, high-line-count, and binary files are summarized instead of expanded
  • Selective revert — revert a single operation's files without losing later unrelated edits
  • Restore preflight — validates checkpoint objects, paths, hashes, and symlink targets before destructive workspace changes
  • Operation log — append-only ops.jsonl for full audit trail
  • Pin & Prune — pin known-good checkpoints, auto-prune old ones with GC
  • Path safety — checkpoint ID validation prevents path traversal

Install

As a CLI tool

go install github.com/superops-team/agent-rollback/cmd/agent-rollback@latest

Or build from source:

git clone https://github.com/superops-team/agent-rollback.git
cd agent-rollback
go build -o agent-rollback ./cmd/agent-rollback/

As a Go library

go get github.com/superops-team/agent-rollback/rollback

CLI Quick Start

# 1. Initialize the store in your project
agent-rollback init

# 2. Create a checkpoint before risky work
agent-rollback checkpoint "before refactor"

# 3. Let your AI agent do its thing...

# 4. Made a mess? Roll back
agent-rollback list
agent-rollback revert cp-123456-before-refactor-abcd --yes

# 5. See what changed
agent-rollback diff cp-before cp-after --patch

CLI Commands

Command Description
init Initialize the rollback store
checkpoint [label] Create a checkpoint
list, ls List checkpoints (newest first)
show <id> Show checkpoint details
diff <from> [to] Diff between checkpoints
revert <id> Restore workspace to checkpoint
undo [count] Undo last N checkpoints
pin <id> [name] Pin a checkpoint
unpin <id> Unpin a checkpoint
prune [opts] Delete old checkpoints
log Show operation history
search <query> Search checkpoints
op revert <op-id> Selectively revert an operation
gc Run garbage collection
info Show store statistics

Global Options

  • --cwd <dir> — working directory
  • --store <dir> — store directory (default: .agent-rollback/)
  • --json — output as JSON
  • --yes — skip confirmations

Diff Patch Guardrails

diff --patch keeps patch generation bounded. Per file, unified patch output is generated only when both sides are text, at most 1 MiB, at most 20,000 lines, and within the internal LCS complexity guard. Files above those limits, or files detected as binary, are reported with a Patch skipped ... summary while other eligible files continue to emit normal unified diffs.

Library Usage

Installation

go get github.com/superops-team/agent-rollback/rollback

Basic Usage

package main

import (
    "fmt"
    "log"

    "github.com/superops-team/agent-rollback/rollback"
)

func main() {
    // Create a store in the current workspace
    store := rollback.NewDefaultStore("/path/to/workspace")
    if err := store.Initialize(); err != nil {
        log.Fatal(err)
    }

    // Create a checkpoint before making changes
    manifest, err := store.CreateCheckpoint(rollback.CheckpointOptions{
        Label:  "before refactor",
        Source: "manual",
        Dedupe: true, // skip if state is identical to latest
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Checkpoint created:", manifest.ID)

    // ... make changes to your workspace ...

    // Restore to the checkpoint
    restored, err := store.RestoreCheckpoint(manifest.ID)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Restored to:", restored.ID)
}

Creating Checkpoints

// With a descriptive label
manifest, err := store.CreateCheckpoint(rollback.CheckpointOptions{
    Label:  "green tests",
    Source: "manual",
    Dedupe: true,
})

// Without a label (auto-generated)
manifest, err := store.CreateCheckpoint(rollback.CheckpointOptions{
    Source: "manual",
    Dedupe: true,
})

// From an AI agent hook
manifest, err := store.CreateCheckpoint(rollback.CheckpointOptions{
    Source:   "hook",
    Phase:    "before",
    ToolName: "file_edit",
    Dedupe:   true,
})

Listing and Showing Checkpoints

// List all checkpoints (newest first)
checkpoints, err := store.ListCheckpoints()
for _, cp := range checkpoints {
    fmt.Printf("%s  %s  %s\n", cp.ID, cp.CreatedAt, cp.Metadata.Label)
}

// Show a specific checkpoint
manifest, err := store.ShowCheckpoint("cp-123456-before-refactor-abcd")
fmt.Printf("Files: %d\n", len(manifest.Files))
for path, entry := range manifest.Files {
    fmt.Printf("  %s  %s  %d bytes\n", path, entry.SHA256[:8], entry.Size)
}

Diffing Checkpoints

// Diff between two checkpoints
diffs, err := store.DiffCheckpoints("cp-before", "cp-after")
for _, d := range diffs {
    fmt.Printf("%s: %s\n", d.Path, d.Change) // added, deleted, modified
}

// Diff a checkpoint against current workspace state
diffs, err := store.DiffCheckpoints("cp-before", "")

// Get a formatted summary
summary := rollback.FormatDiffSummary(diffs)
fmt.Println(summary)

Store.FullPatch(from, to) applies the same patch guardrails used by the CLI: text files within 1 MiB, 20,000 lines, and the internal LCS complexity guard get context hunks; oversized, high-line-count, high-complexity, or binary files get skipped-patch summaries.

Restoring Checkpoints

// Restore workspace to a checkpoint (restores all files)
manifest, err := store.RestoreCheckpoint("cp-123456-before-refactor-abcd")

// Undo the last N checkpoints
manifest, err := store.Undo(1)  // undo last 1 checkpoint
manifest, err := store.Undo(3)  // undo last 3 checkpoints

Restore validates the checkpoint before deleting, overwriting, or creating workspace files. Preflight failures include categories such as missing-object, hash-mismatch, unsafe-path, and invalid-symlink; failed preflight does not append a checkpoint.restored operation.

Pinning and Pruning

// Pin a checkpoint so it survives pruning
err := store.PinCheckpoint("cp-good", "known good state")

// Unpin
err := store.UnpinCheckpoint("cp-good")

// Prune old checkpoints (keep last 20, protect pinned)
toDelete, err := store.PruneCheckpoints(rollback.PruneOptions{
    KeepLast:   20,
    KeepPinned: true,
})
for _, id := range toDelete {
    store.DeleteCheckpoint(id)
}

// Run garbage collection to remove unreferenced objects
deleted, bytes, err := store.CollectGarbage()
fmt.Printf("GC: removed %d objects (%d bytes)\n", deleted, bytes)

Searching Checkpoints

// Search by label, message, or ID
results, err := store.SearchCheckpoints("auth refactor")
for _, cp := range results {
    fmt.Println(cp.ID, cp.Metadata.Label)
}

Operation Log

// List all operations
ops, err := store.ListOps()
for _, op := range ops {
    fmt.Printf("%s  %s  %s\n", op.ID, op.Type, op.Details.CheckpointID)
}

// Get a specific operation
op, err := store.GetOp("op-20260613000000-abcdef")

Selective Revert

// Revert only the files changed by a specific operation
result, err := store.SelectiveRevert(rollback.SelectiveRevertOptions{
    OpID:         "op-20260613000000-abcdef",
    Force:        false, // fail if conflicts detected
    CreateSafety: true,  // create a safety checkpoint first
})

if result.SafetyCP != nil {
    fmt.Println("Safety checkpoint:", result.SafetyCP.ID)
}
fmt.Printf("Files touched: %d, Conflicts: %d\n",
    len(result.FilesToTouch), len(result.Conflicts))

Selective revert is supported for checkpoint.created and checkpoint.restored operations. It computes the touched files from the operation's real chronological predecessor, then refuses to apply by default if any touched file has changed or gone missing since that operation. Use Force only when overwriting those later edits is intentional.

Production Guidance

  • High-frequency automatic checkpoints are best suited to small and medium workspaces. On an Apple M3 Pro, the current benchmark suite measured small checkpoint creation at about 8.6 ms/op, medium at about 24.9 ms/op, and a 1000-file / ~100 MB workspace at about 599 ms/op.
  • Treat every-operation automatic checkpointing on 1000-file / ~100 MB workspaces as unsuitable by default unless you record target-platform benchmark evidence for that workflow.
  • Restore preflight reads checkpoint objects before writing the workspace. That adds I/O but prevents known-corrupt checkpoints from deleting or overwriting user files.
  • diff --patch is safe for normal text files, but it intentionally summarizes files above 1 MiB, above 20,000 lines, or above the internal LCS complexity guard to avoid unbounded memory growth.

Working with File Content

// Read a file from the store (by SHA-256 hash)
data, err := store.ReadObject(manifest.Files["main.go"].SHA256)

// Read a symlink target
target, err := store.ReadSymlinkObject(manifest.Files["link"].SHA256)

Store Statistics

// Get checkpoint count
count, _ := store.CheckpointCount()

// Get total store size on disk
size, _ := store.StoreSize()

// Format for display
fmt.Printf("Checkpoints: %d, Size: %s\n", count, formatBytes(size))

Storage Model

.agent-rollback/
├── checkpoints/<id>/manifest.json   # checkpoint manifests
├── objects/<sha-prefix>/<sha>       # content-addressed file blobs
└── ops.jsonl                        # append-only operation log

API Reference

Core Types

Type Description
Store Storage engine — manages checkpoints, objects, and operations
Manifest Checkpoint manifest — ID, timestamp, files, metadata
FileEntry File or symlink entry — type, SHA-256, size, mode, target
Metadata Checkpoint metadata — label, source, pinned, phase, etc.
DiffEntry File difference — path, change type, before/after entries
Operation Operation log entry — ID, timestamp, type, details
CheckpointOptions Options for CreateCheckpoint
PruneOptions Options for PruneCheckpoints
SelectiveRevertOptions Options for SelectiveRevert
SelectiveRevertResult Result of a selective revert

Store Methods

Method Description
NewDefaultStore(workspaceRoot) Create store with default .agent-rollback/
NewStore(workspaceRoot, storeRoot) Create store with custom store path
Initialize() Create store directory structure
CreateCheckpoint(opts) Snapshot current workspace state
RestoreCheckpoint(id) Restore workspace to checkpoint state
ListCheckpoints() List all checkpoints (newest first)
ShowCheckpoint(id) Get a specific checkpoint manifest
DiffCheckpoints(from, to) Compute differences between checkpoints
Undo(count) Revert last N checkpoints
PinCheckpoint(id, name) Pin a checkpoint (protect from prune)
UnpinCheckpoint(id) Unpin a checkpoint
DeleteCheckpoint(id) Delete a checkpoint manifest
PruneCheckpoints(opts) Plan which checkpoints to delete
CollectGarbage() Remove unreferenced objects, return (deleted, bytes, error)
SearchCheckpoints(query) Search checkpoints by label/message/ID
SelectiveRevert(opts) Revert files changed by a specific operation
AppendOp(op) Append an operation to the log
ListOps() List all operations (newest first)
GetOp(id) Get a specific operation
CheckpointCount() Get number of checkpoints
StoreSize() Get total store size in bytes

Utility Functions

Function Description
ComputeStateHash(files) Compute dedup hash from file map
CreateCheckpointID(label) Generate human-readable checkpoint ID
RequireSafeCheckpointID(id) Validate checkpoint ID (prevents path traversal)
ResolveWorkspacePath(root, rel) Resolve relative path within workspace
ListWorkspaceFiles(root) Enumerate workspace files (git-aware)
CurrentWorkspaceFiles(root) Get current workspace state as file map
DiffManifests(before, after) Compare two manifests, return diff entries
FormatDiffSummary(diffs) Format diff entries as human-readable summary
FormatCheckpointsList(cps, now) Format checkpoint list for display
FormatOpsLog(ops, now) Format operation log for display
FormatSelectiveRevertResult(r, now) Format selective revert result
UnifiedDiff(oldPath, newPath, oldData, newData) Generate unified diff string
ParseDuration(s) Parse duration string like "7d", "12h", "30m"

Testing

go test ./rollback/ -v
go test -race ./...
go test ./benchmark -bench=. -benchmem -run '^$'

License

MIT

About

Golang native agent rollback - the undo button for AI agent workspaces

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages