Skip to main content
The snapshot system provides content-addressable filesystem state capture with Merkle-committed integrity. Take a baseline before an agent runs, detect changes incrementally, and roll back to any previous state.

SnapshotManager

SnapshotManager(
    session_dir: str,
    tracked_paths: list[str],
    exclusion: ExclusionConfig | None = None,
    max_entries: int = 300_000,
    max_bytes: int = 2_147_483_648,
)
ParameterTypeDefaultDescription
session_dirstrrequiredDirectory for session data (snapshots, objects)
tracked_pathslist[str]requiredDirectories to track for changes
exclusionExclusionConfig | NoneNoneFile exclusion configuration
max_entriesint300,000Maximum files to track
max_bytesint2 GiBMaximum total bytes to track
Each tracked path gets its own exclusion filter, so .gitignore rules are interpreted relative to their own root directory.

Methods

create_baseline() -> SnapshotManifest

Capture the initial filesystem state. Must be called before create_incremental().

create_incremental() -> tuple[SnapshotManifest, list[Change]]

Capture current state and return changes since the last snapshot. Uses mtime/size as a fast check, then hashes changed files.

restore_to(snapshot_number: int) -> list[Change]

Restore the filesystem to a previous snapshot. Retrieves file content from the object store, deletes files created after that snapshot. Returns the list of changes applied.

compute_restore_diff(snapshot_number: int) -> list[Change]

Dry-run: compute what restore_to() would change without modifying the filesystem.

load_manifest(number: int) -> SnapshotManifest

Load a snapshot manifest from disk by number.

save_session_metadata(meta: SessionMetadata) -> None

Save session metadata to the session directory.

snapshot_count() -> int

Number of snapshots taken (including resumed sessions).

load_session_metadata(session_dir: str) -> SessionMetadata (static)

Load session metadata from a session directory without creating a full manager.

Example

from nono_py import SnapshotManager, ExclusionConfig

mgr = SnapshotManager(
    session_dir="~/.nono/rollbacks/session-001",
    tracked_paths=["/workspace"],
    exclusion=ExclusionConfig(
        exclude_patterns=["node_modules", "__pycache__"],
        exclude_globs=["*.pyc"],
    ),
)

# Capture baseline
baseline = mgr.create_baseline()
print(f"Merkle root: {baseline.merkle_root.hex()}")

# ... agent modifies files ...

# Capture changes
manifest, changes = mgr.create_incremental()
for change in changes:
    print(f"{change.change_type}: {change.path}")

# Roll back
mgr.restore_to(snapshot_number=0)

Resuming Sessions

A new SnapshotManager pointed at an existing session_dir automatically loads the latest manifest from disk. This means create_incremental() works without calling create_baseline() again, and snapshot_count() returns the correct value.
# Later, or in a new process:
mgr = SnapshotManager(session_dir="~/.nono/rollbacks/session-001", tracked_paths=["/workspace"])
print(mgr.snapshot_count())  # Reflects snapshots already on disk
manifest, changes = mgr.create_incremental()

ExclusionConfig

ExclusionConfig(
    use_gitignore: bool = True,
    exclude_patterns: list[str] = [],
    exclude_globs: list[str] = [],
    force_include: list[str] = [],
)
ParameterTypeDefaultDescription
use_gitignoreboolTrueRespect .gitignore files in tracked roots
exclude_patternslist[str][]Component-level or path-substring patterns
exclude_globslist[str][]Glob patterns matched against filenames
force_includelist[str][]Patterns that override all exclusions

SnapshotManifest

A snapshot recording the state of all tracked files. Returned by create_baseline() and create_incremental().
PropertyTypeDescription
numberintSnapshot sequence number (0 = baseline)
timestampstrISO 8601 creation timestamp
parentint | NoneParent snapshot number
merkle_rootContentHashCryptographic commitment to the entire state
filesdict[str, FileState]Mapping of file paths to their state

ContentHash

SHA-256 content hash. Immutable and hashable.
MethodReturnsDescription
hex()str64-character hex string

FileState

State of a single file within a snapshot.
PropertyTypeDescription
hashContentHashSHA-256 of file content
sizeintFile size in bytes
mtimeintModification time (seconds since epoch)
permissionsintUnix permission bits

Change

A filesystem change detected between snapshots.
PropertyTypeDescription
pathstrPath of the changed file
change_typestr"created", "modified", "deleted", or "permissions_changed"
size_deltaint | NoneSize difference in bytes

SessionMetadata

Metadata for a sandboxed session. Ties together the snapshot chain, network audit trail, and execution context.
SessionMetadata(
    session_id: str,
    command: list[str],
    tracked_paths: list[str],
)
Constructor arguments (read-only after creation):
PropertyTypeDescription
session_idstrSession identifier
startedstrISO 8601 start time (set automatically)
commandlist[str]Command that was executed
tracked_pathslist[str]Tracked directories
Mutable properties (set after the session runs):
PropertyTypeDescription
endedstr | NoneISO 8601 end time
snapshot_countintNumber of snapshots
exit_codeint | NoneChild process exit code
merkle_rootslist[ContentHash]Chain of Merkle roots from each snapshot (read-only, use add_merkle_root())
network_eventslist[dict]Network audit events (read-only, use set_network_events())
MethodDescription
add_merkle_root(root)Append a Merkle root to the chain
set_network_events(events)Set network events from a list of dicts
to_json() -> strSerialize to JSON
from_json(json) -> SessionMetadataDeserialize from JSON (static)

Security Properties

  • Merkle commitment: Single root hash commits to the entire filesystem state
  • Content-addressable dedup: Identical file content stored once
  • APFS clonefile: Copy-on-write on macOS for efficient snapshots
  • Atomic writes: Temp file + rename for all persistent state
  • TOCTOU protection: File content verified after storage