Reference
Architecture
A short map of how vmux is put together — useful whether you are scripting the CLI, pairing a phone, or just want to know why it feels this fast.
Processes
┌──────────────────┐ JSON-RPC over ┌──────────────────────────┐
│ vmux CLI │ Unix socket │ vmux daemon │
│ vmux attach UI │ ─────────────────► │ owns PTYs, layout, │
│ agent scripts │ ◄── snapshots ──── │ hooks, save/restore │
└──────────────────┘ └────────────┬─────────────┘
│
┌──────────────────┐ HTTP + WebSocket │ same socket
│ Phone app / │ (Tailscale or localhost) │
│ browser /paste │ ──── vmux relay :port ──────────┘
└──────────────────┘ (default port 4399;
relay.port / --listen)
The daemon is authoritative. Attach is a dumb client: it renders a snapshot and sends RPCs. It does not keep layout or pane state of its own. Every CLI command speaks the same protocol, which is why agents can drive workspaces without special access.
The relay is optional. When relay.enabled is on (default), attach can start
a managed relay. The relay is just another socket client plus an HTTP front door for Cmux
Remote–compatible apps — see Phone relay & remote. Turning it off does
not change local attach or CLI behaviour.
Persistence is session JSON under the XDG state dir
(~/.local/state/vmux/<session>.json), including scrollback. Runtime sockets and
pid/log files live under $XDG_RUNTIME_DIR/vmux (or /tmp/vmux-$UID/vmux).
Hierarchy
Session (default, work, …) one daemon, one socket, one state file
└── Workspace sidebar row: cwd, git branch, ports, agent status
└── Tab strip above the pane grid
└── Pane one PTY process
Plain-language version with examples: Concepts.
Snapshots and repaints
Server.generationis bumped on every meaningful session/runtime change.- Clients poll
Snapshot { since }. If nothing moved, the daemon returns{ unchanged: true, generation }and the UI skips a repaint. -
Fidelity levels:
- full — includes heavy per-pane scrollback (persistence / rare paths)
- normal — layout and status without full history
- lean — attach poll: live screen contents; scrollback only for panes the client is scrolled back in
The snapshot path must not shell out. Tools like git and ss run in a background
metadata loop and write a cache; snapshots read that cache. Port detection is part of that loop — see
Ports & forwarding.
Locks
The daemon is a struct of independent mutexes (session, panes, workspace metadata, …), not one big lock. Holding the session lock and the panes lock at the same time is forbidden — take one, copy what you need, drop it, then take the other. Locks use poison-tolerant helpers so one panicked handler thread cannot brick the daemon or relay.
Config surface
User config covers ui.*, relay.*, agent_titles.*, and
ports.* — full reference at Configuration, JSON Schema at
config.schema.json.
Source map
src/
main.rs entry point
cli.rs clap commands
daemon/mod.rs PTY management, socket server, port/meta refresh
daemon/browser.rs browser surfaces
ui/mod.rs attach TUI (ratatui)
model.rs Session / Workspace / Tab / Pane
protocol.rs versioned RPC types
config.rs config + config set
agent_hooks.rs Claude / Codex / Grok / shell integrations
paths.rs XDG paths + reserved state stems
relay/mod.rs phone relay (HTTP + WebSocket)
relay/auth.rs device pairing and tokens
Written in Rust, drawn with ratatui. No Electron, no browser runtime, one small daemon.