一個指揮官 AI,用 tmux 指揮多個 worker AI 平行開發,而且 worker 做完會自動把你叫醒。
多開幾個 AI 終端很容易,難的是指揮官不知道誰做完了。 worker 做完不會敲你,你就只能一直輪詢「好了沒」,或是掛機空等—— 實測發生過 worker 完工 14 分鐘沒人收、完成 21 分鐘後才被發現「怎麼還沒開始」。
fleet 解決的就是這件事:worker 完工把報告寫進共用目錄,
背景 watcher 偵測到報告檔落地,就自動把一句短訊注入指揮官的輸入框把人叫醒。
這套工具的價值不在「開很多 AI」,在回報閉環與假訊號防護。
# 1. 開一組新艦隊(預設 2 claude + 1 codex),開完直接把你帶進畫面
fleet up
# 2. 告訴 fleet 你(指揮官)坐在哪 —— 在你自己那格終端裡跑
fleet commander here
# 3. 派工:寫好 brief,送短訊指向它
fleet task cc1 refactor-auth # 自動找 $FLEET_TASKS/refactor-auth.md
# 4. 看板(不帶參數就是看板)
fleet
# id kind 狀態 ctx 最後活動
# cc1 claude 🏃 跑中 68% Editing src/auth.rs
# cx1 codex 😴 閒置 91% -
# 5. 然後你就可以去做別的事。
# cc1 寫完報告的那一刻,你的輸入框會自己跳出:
# 📥 cc1 交報告:20260803-1420-cc1-refactor-auth.md (r1754209200)已經自己開好一堆 claude / codex 了?不用重開,一行接管全部:
fleet discover --adopt # 掃到的全部接管、自動取名(會跳過你自己那格)日常九成只會用到這幾行:
| 想幹嘛 | 指令 |
|---|---|
| 開一組新艦隊 | fleet up |
| 一組裡再加一隻 | fleet add cc / fleet add cx / fleet add cc -n 3 |
| 看板 | fleet |
| 跟某一隻講話 | fleet send <名字> "文字" |
| 派正式任務 | fleet task <名字> <brief> |
| 看某一隻的畫面 | fleet peek <名字> |
| 進去艦隊畫面 | fleet attach |
| 收掉一組 | fleet down <組名> |
| 出問題時 | fleet doctor |
第一次用多一步 fleet commander here(在你自己坐的那格終端裡跑),
之後 worker 交報告就會自動叫醒你。
你不需要記 %NN。 那是 tmux 給每格終端的內部編號,
fleet commander here 會自己抓、fleet discover 會把指令整行印好給你複製。
多開幾組互不影響 —— 再打一次 fleet up 就是全新的一組,
fleet ls 的 socket 欄會顯示它屬於哪一組(agents、agents-2…)。
git clone https://github.com/<you>/ai-fleet-cli.git
cd ai-fleet-cli
./install.shinstall.sh 會:
- 檢查 tmux / python3 版本
- 建立執行期目錄(
FLEET_HOME、報告目錄、派工單目錄) - 把
fleet裝到你的 PATH - 偵測到
~/.claude/skills/就順便裝指揮官 skill(--no-skill可關)
它不會改你的 shell rc。
需要設環境變數時,它只會把該加的 export 指令印出來讓你自己複製貼上——
自動改別人的 .zshrc / .bashrc 是很糟糕的行為。
裝完先跑健檢:
fleet doctor| 來源 | 怎麼來 | socket | fleet down 時 |
|---|---|---|---|
| adopt | 接管你已經開好的 pane(在你日常的 tmux session 裡) | -(預設 socket) |
只解除登記,永不 kill |
| spawn | fleet up 新開一組,跑在獨立 socket(FLEET_SOCKET,預設 agents) |
agents |
kill 掉 |
adopt 是主要模式。 使用者通常已經開好一堆 AI 終端了,重開一次(啟動+信任提示+載入專案)很慢, 而且 fleet 不該假設自己能控制 pane 的生命週期。
兩種來源用同一組 id 定址、同一套 send / peek / wait,registry 會記住各自的 socket。
watcher 是背景常駐行程,週期性掃報告目錄與各 worker 的畫面,符合條件就注入短訊給指揮官。
| 訊號 | 觸發條件 |
|---|---|
| 📥 交報告 | 報告目錄出現新檔或內容 hash 變更,且 mtime 靜置 STABLE_SECS(3)秒 |
worker 由 busy 轉非 busy 超過 IDLE_SECS(90)秒,且距最後一份報告 > QUIET_AFTER_REPORT(300)秒 |
|
| 🚧 卡住 | stuck 訊號出現超過 STUCK_SECS(60)秒且非 busy(忙碌中排隊是正常的,不算卡住) |
首次啟動有 backfill 保護:首次見到且 mtime 超過 BACKFILL_SECS(600)秒的舊檔只建基準線不通知,
避免一啟動就把整個報告目錄重播一遍洗版。
watcher 巡邏時如果在 worker 畫面上比對到 adapter 定義的攔截框,
會自動送出預設按鍵再送 Enter,讓 worker 不會因為一個沒人回答的對話框而停擺整晚。
內建範例:codex 的「Retry with a faster model」一律選 2(等強模型,不降級品質)。
規則寫在 adapter 檔裡(autoreplyN_match / autoreplyN_keys,N 為 1–9),是資料不是程式碼——
你的 CLI 有別的攔截框,加兩行 conf 就好。命中自動應答的那一輪會跳過狀態判定,
因為畫面正要變,這時判忙碌/閒置沒有意義。
為什麼是盯報告檔,而不是要求 worker 主動回報? 因為 worker 會忘記回報,codex 還會卡在輸入框。詳見 docs/design.md。
| 指令 | 做什麼 |
|---|---|
fleet |
即時看板(不帶參數的預設動作) |
fleet mon [-w] |
看板;-w 持續刷新(需要真 TTY,被 pipe 時只印一張快照) |
fleet -w |
fleet mon -w 的 alias |
fleet doctor |
健檢:tmux / python3 / PATH / watcher / 報告目錄 / 指揮官設定 / 舊佈局 |
fleet version |
版本 |
| 指令 | 做什麼 |
|---|---|
fleet discover |
掃現有的 claude / codex pane,列出接管指令 |
fleet discover --adopt |
掃到的 pane 全部接管、自動取名(跳過指揮官那格與已登記的) |
fleet adopt <名字> <pane> [socket] |
單獨接管一格(永不 kill);pane 直接複製 fleet discover 印的那行 |
fleet who <id> |
這個 id 是哪個視窗 |
fleet rename <舊> <新> |
改名 |
fleet forget <id> |
解除登記(不 kill pane) |
fleet ls |
registry 清單 |
fleet gc |
清理 state.json 裡的孤兒項目 |
| 指令 | 做什麼 |
|---|---|
fleet up [--cc N] [--cx M] [-C dir] |
新開一組,預設 --cc 2 --cx 1。已有一組就自動再開獨立的一組,開完直接 attach 進去(--no-attach 可關) |
fleet add <kind> [dir] |
動態增援一隻。kind 是 adapter 名(內建 cc / cx) |
fleet attach |
進 spawn 出來的 session |
fleet down [組名] [-y] |
收攤:kill 新開的那組(並清掉登記);接管的 pane 與其登記都不動。多組時會要你指名,--all 才全收 |
fleet down對 adopt 進來的 worker 是完全不碰——pane 不 kill,registry 那一列也留著。 想解除登記用fleet forget <id>。
| 指令 | 做什麼 |
|---|---|
fleet send <id> "文字" |
送訊息(也可從 stdin) |
fleet bcast "文字" |
廣播給全體 |
fleet task <id> <brief> |
派正式任務(送短訊指向 brief 檔) |
fleet peek <id> [行數] |
讀 worker 畫面(預設 40 行) |
fleet wait <id> [秒] |
等 worker 回到 idle(預設最多 300 秒) |
fleet task 的 brief 解析順序,找到第一個存在的就用:
<brief>當成路徑直接用(相對或絕對都可以)$FLEET_TASKS/<brief>$FLEET_TASKS/<brief>.md
所以 fleet task cc1 refactor-auth 會找到 $FLEET_TASKS/refactor-auth.md,
而 fleet task cc1 ./notes/adhoc.md 直接用你給的路徑。三個都不存在就報錯,不會亂送。
fleet send/fleet task不做送達回讀驗證。 它們走 bracketed paste 送出, 由你當場看畫面確認。這也是為什麼兩者都會提醒你fleet peek—— 送出 ≠ 有在跑,見 pitfalls #6。 帶回讀驗證的是 watcher 通知指揮官那條路徑(send_verified)。
| 指令 | 做什麼 |
|---|---|
fleet reports [N] |
列最近 N 份報告 |
fleet lint <報告檔> |
報告四節 schema 驗證 |
fleet watch start|stop|restart|status|log |
回報閉環 watcher |
| 指令 | 做什麼 |
|---|---|
fleet commander here |
把「你現在坐的這格」設成指揮官(自動抓 $TMUX_PANE,不用查 %NN) |
fleet commander |
顯示目前設定 |
fleet profile [<name>] |
不帶參數=列出;帶名字=建立該 profile 並印出切換指令 |
fleet config |
顯示生效中的設定與其來源(環境變數/設定檔/預設) |
切換 profile 是靠環境變數,
fleet profile <name>只負責建立目錄並把指令印給你:export FLEET_PROFILE=work這是刻意的——
fleet是短命行程,它改不了呼叫它的那個 shell 的環境。
fleet 的價值一半在 CLI,一半在「指揮官 AI 知道怎麼用它」。 沒有這一步,你每開一個新 session 都要重新解釋一次 「你是指揮官、可以派工給其他 pane 裡的 AI」。
install.sh 會在偵測到 ~/.claude/skills/ 時,把
share/skills/fleet-commander/
symlink 過去(不是複製——fleet 升級時 skill 內容自動跟著更新)。
./install.sh # 有 ~/.claude/skills/ 就裝,沒有就完全跳過
./install.sh --skill # 一定要裝(目錄不存在也會建)
./install.sh --no-skill # 一定不裝目標路徑已經有東西而且不是安裝器建的 → 絕不覆寫,只印出手動指令讓你自己決定。
skill 本體只有一頁(開工三件事、派工、看板讀法、三條硬規則), 刻意不塞完整 SOP。指揮官 AI 需要細節時自己跑:
fleet protocol # 印出完整指揮官協定(= docs/commander-protocol.md)這樣協定只有一份、不會有副本分岔,skill 也不用寫死安裝路徑。
其他 harness(Codex、Cursor…)沒有 skill 機制的,把
fleet protocol 的輸出貼進該工具的系統提示或 AGENTS.md 即可。
| 變數 | 預設 | 用途 |
|---|---|---|
FLEET_HOME |
${XDG_DATA_HOME:-$HOME/.local/share}/fleet |
執行期資料根目錄 |
FLEET_CONFIG |
${XDG_CONFIG_HOME:-$HOME/.config}/fleet/config.env |
設定檔 |
FLEET_PROFILE |
default |
艦隊 profile(支援多艦隊;決定 $FLEET_HOME/profiles/<p>/) |
FLEET_REPORTS |
$HOME/fleet/reports |
報告目錄 |
FLEET_TASKS |
$HOME/fleet/tasks |
派工單目錄 |
FLEET_DEFAULT_DIR |
$PWD |
fleet up 的預設工作目錄 |
FLEET_SOCKET |
agents |
spawn 用的 tmux socket |
FLEET_SESSION |
fleet |
spawn 用的 tmux session |
FLEET_LIBDIR |
由 bin/fleet 自動推導 |
安裝後的 lib/libexec/adapters 根目錄 |
FLEET_AUTO_FORGET |
1 |
worker 被 exit 掉後自動從 registry 移除(0 關閉) |
FLEET_FORGET_STRIKES |
3 |
連續幾輪確認 pane 不見才移除。tmux server 問不到時一律不算數 |
FLEET_MAIN_PANE_WIDTH |
60% |
艦隊畫面左邊主格的寬度(main-vertical 版面) |
FLEET_SEND_DELAY |
1 |
貼字與 Enter 之間的等待秒數 |
FLEET_POLL_SECS |
3 |
watcher 掃描間隔 |
FLEET_STABLE_SECS |
3 |
報告檔靜置多久才算落地 |
FLEET_IDLE_SECS |
90 |
由忙轉閒多久算「閒置未交報告」 |
FLEET_STUCK_SECS |
60 |
卡住訊號持續多久算真卡住 |
FLEET_QUIET_AFTER_REPORT |
300 |
剛交過報告後多久內不發閒置警報 |
FLEET_BACKFILL_SECS |
600 |
首次見到且超過這個秒數的舊報告只建基準線不通知 |
FLEET_MAX_ATTEMPTS |
3 |
送達驗證連續失敗幾次後熔斷 |
FLEET_REPORT_COOLDOWN_SECS |
300 |
同一份報告通知後多久內的補寫合併成一則(0 關閉) |
FLEET_LINT_REPORTS |
1 |
通知前是否對報告跑四節驗證(0 關閉) |
以上全部都可以在 config.env 覆寫,也可以用真實環境變數覆寫。
優先權:真實環境變數 > config.env > 內建預設。
想知道某個值現在是誰決定的,跑 fleet config——它會逐項標出來源。
fleet config目前不列FLEET_CONFIG、FLEET_LIBDIR、FLEET_LINT_REPORTS三項: 前兩者分別顯示在該指令的檔頭與fleet version,FLEET_LINT_REPORTS只在 watcher 內就地取用。 三者都仍可正常覆寫。
KEY=value,一行一條,# 開頭是註解:
# ~/.config/fleet/config.env
FLEET_REPORTS=$HOME/work/reports
FLEET_TASKS=$HOME/work/tasks
FLEET_SOCKET=agents
FLEET_IDLE_SECS=120載入時的三條安全規則:
- key 必須是
FLEET_開頭的大寫名稱——設定檔改不了PATH/LD_PRELOAD - 值不得含反引號或
$(——設定檔不是任意程式碼執行的入口 - 已經存在於環境的 key 一律不覆寫——真實環境變數優先
不符合的行會被拒絕載入並印警告,不會靜默忽略。
$FLEET_HOME/profiles/$FLEET_PROFILE/
registry id<TAB>socket<TAB>pane<TAB>kind
commander socket<TAB>pane
state.json 單一狀態檔(去重/熔斷/worker 轉換)
watch.log 結構化日誌(JSON Lines)
watch.pid daemon pid
locks/ mkdir 原子鎖
沒有任何 .notified / .hash 散檔,全部進 state.json。
訊號比對規則是資料不是程式碼。新增一種 worker CLI = 新增一個 adapters/<kind>.conf,
不需要改任何程式碼:
name=claude
command=claude
detect=bypass permissions|Claude Code|for shortcuts|esc to interrupt
ready=for shortcuts|bypass permissions on
busy=esc to interrupt|without interrupting|shell still running|Running [0-9]+ shell
stuck=Press up to edit queued messages|\[Pasted Content [0-9]+ chars\]
asking=Do you want|❯ 1\.|Ready to submit
ctx=◔ *([0-9]+)%
trust=trust this folder|Do you trust值是 ERE(同時給 grep -E 與 python re 用,所以只能用兩者的交集語法)。
有幾個地方是刻意偏離原始規格的,寫在這裡免得日後有人「順手改回去」。
原始規格寫的是 ~/.config/fleet/config.toml。實作沒有照做,理由:
- bash 沒有可靠的 TOML parser。 這個工具的核心是 bash(tmux I/O 只能在 bash 做),
設定必須在 bash 側就能讀。自己用
sed/awk拼一個 TOML parser 是 bug 工廠—— 多行字串、陣列、巢狀 table、跳脫規則,每一項都是一個等著咬人的邊界情況。 - 引入相依只為了讀設定不划算。 要正確解析 TOML 就得依賴外部工具或 python 套件, 而這個工具現在的相依只有 tmux + python3 + bash 3.2,全是系統內建。
- 設定內容本來就是扁平的。 全部是
FLEET_*的純量。TOML 的表達力用不上, 只是多付 parser 的代價。 KEY=value可以被安全地驗證。 三條規則(key 白名單、值禁止命令替換、環境變數優先) 用幾行 bash 就能寫對,而且看得懂——這比一個「大致能動」的 TOML parser 值得信任。
代價是設定檔不能有結構化資料。目前不需要;真的需要的那天,該長結構的東西 (例如 adapter 規則)已經有自己的檔案格式了。
| 選擇 | 替代方案 | 為什麼不選替代方案 |
|---|---|---|
| 盯報告檔落地 | worker 主動回報 | 已證明會失敗——worker 會忘、會卡、context 會爆 |
pane id %NN |
session:win.pane 座標 |
座標會位移,派工靜默送錯人 |
| adapter 是資料檔 | 規則寫在程式碼 | 同一份 regex 曾複製到 4 個檔案並漂移 |
單一 state.json |
.notified / .hash 散檔 |
檔名轉碼會撞 key,中文報告永久靜音 |
| bash + python3 | 重寫成單一語言 | tmux I/O 只能 bash;純函式邏輯要能測 |
每一條的完整推導在 docs/design.md。
| 項目 | 版本 |
|---|---|
| tmux | ≥ 3.0 |
| python3 | ≥ 3.8 |
| bash | 3.2+(macOS 內建那個就夠) |
沒有其他相依。純函式邏輯放 python3(可測),tmux I/O 留在 bash。
| 文件 | 內容 |
|---|---|
| docs/ARCHITECTURE.md | 實作契約:目錄佈局、路徑、state.json schema、送訊協定 |
| docs/design.md | 為什麼這樣設計——各項核心決策的理由與失敗經驗 |
| docs/pitfalls.md | 血淚細節。踩過的每一個坑:症狀/根因/修法。改動前務必讀 |
| docs/commander-protocol.md | 給「指揮官 AI」讀的派工 SOP(fleet protocol 印同一份) |
| share/skills/fleet-commander/SKILL.md | Claude Code 用的一頁版指揮官 skill |
| docs/migration.md | 從舊版個人 fleet 佈局遷移 |
- ❌ 不改成「worker 主動回報」——已證明會失敗,盯報告檔落地是核心決策
- ❌ 不重寫成其他語言
- ❌ 不自動修改使用者的 shell rc
- ❌ 不加自動合併 PR——合併是人的決定
- ❌ 不注入使用者的 AI CLI 設定檔
MIT