AgentMux: A Control Room for AI Agents Living on Other People’s Computers
I open-sourced a new project, AgentMux: a desktop app for managing AI coding agents that run on remote machines. Everything it starts runs inside a tmux session on the server, so closing the app, losing the wifi, or shutting the laptop leaves the agent working. Go + Wails 3 + React, MIT licensed.
Why build this
If you run agents on remote machines, the day starts the same way every time. Open a terminal. SSH in. Try to remember whether the checkout was under ~/work or ~/src. Start the agent. Do it again for the second box. And the third. Then you close the laptop to catch a train, and everything you started dies with the connection.
The workarounds are familiar: a tmux cheat sheet taped to the wall, a folder of shell aliases, a notes file listing which agent is doing what on which host. It works, right up until you have thirty projects and eight servers, at which point you are the scheduler, and you are not a good one.
What it does
AgentMux is a desktop window onto work that is already running somewhere else.
Everything it starts, it starts inside a tmux session on the server; the desktop app never owns the process. Around that one idea sits the thing you actually wanted: a single tree with every server, project and agent in it, colour-coded by what is running, searchable when the list gets long. A green dot means running, grey means idle, and the line underneath an agent is the last thing it printed. Hundreds of rows stay fast because the tree only renders what is on screen.
Closing a terminal is not killing it. Open an agent's terminal and you are attached to its tmux pane. Close the tab and you have detached. The distinction sounds small and it changes how you work: you stop being careful with windows.
It is a real terminal. Not a chat box with a terminal theme. Colours, mouse, copy and paste, search — in the same pane the agent is working in, which means you can take over mid-task without disturbing it.
Talk to one agent, or twenty. Tick the agents you want, type an instruction, send. Each one comes back with a receipt saying whether the message actually landed. A fan-out you cannot verify is just hope.
Files, without leaving the app. Every server gets a file browser over SFTP: navigate, upload, download, rename, delete, make directories, with a progress bar you can cancel. It runs over the same pooled connection as everything else, so it costs no extra login.
You can see what the machine is doing. The metrics panel reads the host in a single command: CPU broken down by user/system/iowait/steal and per core, memory and cache, load, swap, disk usage with inode pressure, disk throughput, per-interface network rates, connections, open file descriptors, context switches, temperature, and the top five processes by CPU and by memory. NVIDIA GPUs come through too — utilisation, memory, temperature, power — which is the number you actually want when an agent is training something.
A new box is not a chore. The install panel looks at a server and tells you what is already there and what is missing — Claude Code, Codex, Gemini CLI, OpenCode, Aider, Cursor CLI, plus the runtimes they need. One click installs the right one, and the install itself runs inside tmux, so a dropped connection cannot leave you with half a package tree.
Things it deliberately does not do
It does not replace your terminal, your editor, or your agent. It does not proxy the agent's model traffic or read its API keys. It is a control plane: it starts things, watches them, talks to them, and gets out of the way.
Deleting anything in the app never kills remote work. The only button that destroys a running session is called Kill, and it asks first.
Where your secrets live
Passwords and key passphrases are encrypted with AES-256-GCM before they touch disk, and the key lives in the OS keychain — Credential Manager on Windows, Keychain on macOS, Secret Service on Linux. If the keychain cannot be reached, AgentMux falls back to a 0600 file and says so in the status bar, because a weaker guarantee should never be a silent one. Host keys are pinned on first connection, and a later mismatch aborts with a plain explanation. Secrets never cross into the UI: the frontend only ever learns whether a secret is set.
How it is put together
Go and Wails 3 on the back, React 19 + TypeScript + Tailwind 4 + xterm.js on the front — around 4,300 lines of Go and 4,000 of TypeScript. SQLite holds the configuration, and internal/ is split by SSH connection pool, tmux wrapper, SFTP, metrics, and agent CLI detection.
One connection per server. Every terminal and every command multiplexes over a single SSH connection as separate channels — the same idea as OpenSSH's ControlMaster. Idle links are dropped after ten minutes and status polling only touches servers that are already connected, which is why a hundred configured machines cost nothing until you use them.
Agents run in a login shell, not as the process. Starting an agent creates agentmux/{project}/{agent} and types the command into it. If the agent exits, the pane survives with its scrollback, and you can take over by typing.
A few things that cost real debugging time, written down so they do not cost yours:
tmux escapes control characters in its output. When tmux -F output goes to a client that is not a terminal — an SSH exec channel is not — control bytes get escaped. A tab comes back as _; 0x1f comes back as the literal text \037. Using a tab as a field separator silently collapsed every row into one unparseable field. The wrapper uses a printable separator and puts free-form fields last.
Terminal bytes are base64 across the IPC boundary. PTY output is not valid UTF-8 at arbitrary chunk boundaries, so both directions are encoded and xterm is fed a Uint8Array. The backend also keeps 256 KB of scrollback per shell and replays it on attach, so switching tabs never shows you a blank terminal.
A component defined inside another component remounts on every render. React compares element types by reference, so a nested definition is a new type each time — the whole subtree unmounts, effects re-run, and any panel underneath refetches. It showed up as the right panel flickering every few seconds once a server was connected and status polling started. The rule react/no-unstable-nested-components is now an error in the lint config so it cannot come back.
Metrics are one command, not one command per number. Rates are measured by sampling /proc twice around a half-second sleep inside that command, which keeps the backend stateless and means a reading says the same thing regardless of how often the panel asks. Requires Linux /proc; on other hosts the panel reports what it could not read rather than showing zeros.
Getting started
You need Go 1.25+ and Node 20+. On the remote side you need tmux and an SSH account; AgentMux can install tmux for you if it is missing. The frontend is embedded in the binary, so npm run build has to happen before go build:
git clone https://github.com/tan-zhuo/AgentMux.git
cd AgentMux
cd frontend && npm install && npm run build && cd ..
go build -o agentmux .
./agentmux
On Windows, for something you double-click, link it as a GUI binary (go build -ldflags "-H windowsgui" -o agentmux.exe .), or run scripts\install-windows.ps1 -Build, which builds it, installs into %LOCALAPPDATA%, and creates shortcuts. It is per-user, needs no administrator rights, and installing over an existing copy keeps your data.
Add a server, point a workspace at a directory on it, give an agent a command (claude, opencode, whatever you run), and press Start. Ctrl+K opens the command palette, which is the fastest way to attach to an agent, open a shell, switch theme or install a CLI; Ctrl+B toggles the sidebar.
Not there yet
The orchestrator AI from the original design is not built — broadcast already returns per-agent receipts, which was the hard part, but there is no model panel and no tool-permission model yet. Folders exist in the schema but the tree renders projects flat. There is no config import/export, no plugin system, no split panes inside a tab, file transfers are one file at a time, and metrics history lives only in the open panel.
The code is at github.com/tan-zhuo/AgentMux — try it and file issues.
COMMENTS