Sessions: what survives what
Each terminal tab's activity comes from that tab's engine events and PTY. A
completed tab stays completed when another tab in the same directory writes
output. File modification times do not override completion events. Completion
polling and the activity watchdog read only the transcript identified for that
session; missing or unreadable session identity never falls back to another
session in the directory. Changing sessions, including /clear, invalidates
the old completion baseline and pending reads.
Events without a tab identity remain task-level information; selecting a tab
does not assign those events to it.
On Windows, session discovery and hook-to-task attribution accept both native backslashes and Git-style forward slashes. Trailing directory separators and drive-letter spelling do not change the matched task. Daemon home verification uses the same path comparison.
Short answer: quitting Rove only detaches. A PTY-host restart or machine
reboot ends the child processes, but restores their screens and relaunches
their commands on attach. Closing a tab, deleting a managed/directory Task,
resetting a terminal, or running rove reset is an intentional teardown
instead.
Sleep is not detach. If the machine hosting the engines sleeps, local computation pauses and network connections may need recovery after wake. Closing the lid of a laptop used only as an SSH client can leave work running on a separate, awake remote host. Rove does not keep a sleeping host awake.
A restored screen is saved output, not a running process or proof of completion. A relaunched command is a new process; resuming its conversation depends on the engine and an available session identity (see Resuming a conversation). Review the engine's output before assuming an interrupted turn continued.
During recovery, an engine tab keeps its saved conversation while its shell starts. Rove converts it to a shell tab only after observing the engine run and then exit in the current TUI session. On Windows, engine history lookup accepts both native backslashes and Git's forward slashes for the same directory. For engines that generate their own session IDs, an invalid saved ID triggers discovery of an unclaimed conversation in that directory before relaunch.
What survives
| If you… | Running process | Scrollback | Tasks + worktrees | Conversation files |
|---|---|---|---|---|
| Quit the TUI | ✓ | ✓ | ✓ | ✓ |
| Drop your SSH connection | ✓ | ✓ | ✓ | ✓ |
rove daemon restart | ✓ | ✓ | ✓ | ✓ |
| Reboot, or the PTY host dies | — command relaunched on attach | ✓ restored, newest first, up to 64MB of scrollback | ✓ | ✓ |
| Close a tab | — that tab only | — that tab's ring is dropped | ✓ | ✓ |
| Delete a managed/directory Task | — all of that Task's tabs | — those rings are dropped | task record removed; worktree removed unless it's a directory Task (branch stays; a force-delete salvages uncommitted work under refs/rove/salvage/) | ✓ |
| Press F5 | — active terminal is replaced | — old ring is dropped | ✓ | ✓ |
rove reset | — all hosted sessions | — all frozen rings are dropped | worktrees ✓; task index and settings file kept unless --hard | ✓ |

The first three rows are the whole point: the TUI is a viewport, and the
daemon is replaceable. That extends to what the daemon only holds in memory.
Engine badges are not persisted, so a restarting daemon re-derives them: it
walks every surviving session once, and a tab that is a bare shell because
its engine died keeps its dead badge instead of coming back as idle —
which is what a tab that never ran an engine looks like, and which typing
into would run your prompt as shell commands. The same pass records an engine
that died while no daemon was up at all. The reboot row is the freeze/restore contract: nothing
keeps processes alive across a reboot, but the PTY host persists every
session's metadata and bounded scrollback ring to disk. The next host first
thaws a dead restored session, then the first attach replays its old screen
and respawns the command in place. Your conversation files survive separately
because the engine owns them.
rove reset stops the daemon and PTY host and wipes the frozen-session store —
including when the host had to be signalled rather than stopped gracefully,
which is exactly the wedged case reset exists for. The normal form keeps the
task index, your settings, worktrees, and engine history. rove reset --hard
also deletes tasks.json and the whole settings file
(~/.config/rove/state.json): saved projects, registered custom engines,
theme, default engine, language, onboarding. It still does not delete git
worktrees or engine-owned transcripts. See
CLI → reset for the full list.
When the PTY host stops answering, Rove waits up to 15 seconds for it before
replacing it, and only replaces a host that holds no live sessions. If it
still has sessions, or Rove cannot read the machine's process table to count
them, Rove refuses to restart it and says so; inspect it with
rove api pty-list, or kill the host yourself once you accept losing those
sessions. A connect that nothing accepts fails after
ROVE_CONNECT_TIMEOUT_MS (default 5000ms) instead of hanging.
On Windows, every hosted session runs inside its own Job Object, so the
teardowns above end everything the session started — including processes
whose parent already exited, like a dev server an engine backgrounded or a
nested Rove (dev:sandbox) an agent launched. Windows has no reparenting, so
before this those survived every teardown. A daemon of the same Rove
instance started from inside a tab (an agent's rove daemon restart) still
outlives the tab. The job needs the .NET Framework compiler every Windows
10/11 ships (csc.exe); without it the PTY host log says session jobs off
and sessions end the old way.
Why: three processes, three lifetimes
- The TUI is an attach client for standalone-host sessions. Closing it only detaches.
- The daemon owns your task index, worktree records, and the event bus.
It starts on first launch and stops after the last attached GUI disconnects,
unless an enabled routine or a live session in the PTY host holds it alive.
While an engine or shell tab is still running, the daemon stays up to
collect its activity events, so the sidebar status dots survive a detach.
The hold follows actual live standalone-host sessions, not persisted tab
snapshots. Restarting the daemon is routine:
rove daemon restart. - The standalone PTY host owns every TUI/API engine and shell process, plus their
scrollback. It's deliberately a separate process from the daemon, so a
daemon restart never kills a running engine — with one exception: a starting
daemon sweeps hosted sessions whose task is no longer in the index, so a
session belonging to a task deleted while no daemon was up is ended on the
next daemon boot. Because the host outlives daemon restarts, it also keeps
running whatever build it started with;
rove doctorreports its version, and onlyrove resetreplaces it. Like the tmux server, it exits on its own only after sitting at zero live sessions.rove resetis the explicit teardown. While it runs, it freezes every session (metadata + scrollback ring) to<home>/.rove/pty-sessions/— see Scrollback for what a periodic freeze costs and when it fires — immediately on exit, and in full at shutdown. A host that comes back up (after a crash, a reboot, an idle-exit) thaws each surviving record into a dead restored session: reattaching replays the old screen and respawns the command in place. A boot restores newest first up to 64MB of scrollback and stops there; the records past that budget are left on disk untouched, so a directory bigger than one boot wants to read never loses anything. What actually deletes is age: a record untouched for 14 days goes, which is what keeps the directory bounded. Closing a tab, deleting a task, orrove resetdeletes the record too. An intentional end is never resurrected. Each boot writes what it restored, deferred, and expired topty.log.
Detaching and reattaching
There's no detach command. Quitting is detaching. ctrl+q, closing the
terminal, an SSH drop: the connection closes and the engine keeps running.
Reattaching is just running rove again. A fresh TUI finds the background
sessions and reopens them. A still-live hosted session always wins and is not
restarted. A freeze-restored session is the exception: its old process is
already gone, so the first attach replays the frozen ring and respawns its
recorded command.
You can attach from several clients at once. Terminal output goes to every client watching that session; your cursor, focus, and unsent draft stay local to each one.
What actually ends a session
- Exiting the engine CLI is not normally a PTY death. Every engine runs inside the tab's login shell. When the CLI exits, Rove prints a settings hint for a non-zero code, returns to the shell prompt, and treats the tab as a shell. The same PTY and scrollback remain alive.
- Exiting the shell or a one-off command ends that PTY. An extra tab closes itself; if it was the task's only tab, Rove recycles the slot into a fresh engine tab, so a process that died on its own leaves you somewhere to work.
- Closing a tab explicitly kills that tab's hosted PTY and drops its frozen record. Closing the last tab is allowed and leaves the task with zero tabs: the sidebar row stays and re-opens on ⏎ / ctrl+e. A scratch task is the exception — its last tab is its whole life, so closing it tears the task down.
- Deleting a managed or directory Task stops all of its hosted sessions and drops their frozen records. The task record is removed, but the branch stays; the worktree is removed unless the task is a directory Task.
- F5 confirms, kills, and replaces the active terminal PTY. It is a
per-terminal recovery action, not the same as the global
rove reset.
Tab and split state
Rove saves each Task's tab list and each terminal tab's split tree in UI state. Closing and reopening the TUI therefore restores tab order, active tab, split directions, and custom tab or split names. This saved layout is separate from the PTY host: a still-running hosted process is reattached, while a process lost to a reboot is relaunched according to the rules above.
Every split created with ctrl+\ or ctrl+= starts a login shell in the
same worktree as its tab. The first leaf keeps the tab's original engine or
command; extra leaves do not start extra agents unless you run one yourself.
Split focus is intentionally local and temporary, so a restored layout starts
from its saved structural leaf rather than trying to reproduce another
client's cursor. Closing or exiting a leaf removes it and collapses any empty
split group.
Notifications while detached
The daemon records finishes, failures, rate limits, and permission requests in the durable attention Inbox whether or not the TUI is open. Desktop notifications are different: the TUI emits an OSC 9 escape into its current terminal stream. That works through an active SSH connection to a supporting local terminal, but after the TUI, terminal, or SSH stream closes, no desktop notification can be delivered. Reattach to see the pending Inbox items and unread state.
Scrollback
Three related limits are easy to confuse:
- What a reattach replays. The PTY host keeps ~512 KiB of recent output
per session. The live copy is in memory; the complete bounded ring is also
frozen under
<home>/.rove/pty-sessions/, immediately when the child exits, in full during a clean host shutdown, and periodically while output streams. A periodic freeze rewrites the whole ring, so it waits for one of two gates, never sooner than 5 seconds after the last one: 64 KiB of new output, or 60 seconds since the last freeze. An engine session emits a few hundred bytes a second, so 60 seconds is its normal cadence; the byte gate is what makes a build log or a largecatfreeze at the 5 second floor instead. A crash therefore loses at most the last 60 seconds of terminal repaint — a reboot or host restart restores the last completed snapshot, and the engine's own--resumecarries the conversation regardless. Closing the tab, deleting its task, orrove resetdeliberately drops the relevant frozen record. - What diagnostics retain after a death.
<home>/.rove/pty-exits.jsonstores the newest 50 records. Each has the exit code or signal, time, and the last 40 plain-text lines extracted from up to 16 KiB of raw ring data. This is a diagnostic tail, not scrollback. Records come in two layers:layer: "pty"— the terminal's own process died. Clean exits are omitted.layer: "engine"— the AI process is gone from a terminal that is still running, which is what you get when an engine crashes and drops you at the fallback shell. Written by the daemon's foreground walk, so it appears within about a minute of the death rather than instantly, and carriesvendor, the engine's own pid,parentAlive: true, and an exit code read from theEngine exited (code N)banner when the shell printed one. Recorded whether or not the engine exited cleanly — an engine that vanishes without explanation is the case worth keeping.
- How far you can scroll.
terminal.scrollbackRowsin Settings → General → Terminal, default 1000 rows. Applies to terminals started after the change.
Reattach has a fast path: if a tab was only hidden, Rove replays just the bytes written since it was parked, so waking it is bit-identical to never having left. Attaching from a different-sized terminal resizes the session, last attach wins, like tmux.
Resuming a conversation
Process survival and conversation survival are different things. The conversation is the engine's own file on disk, so it outlives every Rove process, including a reboot.
- Claude tabs pin their conversation up front, so a tab that already ran comes back into the same conversation after a reboot rather than a blank one. Codex and Kimi mint their own ids instead — Codex announces its in the terminal title, Kimi's is discovered from its session store — and Rove reopens the last conversation with the engine's own resume verb. Copilot and custom engines have no resume verb Rove knows, so their tabs relaunch fresh.
- A resumable engine tab found dead on attach gets one automatic resume attempt. If that dies too, an extra tab closes; the task's only tab recycles into a fresh engine tab rather than respawning forever. (That recycle is a convenience, not an invariant — closing the last tab yourself leaves the task with none.)
- To resume a conversation that is not represented by a tab, use the engine's
own picker (e.g. claude-code's
/resume) inside a fresh engine tab. When the conversation IS a tab, you do not have to hunt for the id:rove api get-task --task-id <id>prints each engine tab'ssessionId. - Headlessly, a tab a host restart froze is revived by
rove api send --task-id <id> --tab tab-N --respawn --prompt "…". Without--respawnthat tab refuses withTAB_RESTOREDrather than being re-run behind your back — reviving a tab Rove holds no snapshot for replays its frozen launch command, first prompt and all. Asendthat starts a fresh session while such tabs exist lists them infrozenTabs, so an automation is told the message did not reach the conversation it thought it did.
Not supported
Stated plainly so this page doesn't overpromise:
- Surviving a reboot with processes intact. Tasks, worktrees, scrollback, and conversations come back from disk, and sessions relaunch on attach, but the processes themselves die with the machine; any in-flight execution state inside them is gone.
- Attaching from another machine. The sockets are local only. Running the TUI over SSH works because both ends are on the same host; there's no native remote attach.
- Event replay on reattach. Reattaching resyncs from a snapshot, not by replaying events you missed.
- Unsent drafts. A typed-but-unsent message is local to that client and lost if it dies.