> ## Documentation Index
> Fetch the complete documentation index at: https://nono.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SnapshotManager

> Filesystem snapshots and rollback

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

```python theme={null}
SnapshotManager(
    session_dir: str,
    tracked_paths: list[str],
    exclusion: ExclusionConfig | None = None,
    max_entries: int = 300_000,
    max_bytes: int = 2_147_483_648,
)
```

| Parameter       | Type                      | Default   | Description                                     |
| --------------- | ------------------------- | --------- | ----------------------------------------------- |
| `session_dir`   | `str`                     | required  | Directory for session data (snapshots, objects) |
| `tracked_paths` | `list[str]`               | required  | Directories to track for changes                |
| `exclusion`     | `ExclusionConfig \| None` | `None`    | File exclusion configuration                    |
| `max_entries`   | `int`                     | `300,000` | Maximum files to track                          |
| `max_bytes`     | `int`                     | 2 GiB     | Maximum 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

```python theme={null}
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.

```python theme={null}
# 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

```python theme={null}
ExclusionConfig(
    use_gitignore: bool = True,
    exclude_patterns: list[str] = [],
    exclude_globs: list[str] = [],
    force_include: list[str] = [],
)
```

| Parameter          | Type        | Default | Description                                 |
| ------------------ | ----------- | ------- | ------------------------------------------- |
| `use_gitignore`    | `bool`      | `True`  | Respect `.gitignore` files in tracked roots |
| `exclude_patterns` | `list[str]` | `[]`    | Component-level or path-substring patterns  |
| `exclude_globs`    | `list[str]` | `[]`    | Glob patterns matched against filenames     |
| `force_include`    | `list[str]` | `[]`    | Patterns that override all exclusions       |

***

## SnapshotManifest

A snapshot recording the state of all tracked files. Returned by `create_baseline()` and `create_incremental()`.

| Property      | Type                   | Description                                  |
| ------------- | ---------------------- | -------------------------------------------- |
| `number`      | `int`                  | Snapshot sequence number (0 = baseline)      |
| `timestamp`   | `str`                  | ISO 8601 creation timestamp                  |
| `parent`      | `int \| None`          | Parent snapshot number                       |
| `merkle_root` | `ContentHash`          | Cryptographic commitment to the entire state |
| `files`       | `dict[str, FileState]` | Mapping of file paths to their state         |

***

## ContentHash

SHA-256 content hash. Immutable and hashable.

| Method  | Returns | Description             |
| ------- | ------- | ----------------------- |
| `hex()` | `str`   | 64-character hex string |

***

## FileState

State of a single file within a snapshot.

| Property      | Type          | Description                             |
| ------------- | ------------- | --------------------------------------- |
| `hash`        | `ContentHash` | SHA-256 of file content                 |
| `size`        | `int`         | File size in bytes                      |
| `mtime`       | `int`         | Modification time (seconds since epoch) |
| `permissions` | `int`         | Unix permission bits                    |

***

## Change

A filesystem change detected between snapshots.

| Property      | Type          | Description                                                        |
| ------------- | ------------- | ------------------------------------------------------------------ |
| `path`        | `str`         | Path of the changed file                                           |
| `change_type` | `str`         | `"created"`, `"modified"`, `"deleted"`, or `"permissions_changed"` |
| `size_delta`  | `int \| None` | Size difference in bytes                                           |

***

## SessionMetadata

Metadata for a sandboxed session. Ties together the snapshot chain, network audit trail, and execution context.

```python theme={null}
SessionMetadata(
    session_id: str,
    command: list[str],
    tracked_paths: list[str],
)
```

**Constructor arguments** (read-only after creation):

| Property        | Type        | Description                             |
| --------------- | ----------- | --------------------------------------- |
| `session_id`    | `str`       | Session identifier                      |
| `started`       | `str`       | ISO 8601 start time (set automatically) |
| `command`       | `list[str]` | Command that was executed               |
| `tracked_paths` | `list[str]` | Tracked directories                     |

**Mutable properties** (set after the session runs):

| Property         | Type                | Description                                                                   |
| ---------------- | ------------------- | ----------------------------------------------------------------------------- |
| `ended`          | `str \| None`       | ISO 8601 end time                                                             |
| `snapshot_count` | `int`               | Number of snapshots                                                           |
| `exit_code`      | `int \| None`       | Child process exit code                                                       |
| `merkle_roots`   | `list[ContentHash]` | Chain of Merkle roots from each snapshot (read-only, use `add_merkle_root()`) |
| `network_events` | `list[dict]`        | Network audit events (read-only, use `set_network_events()`)                  |

| Method                               | Description                             |
| ------------------------------------ | --------------------------------------- |
| `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() -> str`                   | Serialize to JSON                       |
| `from_json(json) -> SessionMetadata` | Deserialize 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

## Related

* [ProxyConfig](/python/api/proxy-config) - Network proxy configuration
* [Module Functions](/python/api/functions) - `start_proxy()`, `sandboxed_exec()`
