Killing the session shouldn't kill the conversation
A pane that reappears proves nothing. The question is whether the conversation came back with it.
If you run coding agents in a terminal multiplexer, detaching is the easy case. The daemon owns the PTYs, so closing your laptop lid drops the connection and everything keeps running. Every multiplexer since screen has done that.
Stopping the session is the hard case. The daemon exits, every pane shell is killed, and the agents go with it. When you start it again you get your layout back — tabs, lanes, panes, scrollback — and five dead terminals where your agents used to be. The shape survived. The work didn't.
Here is that gap being closed, in two minutes:
If the embed does not load, it is here on YouTube.
The test is a codeword, not a screenshot
The demo does one thing deliberately. Before anything is stopped, each agent is told a codeword
and nothing else — one gets WALRUS-1111, the other MARMOT-2222. Nothing on disk in the
project mentions either word.
Then rysh stop takes the daemon down and both agent processes with it. On the next attach,
each pane comes back and is asked what codeword it was given. Both answer correctly.
That distinction is the whole point. A pane that merely reappears proves nothing — it is easy to respawn a shell in the right place and call it persistence. The codeword is the only evidence that the conversation came back, because the answer cannot be reconstructed from anything left in the working directory.
How each half actually works
The two agents need completely different mechanics, because the two CLIs make different promises about session identity.
Claude pins its id at launch. ##claude starts claude --session-id <pane-uuid>, so the
conversation id is the pane id. Restoring it is then a matter of replaying
claude --resume <pane-uuid> in the same directory. The pane id is the invariant everything
else hangs off — which is also why ##claude new (a fresh conversation under a new id) and
##claude --fresh-session (start over, keep the pane id) are different commands.
Codex issues its own id, so it has to be discovered. There is no launch-time flag to pin
one. rysh watches ~/.codex/sessions/ for a rollout-*.jsonl whose first line's cwd matches
the pane's recorded directory and whose mtime is at or after launch. That id is stamped into the
pane's metadata, and the restore replays codex resume <id>.
The awkward case is two codex panes in the same directory — exactly what codex resume --last
cannot resolve. Each gets its own discovered id, so each comes back to its own conversation.
On restart, a sweep walks the panes that rysh itself launched agents in, and replays each resume line behind a foreground probe. Two design choices worth naming:
- It is scoped to
agent.native— agents rysh started. Aclaudeyou typed by hand stays yours, and rysh will not resurrect it. - The sweep carries no nudge. It brings the conversation back; it does not send a prompt. A session restart should not silently set five agents working and spending.
What this does not claim
Persistence stories are usually sold with the caveats removed. These are the ones that matter, and they are in the design doc rather than a footnote:
- Codex records a session when the conversation starts, not when the process does. So
##codexfollowed immediately by a stop leaves nothing to resume, and the pane comes back as a freshcodex. This was observed on a real machine, is documented nowhere upstream, and is the behaviour most likely to change under a future codex release. - Neither half has been run on Linux. The cwd resolver takes a
/procpath there instead of thelsofone, and a symlink defect that bit on macOS — codex recording/private/tmp/...while rysh asked with/tmp/...— is invisible on Linux, which means untested rather than fine. - One unexplained observation, recorded rather than swept up. Across a second stop/start
cycle, a pane's metadata was absent from its KV snapshot and the sweep skipped it. It did not
reproduce, and no code path that clears those keys was found. Root cause not established. The
user-visible cost is bounded — a pane that lost its stamps now recovers on the next
##clauderather than refusing to start — but the observation stands unexplained.
Building this also turned up two defects that no unit test could have produced. Codex's cwd
comparison was a string compare, so /tmp/x never matched /private/tmp/x and every codex pane
on a Mac would have silently fallen back to --last. And a pane that lost its metadata could
never start an agent again, because re-pinning its own id makes claude exit with
Session ID … is already in use. Both were fixed with regressions written red first.
Try it
rysh is open source, Apache-2.0, at
github.com/rysh-ai/rysh-cli-code — everything above is in the public
tags v0.2.6, v0.2.7 and v0.2.9.
go install github.com/rysh-ai/rysh-cli-code/cmd/rysh@latest
rysh create work # a session is a daemon, not your terminal
##claude # or ##codex — runs in this pane, pinned to it
rysh stop work
rysh attach work # the agents come back on their own
Tell one a codeword first. It is a better test than watching the panes redraw.