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

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, where the top-level restore function accepts the critical boolean parameter:

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:

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 validates the agent and constructs the execution plan:

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), which delegates to PaneRuntime::spawn_agent_restore in 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:

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:

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:

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:

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 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.
  • 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, 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). 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →