DAY 234 · 2026-08-21

幫你的 subagent 們找一個老大

SLIDES · 8
Day 234 幫你的 subagent 們找一個老大 — 投影片 1Day 234 幫你的 subagent 們找一個老大 — 投影片 2Day 234 幫你的 subagent 們找一個老大 — 投影片 3Day 234 幫你的 subagent 們找一個老大 — 投影片 4Day 234 幫你的 subagent 們找一個老大 — 投影片 5Day 234 幫你的 subagent 們找一個老大 — 投影片 6Day 234 幫你的 subagent 們找一個老大 — 投影片 7Day 234 幫你的 subagent 們找一個老大 — 投影片 8
1 / 8

kunchenguid/firstmate 這個 repo 的 slogan 是「Talk to one agent. Ship with a crew.」——你只要交代一個人,他再往下分配給底下的人做事。一看到就引起我強烈的共鳴:我要平行跑好幾件事的時候,方法是自己開 git worktree、自己開好幾個 session、自己把 context 從這個視窗複製到那個視窗。

跑了幾天。這篇分享:它的設計、那隻不燒 token 的 bash watcher(我覺得可以直接偷走的一套做法),還有跑過幾天之後才看清楚的瓶頸——它其實不太適合拿來跨專案用。

它把「開好幾個 session」收斂成一條指揮鏈

你(captain,船長)
  ↓ 講話
first mate(大副,負責派工和監督)
  ↓ 派任務
crewmates(船員,平行跑的 agent)
  ↓ 各自一個乾淨的 git worktree
PR、本地 merge,或一份調查報告

派工你只跟大副講。要派幾個船員、每個丟什麼任務、誰卡住了要不要叫你,都是大副的事。船員跑在看得到的視窗裡——預設是 tmux window,你隨時可以切過去看,要插手也可以直接打字進去。

任務只分兩類:ship 會動到專案,最後落地成 PR 或(local-only 模式)一次核准過的本地 merge;scout 不動專案、不 push,只在 data/<id>/report.md 留一份調查報告。這個切法我滿喜歡的,因為「幫我查一下這段為什麼會這樣」跟「幫我改掉」本來就該有不同的權限。

上手是這三行,然後用你原本在用的 agent CLI 進去:

gh auth login
git clone https://github.com/kunchenguid/firstmate
cd firstmate

官方列為第一線支援的三隻是 Claude Code、Grok、Pi,另外也驗證過 Codex、OpenCode、Cursor Agent CLI。Grok 和 Cursor 要帶 --trust 啟動,不然 project hook 不會載入,turn-end guard 等於沒接上(Grok 也可以進去打 /hooks-trust)。

每個船員一個 worktree,而且沒對到 origin 就不准開工

worktree 這層它管得比我自己手動做的嚴。

bin/fm-spawn.sh 會拒絕啟動,除非那個任務路徑本身就是一個獨立的 git worktree root。再來是 base-freshness:worktree 要乾淨、而且對得上遠端預設分支的最新一版,否則不開工。worktree 這層是另一個叫 treehouse 的專案在管,tmux、herdr、zellij、cmux 這幾種 backend 共用同一組 worktree。

我自己開 worktree 的時候沒有這道檢查,開下去才發現 base 是三天前的,這種事發生過。

那隻 bash watcher 才是我真正想講的

多 agent 這件事最容易被忽略的成本,是「誰在旁邊看著」。你要知道哪一隻卡住了、哪一隻停下來在等人核准,總得有東西在輪詢。如果那個輪詢的東西本身也是一隻 LLM,那它什麼都沒做也在燒錢。

firstmate 的答案是:監督這一層根本不用模型。

bin/fm-watch.sh,1,311 行 bash。它自稱 zero-token event-driven watcher,「sleeps on the fleet and wakes the first mate only when something needs you」。關鍵在於狀態分類全部在 bash 裡做完——哪些是船長該知道的訊號、哪一個窗格安靜太久、又沒看到有在寫檔、PR 有沒有合進去——分類完確定有事,才叫醒大副那層 LLM。沒事的時候,成本是零。

它的等待迴圈叫 event_wait_or_sleep,預設 POLL 是 15 秒(FM_POLL 可以改)。有趣的是它對不同 backend 有兩條路:

backend 能不能推事件 怎麼等 一隻船員停下來等人核准,多久會被發現
可以(herdr) 等它原生的狀態轉換串流 不到一秒
不行(tmux 等) 就是 sleep 15 下一輪 poll,最慢到 stale-pane 計時器

而且它把安全網寫得很明白:外層的 poll 迴圈每一輪照跑,事件那條只會「縮短延遲,不可能漏掉一次該叫醒你的時刻」——poll 永遠是那條 fail-closed 的退路。快的那條壞了就退回慢的,退回去的行為跟原本一模一樣。

還有一個細節:這隻 watcher 是刻意設計成一次性的——出現一個真的該找你的理由就收掉一輪,它不自己續命,要靠各家 agent CLI 那邊的 adapter 重新把它啟動。

Day 233 講的 herdr 剛好就是這裡「推得動事件」的那個 backend。當時我看的是它幫我把 working / blocked / idle 標在窗格上,讓我一眼掃過去知道誰在等我;firstmate 直接接手同一組狀態,把「一眼掃過去」的那個人換成一隻 bash。不過 firstmate 文件把 herdr backend 標成 experimental,要 protocol 14 以上、要 jq,reference default 還是 tmux;而且上面那條「推得動事件」的快路徑門檻更高——要 protocol 16 以上,還要 python3,達不到就自動退回 sleep 15 的 poll。

你要走開的時候,打一聲 /afk

上面那隻 watcher 的前提是你人在旁邊,有事它才叫得動你。你不在的時候是另一套,指令是 /afk

你打 /afk,它會寫一個 state/.afk 旗標,然後起一隻 bin/fm-supervise-daemon.sh 在背景接手監督。這隻 daemon 一樣是純 bash,裡面沒有任何 LLM 呼叫——它包住 fm-watch.sh,分類靠的是 bin/fm-classify-lib.sh 裡那些 bash 判斷式。只要 state/.afk 還在,監督就歸它管,不會另外再啟動一隻 watcher 出來打架。

差別在於它變得比較捨不得吵你。一般的 wake 它自己在 bash 裡處理掉;真的該往上送的,會先攢成一則單行的 digest 再送(FM_ESCALATE_BATCH_SECS 預設 90 秒,設 0 就是即時)。但有幾種狀態是一律往上送、不打折的:done:needs-decision:blocked:failed:

怎麼結束?沒有結束指令。你下一句話只要不是它自己注入的訊息,就算你回來了。它的判斷方式是看有沒有帶 operational prefix——那個 prefix 是一個看不見的 U+2063 字元加上 FIRSTMATE_OP: 。判斷不出來的時候,它一律當作你回來了,理由在文件裡寫得很直白:captain 在場優先。

還有一條我覺得該記著:away mode 不會多給它權限。merge、砍東西、不可逆的操作、跟安全有關的選擇,你在的時候它不能自己批,你走開之後一樣不能。它只是把「誰在旁邊看著」這件事接過去,沒有把「誰可以決定」也接過去。

它不是什麼特別的框架,就是一包 skill 加 hook

這點值得單獨講,因為它會影響你怎麼看這整套東西。

我原本以為背後會多跑一隻 daemon 或什麼獨立服務。不是。除了上面那隻 /afk 的 daemon 以外沒有了,而那隻也一樣是 bash。clone 下來就是一個 repo,GitHub 標的主要語言是 Shell:

  • .claude/settings.json 掛三種 hook 事件——SessionStart、PreToolUse、Stop——每一條都是去 exec bin/ 底下的 bash script
  • Stop 上掛的是 fm-turnend-guard.shfm-claude-stop-autoarm.sh。前面說 watcher 是一次性的、要靠 adapter 重新啟動,這兩支就是那個 adapter
  • .agents/skills/ 底下 20 個 skill(/afk/ahoy/bearings/stow 這些),.claude/skills 只是一個 symlink 指過去
  • AGENTS.mdCLAUDE.md 就是它的操作規範,hard rule 寫在裡面
  • 每一種 agent CLI 各自一個資料夾:.claude.codex.cursor.grok.opencode.pi

所以「支援多種 agent CLI」的意思不是它抽象了一層出來,是它替每一家各寫了一套接法。

好消息是這代表沒有黑盒子,卡住的時候你打得開來看,也改得動。壞消息是它能做的事,上限就是 skill 和 hook 能做的事。

跑幾天之後,卡住的不是船員,是主視窗

這是實際用起來最明顯的一件事,而且跟上面那段是同一個原因。

大副本身就是一隻跑在你終端機裡的 agent session,不是獨立的排程服務。所以性質差很遠的任務——這個在查 bug、那個在改樣式——回報全部回到同一個主視窗。船員是平行的,主 session 不是:它一次還是只能決定一件事。

它處理這件事的方式我覺得對:你一直跳過不決定,它會回頭提醒你還有決定沒做。要你決定的事會變成一筆掛在那裡的 hold,帶著自己的 task id,沒回答就一直留在 open decisions 裡,你得真的回答它才關得掉。不會有東西默默不見。

但主 session 的 context 會一直長,長到要定期 compact。所以「同時處理好幾個專案的好幾個 session」這件事,實際上做不到——瓶頸就是那個主視窗:一次只能決定一件事,而且 context 還會塞爆。

我現在的用法是:同一個專案底下開一群船員,這樣很順。跨專案就不要了。

還沒答案的

「大副唯讀、只有核准過的操作能寫」這條 hard rule,我跑到現在還沒真的碰到——所以它會不會綁手綁腳,我還講不出來。另外小任務用它划不划算,我也還沒抓到那條線在哪;開 worktree、開視窗、派工、驗收這些固定成本擺在那,改一行 CSS 大概是不用了。

它的 hard rule 1 是這樣寫的:

Never write to a project. Do not edit, commit, or run state-changing commands under projects/ or in any project worktree

例外只有幾條,而且都限得很死:專案初始化、fleet sync、自我更新、核准過的 local-only merge,加上船長當下明確批准的單一操作。就算船長批准了單一操作,那個例外也涵蓋不到「forcing、stashing、discarding unlanded work」。hard rule 2 是沒有船長批准不准 merge,hard rule 3 是沒有明確授權不准砍掉還沒 commit 的東西。

順帶一提,bin/fm-pr-merge.sh 在呼叫端沒指定方法時預設帶 --squash。我自己是不 squash 的,好在 -- 後面可以直接傳 --merge 蓋掉。

這個 repo 現在的狀態

  • 3,895 顆星、1,274 個 fork
  • repo 是今年 6 月 12 日開的,兩個多月
  • open issue 218 個,open PR 763 個
  • 主要語言 Shell,MIT 授權

763 個 PR 開在那裡,代表想合進來的遠多於合得進去的。加上它是一套你 clone 下來直接跑的東西,不是裝進專案的套件——壞掉的時候是壞在你的機器上。

船員可以開很多,做決定的還是只有我一個。

最近開始做免費的一對一諮詢,幫你把 AI 接進自己的工作流——有需要的話可以約:https://www.dawsonwang.com/


來源:

延伸閱讀
看完整 242 篇 →