メインコンテンツまでスキップ

AgentMux:他人のマシンで動く AI エージェントに、管制室を

· 約10分

新しいプロジェクト AgentMux をオープンソースにしました。リモートマシンで動く AI コーディングエージェントを管理するデスクトップアプリです。起動したものはすべてサーバーの tmux セッション内で動くので、アプリを閉じても、回線が切れても、ノートを閉じてもエージェントは働き続けます。Go + Wails 3 + React、MIT ライセンス。

なぜ作ったか

リモートマシンでエージェントを動かしていると、一日の始まりはいつも同じです。ターミナルを開く。SSH でログインする。コードが ~/work だったか ~/src だったか思い出す。エージェントを起動する。二台目でまた同じことを繰り返し、三台目でもまた繰り返す。そして電車に乗ろうとノートを閉じた瞬間、起動したものは接続ごと消えます。

回避策はおなじみです。壁に貼った tmux のチートシート、大量のシェルエイリアス、「どのマシンでどのエージェントが何をしているか」を書いたメモ。これで回ります —— プロジェクトが 30 個、サーバーが 8 台になるまでは。その時点でスケジューラーはあなた自身であり、しかもあまり優秀ではありません。

何をするものか

AgentMux は、すでにどこか別の場所で走っている作業をのぞく窓です。

起動したものはすべてサーバーの tmux セッション内で動き、デスクトップ側がプロセスを持つことはありません。その一点の周りに、本当に欲しかったものが並びます。全サーバー・プロジェクト・エージェントを収めた一本のツリー、稼働状態による色分け、リストが伸びたときの検索。緑の点は稼働中、灰色は待機、エージェントの下の行はそれが最後に出力した内容です。数百行あっても軽いのは、画面に映っている分しか描画しないからです。

ターミナルを閉じるのは kill ではありません。 エージェントのターミナルを開くと、その tmux ペインにアタッチされます。タブを閉じればデタッチしただけ。小さな違いに聞こえますが、働き方が変わります —— ウィンドウを恐る恐る扱わなくてよくなります。

本物のターミナルです。 ターミナル風のチャット欄ではありません。色、マウス、コピー&ペースト、検索。しかもエージェントが作業しているのと同じペインなので、邪魔せずに途中から引き継げます。

1 体にも、20 体にも話しかけられます。 対象のエージェントにチェックを入れ、指示を打ち、送信。届いたかどうかを一台ずつ受領票が返します。検証できない一斉送信は願掛けにすぎません。

ファイル操作もアプリの中で。 どのサーバーにも SFTP のファイルブラウザが付きます。移動、アップロード、ダウンロード、リネーム、削除、ディレクトリ作成。転送には進捗バーがあり、中断もできます。他の機能と同じ接続プールを使うので、ログインが増えることはありません。

マシンの状態が見えます。 メトリクスパネルは 1 コマンドでホストを読み切ります。user/system/iowait/steal に分解した CPU とコア別表示、メモリとキャッシュ、ロード、swap、inode を含むディスク使用量、ディスクスループット、NIC 別の通信レート、接続数、オープンしているファイルディスクリプタ、コンテキストスイッチ、温度、CPU 順・メモリ順の上位 5 プロセス。NVIDIA GPU も読めます —— 使用率、VRAM、温度、消費電力 —— エージェントが学習を回しているとき、本当に見たいのはその数字です。

新しいマシンの用意が苦行でなくなります。 インストールパネルはサーバーを見て、何が入っていて何が足りないかを教えます —— Claude Code、Codex、Gemini CLI、OpenCode、Aider、Cursor CLI、そしてそれらが必要とするランタイム。ワンクリックで導入でき、そのインストール自体も tmux 内で走るので、切断で中途半端なパッケージツリーが残ることはありません。

あえてやらないこと

ターミナルもエディタもエージェントも置き換えません。エージェントのモデル通信を中継することも、API キーを読むこともありません。これは制御面です。起動し、見守り、話しかけ、あとは邪魔をしません。

アプリ内で何かを削除しても、リモートの作業が死ぬことはありません。稼働中のセッションを壊す唯一のボタンは Kill という名前で、しかも先に確認します。

秘密情報の置き場所

パスワードと鍵のパスフレーズは、ディスクに触れる前に AES-256-GCM で暗号化されます。マスターキーは OS のキーチェーン —— Windows は資格情報マネージャー、macOS は Keychain、Linux は Secret Service —— に置かれます。キーチェーンに届かない場合は 0600 のファイルにフォールバックし、そのことをステータスバーで明示します。弱い保証が黙って適用されるべきではないからです。ホスト鍵は初回接続時に固定され、以後の不一致は明快な説明とともに中止します。秘密情報が UI 層に渡ることはなく、フロントエンドは「設定済みかどうか」しか知りません。

中身の作り

バックエンドは Go + Wails 3、フロントエンドは React 19 + TypeScript + Tailwind 4 + xterm.js。Go が約 4,300 行、TypeScript が約 4,000 行です。設定は SQLite に保存し、internal/ は SSH 接続プール、tmux ラッパー、SFTP、メトリクス、エージェント CLI の検出に分かれています。

サーバーごとに 1 接続。 すべてのターミナルとコマンドが、独立したチャネルとして 1 本の SSH 接続を多重化します。OpenSSH の ControlMaster と同じ発想です。アイドル接続は 10 分で回収され、状態ポーリングはすでに接続済みのサーバーにしか触れません。だから 100 台登録しても、実際に使うまではほとんどコストがかかりません。

エージェントはプロセスそのものではなく、ログインシェルの中で動きます。 起動すると agentmux/{プロジェクト}/{エージェント} セッションを作り、そこにコマンドを打ち込みます。エージェントが終了してもペインとスクロールバックは残り、そのまま自分で打ち続けられます。

デバッグに実際に時間を使った箇所を、他の人が同じ時間を使わずに済むように書いておきます。

tmux は出力中の制御文字をエスケープします。 tmux -F の出力が端末でないクライアント(SSH の exec チャネルは端末ではありません)に渡ると、制御バイトはエスケープされます。タブは _ に、0x1f はリテラルの \037 になります。タブを区切り文字に使うと、各行が黙って 1 個の解析不能なフィールドに潰れます。ラッパーは印字可能な区切り文字を使い、自由記述のフィールドを最後に置いています。

ターミナルのバイト列は IPC 境界で base64 を通ります。 PTY の出力は任意のチャンク境界で正しい UTF-8 とは限らないため、両方向でエンコードし、xterm には Uint8Array を渡します。バックエンドはシェルごとに 256 KB のスクロールバックを保持してアタッチ時に再生するので、タブを切り替えて空白のターミナルが出ることはありません。

コンポーネントの中で定義したコンポーネントは、レンダーのたびに再マウントされます。 React は要素の型を参照で比較するため、入れ子の定義は毎回新しい型になります —— サブツリー全体がアンマウントされ、effect が再実行され、下のパネルが再取得します。ここではサーバーに接続して状態ポーリングが始まると右パネルが数秒ごとにちらつく、という形で現れました。今は react/no-unstable-nested-components を lint で error にしてあるので、再発しません。

メトリクスは数字ごとに 1 コマンドではなく、全体で 1 コマンドです。 レート系の指標は、そのコマンドの内部で 0.5 秒の sleep を挟んで /proc を 2 回サンプリングして算出します。バックエンドは状態を持たずに済み、読み値の意味がパネルのポーリング頻度に左右されません。Linux の /proc に依存するため、他の OS では 0 を並べる代わりに「読めなかったもの」を報告します。

使いはじめる

Go 1.25+ と Node 20+ が必要です。リモート側には tmux と SSH アカウントが要ります(tmux が無ければ AgentMux が入れられます)。フロントエンドはバイナリに埋め込まれるので、npm run buildgo 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% に配置し、ショートカットまで作ります。ユーザー単位のインストールなので管理者権限は不要で、上書きインストールしてもデータはそのままです。

サーバーを 1 台追加し、ワークスペースにそのマシン上のディレクトリを指定し、エージェントにコマンド(claudeopencode、使っているもの)を与えて Start を押します。Ctrl+K はコマンドパレットで、アタッチ、シェルを開く、テーマ変更、CLI の導入はここからが最速です。Ctrl+B でサイドバーを畳めます。

まだできていないこと

当初の設計にあった統括 AI はまだ実装していません。一斉送信はすでにエージェントごとの受領票を返しており、難所は済んでいるのですが、モデルパネルもツール権限モデルもまだありません。フォルダはスキーマ上は存在しますが、ツリーはプロジェクトをフラットに並べています。設定のインポート/エクスポート、プラグイン機構、タブ内の分割ペインはなく、ファイル転送は一度に 1 ファイル、メトリクスの履歴は開いているパネルの中だけに存在します。

コードは github.com/tan-zhuo/AgentMux にあります。試して issue を投げてもらえると嬉しいです。

COMMENTS