Session-native terminal multiplexer

Processes are disposable. Sessions are precious.

roost is a terminal multiplexer for AI agent CLIs — pi, Claude Code, Codex, Gemini, opencode, your shell. Quit it, reboot your Mac, run roost — every tab, split, and stacked pane comes back, each agent resumed into its exact session.

No daemon. No detach. roost resurrects the desk, not the sentence being typed on it.

$ brew install navbytes/tap/roost
Star on GitHub ↗
The whole pitch is this switch. Flip it.
roost — ~/code
1 main 2 api 3 infra ~/code · saved ✓
refactor tui · pi
~/code/pi-mono
split the pane renderer into one draw fn per node
read src/ui/render.rs · 342 lines
edit src/ui/render.rs
src/ui/render.rs · draw_pane()
- fn draw_pane(f, pane) {
+ fn draw_pane(f, pane, focus: bool) {
+ let edge = if focus { ACCENT } else { RULE };
blit_grid(f, pane.grid, rect);
composing edit — 3 hunks, tests next
stack · 5 panesAlt+↑↓ write tests · claude ~/code/pi-mono/tests Claude wants to edit status_test.rs Allow this edit? 1. Yes   2. Yes, and don't ask again api-workerpi · working write docsclaude · your turn ·scratchshell · idle old buildpi · exited
Alt+n new Alt+↵ launch Alt+s stack Alt+←↓↑→ focus Alt+r edit Alt+w close Alt+? keys ◆ 1 needs you · Alt+aNORMAL
$ roost
quit — 3 tabs · 7 panes · every session saved to workspace.json
 
$
working needs input ○ waiting for you · idle ✕ exited

Real chrome, borrowed colors — these are roost's exact glyphs and layout, wearing a dark theme. In your terminal it wears yours: the chrome inherits your theme, always. Token spec: DESIGN-ui.md ↗


0

Daemons to run

6

Agent CLIs, one desk

1.4 MB

The whole binary

1

File of state: workspace.json


What roost does

The muxer contract, inverted.

01

Full workspace resurrection

Quit, reboot, reopen the terminal, run roost. It rebuilds every tab, split, and stacked pane, then resumes each agent into the exact session it was in — right working directory and all. One honest exception: a turn in flight at the moment you quit — the sessions come back, the half-typed sentence doesn't.

02

Session-native by design

Agent CLIs already persist their own conversation state and resume by id. So roost persists only the layout tree plus each pane's (adapter, cwd, session-id) — and relaunches processes on demand. No daemon required.

03

Fleet at a glance

Stacked panes collapse to one-line title bars, each carrying a live status badge — working, needs input, waiting, idle, exited. Any number of agents fit in the space of one, and roost rings the bell when a background pane needs you.

04

Drive a fleet from the CLI

list, status, spawn, fork, send, read, close, wait — eight verbs, the same binary in client mode, including a send --all broadcast to reach every pane at once. An agent inside a pane can spawn and orchestrate its own workers while you watch the whole fleet live. Authorization is scoped by default, not sandboxed — any in-pane agent with shell access can still reach the fleet token.

05

Never types into dead air

spawn --input holds the prompt until the pane's first output, so it can never land in a CLI that isn't reading yet. wait --until waiting blocks until the agent settles — and exits 3 on timeout, not 0, so a script chaining wait && read can tell a stall from a finish. Exit codes: 0 ok · 1 runtime · 2 usage · 3 timeout.

06

Nothing heavy left over

No client/server protocol, no detach, no scrollback serialization, no process migration. roost is a single foreground process — the hardest parts of a multiplexer simply don't exist here. The release binary is about 1.4 MB.

How it works

Keep the map, not the machine.

tmux / zellij

The layout is cheap, the processes are precious — keep them alive at all cost.

roost

The processes are cheap, the layout × session-id mapping is precious — persist that, relaunch the rest.

The precious state

A single file — workspace.json — is the whole product, morally. Atomic writes on every change; delete it to start clean.

{
  "active_tab": 0,
  "tabs": [{
    "name": "pi-mono",
    "panes": {
      "p1": { "adapter": "pi",
             "cwd": "~/code/pi-mono",
             "session": "01998e5f…" }
    }
  }]
}

Ports & adapters

The core is pure and fully unit-tested — it never touches a PTY, socket, or the filesystem. Real I/O lives at the edges, behind trait boundaries.

core/pure domain
ports.rstrait boundaries
agents/pi · claude · shell · codex · gemini · opencode
infra/real I/O at the edges
ui/ratatui rendering
Keys

One flat modifier layer.

Everything's on Alt, so it never fights the agents' own Ctrl bindings. Everything else passes straight through to the focused pane.

Mouse selection needs no chord. Drag to select — it copies on release. Double-click a word, triple-click a line, Shift+click extends. It goes straight to the system clipboard, over SSH too (OSC 52). Alt+c is only for scrollback, or panes where the app underneath already owns the mouse.

Alt+nnew pane
Alt+Enterquick-launch picker
Alt+←↓↑→move focus, crosses tabs at an edge
Alt+sstack ⇄ explode
Alt+ajump to needs-input
Alt+zzoom pane
Alt+uundo — reopen last closed
Alt+qquit — sessions live
+ 18 more keys
Alt+Shift+arrowresize pane
Alt+oflip orientation
Alt+gcycle layouts
Alt+ffloat scratch pane
Alt+Shift+afleet roster
Alt+eactivity feed
Alt+redit pane (name + note)
Alt+Shift+rrename tab
Alt+tnew tab
Alt+1..9 / Alt+0go to tab / last tab
Alt+i / Alt+mprevious / next tab
Alt+Shift+i / +mmove pane to that tab
Alt+wclose pane
Alt+ccopy mode
Alt+PgUpscroll mode
Alt+Shift+praw pass-through
Alt+/toggle hint bar
Alt+?full keymap
Supported agents

An adapter per CLI. Eight small methods.

Flagship

pi

A shipped extension reports session ids and exact status over roost's unix socket — no spinner-parsing. roost installs and updates it automatically at startup (opt out with ROOST_NO_EXT_INSTALL).

launch  pi
resume  pi --session <id>
Hooks

Claude Code

Hook snippets point Notification / Stop / PreToolUse at the same socket. Session detection falls back to diffing the project's .jsonl dir.

launch  claude
resume  claude --resume <id>
Generic

shell

Any $SHELL, relaunched in its saved cwd. Status comes from PTY-output heuristics — bytes flowing means working, the bell means needs-you.

launch  $SHELL
resume  relaunch in cwd
File-based

codex

Sessions are filed by date, not by project — ~/.codex/sessions/YYYY/MM/DD/ — so detection can't be scoped to a working directory the way claude's is. Newest file wins.

launch  codex
resume  codex resume <id>
File-based

gemini

Scoped per project: a slug from ~/.gemini/projects.json points at that project's chat history, and the session id is read straight out of the newest record.

launch  gemini
resume  gemini --resume <uuid>
DB-only

opencode

Every session lives in one global SQLite database, not separate files — so there's no filesystem detection at all. roost resumes only by the session id it already has on record.

launch  opencode
resume  opencode --session <id>
Get started

Two lines and a reboot you'll never fear.

Homebrew

Prebuilt binaries, checksum-verified by brew.

mise

Same binaries, no tap — straight from releases.

Releases

v0.1.11, four targets, checked against SHA256SUMS.txt.

Source

cargo install --git, no clone needed.

# Homebrew — prebuilt binary, no Rust toolchain
$ brew install navbytes/tap/roost
$ roost
# or with mise — no tap, straight from releases
$ mise use -g github:navbytes/roost
# or from source
$ cargo install --git https://github.com/navbytes/roost

macOS note: roost's shortcuts live on Alt. Tell your terminal to treat Option as Meta — Terminal.app and iTerm2 need a one-time setting; Ghostty, WezTerm, and kitty already do.

Bring your agents home.

Free and open source. Your agents come home to roost after every reboot.

brew install navbytes/tap/roost
View on GitHub ↗