# How Resume Agents on Restore Works in herdr: Native Agent Session Recovery

> Discover how herdr's resume agents on restore flag enables native agent session recovery. Learn about config propagation, command-line plans, and session deduplication.

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

---

**The `resume_agents_on_restore` flag in herdr triggers native agent session recovery by propagating a configuration boolean through the restoration pipeline, generating agent-specific command-line plans, and deduplicating sessions to ensure only one process per conversation is spawned.**

The herdr terminal multiplexer supports persistent sessions for native AI agents like `pi`, `claude`, and `codex`. When restoring a saved workspace, the `resume_agents_on_restore` functionality determines whether to relaunch these agents with their previous conversation state or fall back to standard shell restoration.

## Entry Point: The `restore` Function and Configuration Flag

The restoration flow originates in [`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs), where the top-level `restore` function accepts the critical boolean parameter:

```rust
pub fn restore(
    snapshot: &SessionSnapshot,
    ...,
    resume_agents_on_restore: bool,
    ...,
) -> RestoredSession { ... }

```

This flag is encapsulated within a `RestoreRuntimeContext` and propagated downward through workspaces and tabs to every individual pane. When enabled, it signals the runtime to inspect saved pane metadata for native agent sessions rather than defaulting to standard shell initialization.

## Pane-Level Detection and Session Deduplication

For each pane being reconstructed, the `pane_restore_startup` function evaluates whether an agent resume is appropriate:

```rust
fn pane_restore_startup<'a>(
    session: Option<&PaneAgentSessionSnapshot>,
    history: Option<&'a PaneHistorySnapshot>,
    agent_restore: &mut AgentRestoreState<'_>,
) -> PaneRestoreStartup<'a> {
    let restore_plan = session
        .and_then(|s| restore_plan_for_snapshot(s, agent_restore.enabled));

    let duplicate_agent_session = restore_plan.as_ref().is_some_and(|plan| {
        if agent_restore.resumed_sessions.insert(plan.dedupe_key.clone()) {
            false
        } else {
            true
        }
    });

    let restore_plan = if duplicate_agent_session { None } else { restore_plan };
    ...
}

```

The deduplication mechanism uses a `HashSet<String>` of **dedupe keys** formatted as `source\0agent\0kind\0value`. This ensures that if multiple panes reference the same agent session—identified by its source, agent type, and session reference—only the first pane spawns the process while subsequent references fall back to normal shell restoration.

## Generating Agent-Specific Resume Plans

When a valid session snapshot exists, [`src/agent_resume.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/agent_resume.rs) validates the agent and constructs the execution plan:

```rust
pub fn plan(source: &str, agent: &str, session_ref: &AgentSessionRef) -> Option<AgentResumePlan> {
    if !is_official_agent_source(source, agent) { return None; }

    let argv = match (source, agent, session_ref.kind) {
        ("herdr:pi", "pi", _) => vec!["pi".into(), "--session".into(), session_ref.value.clone()],
        ("herdr:claude", "claude", AgentSessionRefKind::Id) => {
            vec!["claude".into(), "--resume".into(), session_ref.value.clone()]
        },
        // ... hermes, opencode, codex ...
        _ => return None,
    };

    Some(AgentResumePlan {
        agent: agent.to_string(),
        argv,
        dedupe_key: dedupe_key(source, agent, session_ref),
    })
}

```

Only **official agents**—`pi`, `claude`, `codex`, `hermes`, and `opencode`—are supported. Custom agents or invalid session references return `None`, causing the pane to restore as a standard terminal.

## Spawning Agents and Terminal Lifecycle

If `restore_plan` exists, the system invokes `TerminalRuntime::spawn_agent_restore` (defined in [`src/terminal/runtime.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/terminal/runtime.rs)), which delegates to `PaneRuntime::spawn_agent_restore` in [`src/pane/mod.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/pane/mod.rs). The spawned process receives the pre-constructed `argv` vector and a PTY connected to the session file. 

Critically, the terminal is marked with `with_respawn_shell_on_exit()`, ensuring that when the native agent process terminates, the pane remains alive and falls back to a standard shell rather than closing.

## History Suppression and User Experience

When an agent resume occurs, the restoration logic explicitly suppresses saved ANSI history:

```rust
let initial_history_ansi = if restore_plan.is_some() {
    None
} else {
    history.map(|h| h.ansi.as_str())
};

```

This prevents duplicate conversation history, as native agents maintain their own state files (e.g., `/tmp/pi-session.jsonl`). Users see a clean agent prompt rather than replayed scrollback from the previous session.

## Code Examples

### Enabling Agent Resumption During Session Restore

To activate agent restoration when calling the restore API:

```rust
use herdr::persist::restore::restore;

let restored = restore(
    &snapshot,
    Some(&history),
    24,               // rows
    80,               // cols
    4 * 1024 * 1024, // scrollback limit
    "/bin/bash",
    herdr::config::ShellModeConfig::NonLogin,
    true,             // resume_agents_on_restore
    events,
    Arc::new(Notify::new()),
    Arc::new(AtomicBool::new(false)),
);

```

When the snapshot contains a `PaneAgentSessionSnapshot` with `"source": "herdr:pi"` and a valid session path, the `pi` binary launches with `--session /tmp/pi-session.jsonl` instead of the default shell.

### Manually Creating a Resume Plan

You can generate launch arguments for supported agents using the `agent_resume` module:

```rust
use herdr::agent_resume::{AgentSessionRef, plan};

let session_ref = AgentSessionRef::path("/tmp/pi-session.jsonl")
    .expect("valid absolute path");
let resume_plan = plan("herdr:pi", "pi", &session_ref)
    .expect("supported agent");

assert_eq!(resume_plan.argv, vec!["pi", "--session", "/tmp/pi-session.jsonl"]);
assert_eq!(resume_plan.agent, "pi");

```

### Detecting Pane Resume State Programmatically

To determine if a specific pane will trigger an agent resume:

```rust
use herdr::persist::restore::{pane_restore_startup, AgentRestoreState};
use herdr::persist::snapshot::PaneAgentSessionSnapshot;

let session = PaneAgentSessionSnapshot {
    source: "herdr:claude".into(),
    agent: "claude".into(),
    kind: herdr::agent_resume::AgentSessionRefKind::Id,
    value: "sess_123".into(),
};

let mut resumed = std::collections::HashSet::new();
let mut state = AgentRestoreState {
    enabled: true,
    resumed_sessions: &mut resumed,
};

let startup = pane_restore_startup(Some(&session), None, &mut state);
assert!(startup.restore_plan.is_some());
assert!(startup.initial_history_ansi.is_none()); // History suppressed
assert!(!startup.duplicate_agent_session);

```

## Summary

- **The `resume_agents_on_restore` flag** in [`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs) controls whether native AI agents are relaunched during session recovery.
- **Pane-level deduplication** uses `HashSet` tracking of composite keys to prevent multiple processes for the same agent session.
- **Supported agents** (`pi`, `claude`, `codex`, `hermes`, `opencode`) generate specific command-line arguments via `agent_resume::plan` in [`src/agent_resume.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/agent_resume.rs).
- **Terminal history suppression** occurs automatically when resuming agents to avoid conversation state duplication.
- **Lifecycle management** via `with_respawn_shell_on_exit()` ensures panes remain available after agent termination.

## Frequently Asked Questions

### What happens if two panes reference the same agent session during restore?

The first pane to process the session inserts its **dedupe key** (comprising source, agent, kind, and value) into a `HashSet` tracked in `AgentRestoreState`. Subsequent panes detecting the same key have their `restore_plan` set to `None`, causing them to spawn standard shells instead. This prevents multiple processes from accessing the same session file concurrently.

### Which native agents support resume on restore functionality?

According to [`src/agent_resume.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/agent_resume.rs), herdr officially supports `pi`, `claude`, `codex`, `hermes`, and `opencode`. Each agent requires specific command-line arguments—for example, `pi` uses `--session` with a path, while `claude` uses `--resume` with a session ID. Custom agents or unsupported sources return `None` from the `plan` function, triggering fallback to shell restoration.

### Why is terminal history suppressed when resuming a native agent?

When `pane_restore_startup` detects a valid agent resume plan, it sets `initial_history_ansi` to `None`. This suppression is intentional because native agents maintain their own conversation history in external files (such as `/tmp/pi-session.jsonl`). Restoring the terminal's saved ANSI scrollback would duplicate messages already stored in the agent's state, creating a confusing user experience.

### What occurs if the agent fails to spawn during the restore process?

If `TerminalRuntime::spawn_agent_restore` fails after the dedupe key has been reserved, the system removes the key from the `resumed_sessions` set (as implemented around lines 85-101 in [`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs)). This rollback ensures the deduplication state remains consistent, allowing potential retry logic or alternative restoration paths to proceed without incorrectly marking the session as already resumed.