# How Herdr Session Persistence Restores Panes: Complete Restoration Pipeline Explained

> Discover how Herdr session persistence restores panes by deserializing JSON, remapping IDs, and reconstructing TerminalRuntimes. Learn the complete restoration pipeline.

- Repository: [Can Celik/herdr](https://github.com/ogulcancelik/herdr)
- Tags: deep-dive
- Published: 2026-05-31

---

**Herdr restores panes by deserializing a JSON snapshot from disk, remapping legacy pane IDs to new globally unique identifiers, and reconstructing TerminalRuntimes that optionally replay scrollback history or resume agent sessions.**

When resuming work in the `ogulcancelik/herdr` terminal workspace manager, the application reconstructs your complete pane topology from persistent storage. The **herdr session persistence restore panes** mechanism operates through a coordinated pipeline across [`src/persist/io.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/io.rs), [`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs), and [`src/app/mod.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/app/mod.rs), ensuring that working directories, agent states, and scrollback buffers survive system restarts.

## The Three-Stage Restoration Architecture

The restoration process follows a strict sequence to recover the UI state. First, `crate::persist::load()` reads `~/.config/herdr/session.json` and deserializes it into a `SessionSnapshot`. Second, if experimental pane history is enabled, `crate::persist::load_history()` ingests the companion [`session-history.json`](https://github.com/ogulcancelik/herdr/blob/main/session-history.json) file. Third, `crate::persist::restore()` in **src/persist/restore.rs** orchestrates the reconstruction, accepting the snapshots along with terminal dimensions, shell configuration, and runtime options to regenerate the workspace objects.

## Loading and Validating Persisted State

The persistence layer begins its work in **src/persist/io.rs**, where the on-disk JSON is parsed into structured snapshot types. The `SessionSnapshot` captures the complete workspace hierarchy including tabs, pane layouts, and metadata. When history persistence is active, a separate `SessionHistorySnapshot` contains the ANSI scrollback buffers for each pane. These structures serve as the immutable blueprint for the restoration process, allowing the system to reconstruct the terminal state without relying on live process restoration.

## Remapping Pane IDs and Resolving Context

Inside `restore()`, the system first calls `restore_node_remapped()` at **src/persist/restore.rs#L1000-L1007** to walk the saved `LayoutSnapshot` and allocate brand-new `PaneId` values. This remapping returns a `HashMap<u32, PaneId>` that guarantees every restored pane receives a globally unique identifier, preventing collisions with any existing runtime state.

For each pane, the restoration logic resolves the working directory at **src/persist/restore.rs#L3838-L3898**. If the saved `cwd` path no longer exists, Herdr falls back to `$HOME` or `/`, logging a warning via the `warn!` macro. Simultaneously, the system reapplies saved manual labels, launch arguments, and agent names to the newly instantiated `TerminalState` structures (**src/persist/restore.rs#L4778-L4804**).

## Agent Session and Terminal Runtime Restoration

The `pane_restore_startup()` function at **src/persist/restore.rs#L7600-L7638** handles **AgentResumePlan** creation. When the user has enabled `resume_agents_on_restore` and the persisted `PaneAgentSessionSnapshot` remains resumable, Herdr prepares to respawn the agent process rather than launching a standard shell. Duplicate native agent sessions are suppressed through an internal deduplication set.

Terminal spawning occurs at **src/persist/restore.rs#L7020-L7070** through three distinct paths:

- **Imported hand-off runtimes** are re-attached directly if present in the snapshot.
- **Agent resume plans** trigger `TerminalRuntime::spawn_agent_restore()`, which starts the agent process and marks the terminal to respawn a shell upon agent exit.
- **Standard restoration** invokes `TerminalRuntime::spawn_with_initial_history()`, launching a fresh shell and seeding it with `initial_history_ansi` when scrollback persistence is available.

## Workspace Reconstruction and Layout Pruning

After pane creation, the restoration pipeline prunes orphaned layout nodes via `prune_restored_node` at **src/persist/restore.rs#L5450-L5490**, removing any containers that lost their constituent panes during the remapping phase. The focus state and root-pane references are then resolved against the new ID map, with automatic fallback to the first surviving pane when the original focus target no longer exists.

Finally, the restored tabs, terminals, and runtimes are collected into `Workspace` structs at **src/persist/restore.rs#L8910-L8945**, preserving workspace IDs, custom names, worktree membership, and the active tab state. This collection replaces the empty initial application state when triggered from the entry point in **src/app/mod.rs**.

## Implementation Example

The following pattern demonstrates how to invoke the restoration pipeline programmatically:

```rust
use herdr::persist::{load, load_history, restore};
use herdr::config::ShellModeConfig;
use tokio::sync::mpsc;
use std::sync::{Arc, atomic::AtomicBool};
use tokio::sync::Notify;

// Load persisted snapshots
let snapshot = load().expect("session file not found");
let history = load_history();

// Async runtime plumbing
let (event_tx, _event_rx) = mpsc::channel(4);
let render_notify = Arc::new(Notify::new());
let render_dirty = Arc::new(AtomicBool::new(false));

// Execute restoration
let (workspaces, terminals, runtimes) = restore(
    &snapshot,
    history.as_ref(),
    24,                              // terminal rows
    80,                              // terminal cols
    4 * 1024 * 1024,                 // 4 MiB scrollback limit
    "/bin/bash",
    ShellModeConfig::NonLogin,
    true,                            // resume agents on restore
    event_tx,
    render_notify,
    render_dirty,
);

```

Within the restoration flow, scrollback seeding occurs when no native agent resume is required:

```rust
let startup = pane_restore_startup(saved_agent_session, saved_history, &mut agent_restore);
// startup.initial_history_ansi contains captured ANSI scrollback for replay

```

## Summary

- **Herdr session persistence restore panes** relies on `SessionSnapshot` deserialization from `~/.config/herdr/session.json` to reconstruct the workspace topology.
- `restore_node_remapped()` in **src/persist/restore.rs** generates new globally unique `PaneId` values to avoid runtime collisions.
- Working directory resolution includes automatic fallback to `$HOME` or `/` when the original path is unreachable.
- The **AgentResumePlan** system optionally respawns persistent agents instead of launching new shells.
- `TerminalRuntime::spawn_with_initial_history()` replays saved ANSI scrollback into restored panes when history persistence is enabled.
- The entry point in **src/app/mod.rs** coordinates the entire pipeline, replacing the initial empty state with fully reconstructed workspaces.

## Frequently Asked Questions

### Where does Herdr store session persistence data?

Herdr persists session state to `~/.config/herdr/session.json` and optional pane history to [`session-history.json`](https://github.com/ogulcancelik/herdr/blob/main/session-history.json). The **src/persist/io.rs** module handles all disk I/O for these files, including symlink resolution and version compatibility checks.

### How does Herdr handle missing working directories when restoring panes?

When the saved working directory no longer exists, Herdr falls back to `$HOME` or the root directory `/` at **src/persist/restore.rs#L3838-L3898**. The fallback event is logged as a warning so users can identify broken workspace configurations.

### Can Herdr restore running agent sessions?

Yes, when `resume_agents_on_restore` is enabled in the configuration, Herdr inspects the `PaneAgentSessionSnapshot` and creates an **AgentResumePlan** at **src/persist/restore.rs#L7600-L7638**. This plan respawns the agent process via `TerminalRuntime::spawn_agent_restore()` and configures the terminal to launch a shell only after the agent exits.

### What happens to pane IDs during restoration?

Herdr never reuses persisted pane IDs directly. Instead, `restore_node_remapped()` in **src/persist/restore.rs#L1000-L1007** allocates fresh `PaneId` values and maintains a mapping hash table. This remapping ensures global uniqueness across the new session while preserving the layout hierarchy from the snapshot.