跳到主要内容

AgentMux:给跑在别人机器上的 AI agent 一间控制室

· 阅读需 7 分钟

开源了一个新项目 AgentMux:一个桌面应用,用来管理跑在远程机器上的 AI 编码 agent。它启动的一切都在服务器的 tmux 会话里,所以关掉应用、断网、合上电脑,agent 都还在干活。Go + Wails 3 + React,MIT 协议。

为什么做这个

在远程机器上跑 agent 的人,每天开头都是同一套动作:开终端,SSH 上去,想半天代码是在 ~/work 还是 ~/src,启动 agent。换第二台再来一遍,第三台再来一遍。然后合上笔记本赶地铁,刚才起的东西跟着连接一起没了。

绕过去的办法大家都熟:墙上贴一张 tmux 速查表,一堆 shell 别名,一个记着「哪台机器上哪个 agent 在干什么」的笔记文件。这套东西能用,直到项目变成三十个、服务器变成八台 —— 那时候调度器就是你本人,而你干得并不好。

它做什么

AgentMux 是一扇窗,看向已经在别处运行的工作。

它启动的一切都跑在服务器的 tmux 会话里,桌面端从不持有进程。围绕这一点,才是真正想要的那个东西:一棵树装下所有服务器、项目和 agent,按运行状态着色,列表长了还能搜。绿点表示运行中,灰点表示空闲,agent 下面那行就是它最后打印的内容。几百行也不卡,因为树只渲染屏幕上看得见的部分。

关掉终端不等于杀掉它。 打开 agent 的终端,你就是附加到了它的 tmux pane 上;关掉标签页,你只是分离了。这个区别听着小,但它会改变习惯 —— 不用再小心翼翼地对待窗口。

这是真终端。 不是套了终端皮肤的聊天框。颜色、鼠标、复制粘贴、搜索都在,而且就在 agent 正在工作的那个 pane 里,所以可以中途接手而不打断它。

对一个 agent 说话,或者对二十个。 勾选要发的 agent,输入指令,发送,每个都会回一张回执告诉你消息到底送到没有。无法验证的群发只是许愿。

文件不用离开应用。 每台服务器都有基于 SFTP 的文件浏览器:进目录、上传、下载、重命名、删除、新建文件夹,传输有进度条、可以取消。它走的是和其它功能同一条连接池里的连接,不额外登录一次。

能看见机器在干什么。 指标面板用一条命令读完整台主机:CPU 按 user/system/iowait/steal 拆开、每个核心单独显示、内存与缓存、负载、swap、磁盘占用(含 inode 压力)、磁盘吞吐、每张网卡的速率、连接数、文件描述符、上下文切换、温度,以及按 CPU 和内存排序的前五个进程。NVIDIA 显卡也会读出来 —— 利用率、显存、温度、功耗 —— agent 在训练东西的时候,这才是真正想看的数字。

新机器不再是苦差事。 安装面板会看一眼服务器,告诉你哪些已经装了、哪些没有 —— Claude Code、Codex、Gemini CLI、OpenCode、Aider、Cursor CLI,以及它们依赖的运行时。一键装上,安装过程本身也跑在 tmux 里,所以掉线不会留下半个装了一半的包树。

它刻意不做的事

它不取代你的终端、编辑器或 agent,也不代理 agent 的模型流量、不碰它的 API key。它是控制面:负责启动、观察、对话,然后让开。

在应用里删任何东西都不会杀掉远端的工作。唯一会摧毁运行中会话的按钮叫 Kill,而且它会先问你。

密钥放在哪

密码和私钥口令在落盘之前用 AES-256-GCM 加密,主密钥存在系统钥匙串里 —— Windows 用凭据管理器,macOS 用 Keychain,Linux 用 Secret Service。钥匙串连不上时会退回到 0600 权限的文件,并在状态栏明确说出来,因为更弱的保证不该悄无声息。主机密钥首次连接时固定,之后不匹配直接中止并给出解释。密钥从不进入界面层:前端只知道某个密钥「设了没有」。

怎么搭起来的

后端 Go + Wails 3,前端 React 19 + TypeScript + Tailwind 4 + xterm.js,大约 4300 行 Go、4000 行 TypeScript。SQLite 存配置,internal/ 下按 SSH 连接池、tmux 封装、SFTP、指标、agent CLI 探测分开。

每台服务器一条连接。 所有终端和命令都以独立 channel 复用同一条 SSH 连接,和 OpenSSH 的 ControlMaster 一个思路。空闲连接十分钟回收,状态轮询只碰已经连上的服务器 —— 所以配一百台机器,在真正用到之前几乎不花代价。

agent 跑在登录 shell 里,而不是作为进程本身。 启动 agent 会创建 agentmux/{项目}/{agent} 会话,再把命令敲进去。agent 退出时 pane 还在,滚动历史还在,可以直接接手继续敲。

几个花了真金白银调试时间的坑,写下来省别人的:

tmux 会转义输出里的控制字符。tmux -F 的输出送给一个非终端的客户端时(SSH exec 通道就不是终端),控制字节会被转义:制表符变成 _,0x1f 变成字面量 \037。用制表符做字段分隔符,会让每一行静默塌成一个无法解析的字段。封装层改用了可打印分隔符,并把自由文本字段排在最后。

终端字节在 IPC 边界上走 base64。 PTY 输出在任意分块边界上都不是合法 UTF-8,所以两个方向都编码,xterm 拿到的是 Uint8Array。后端还为每个 shell 保留 256 KB 滚动缓冲并在附加时回放,所以切标签页永远不会看到一片空白。

在组件内部定义组件,每次渲染都会重新挂载。 React 按引用比较元素类型,嵌套定义每次都是新类型 —— 整棵子树卸载、effect 重新执行、下面的面板重新拉数据。表现是连上服务器、状态轮询一开始,右侧面板就每隔几秒闪一下。现在 react/no-unstable-nested-components 在 lint 配置里是 error,不会再回来。

指标是一条命令,不是一个数字一条命令。 速率类指标在那条命令内部围绕半秒 sleep 采样两次 /proc 算出,后端因此保持无状态,读数也不随面板轮询频率改变含义。依赖 Linux 的 /proc;其它系统上面板会告诉你哪些读不到,而不是显示一堆 0。

上手

需要 Go 1.25+ 和 Node 20+,远端需要 tmux 和一个 SSH 账号(没有 tmux 的话 AgentMux 可以帮你装)。前端会被嵌进二进制,所以 npm run build 必须在 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

Windows 上想要双击运行的版本,链接成 GUI 子系统即可(go build -ldflags "-H windowsgui" -o agentmux.exe .),或者直接跑 scripts\install-windows.ps1 -Build,它会构建、装到 %LOCALAPPDATA% 并建好快捷方式,按用户安装、不需要管理员权限、覆盖安装不动你的数据。

加一台服务器,给工作区指一个它上面的目录,给 agent 一条命令(claudeopencode,你跑什么就写什么),按 Start。Ctrl+K 是命令面板,附加 agent、开 shell、换主题、装 CLI 走它最快;Ctrl+B 收侧栏。

还没做的

原设计里的主控 AI 还没实现 —— 广播已经返回每个 agent 的回执,管道是在的,但还没有模型面板和工具权限模型。文件夹在数据库里有,树目前平铺项目。没有配置导入导出、没有插件系统、标签页内还不能分屏,文件传输一次只能传一个,指标历史只活在打开着的面板里。

代码在 github.com/tan-zhuo/AgentMux,欢迎试用和提 issue。

评论 / COMMENTS