一個 agent 忙不過來,就讓一群一起上:多代理怎麼分工又不打架(AI Agent 架構全解析 6/8)
前面幾篇,我們一直在打磨「一個 agent」:把核心迴圈跑穩、把複雜任務拆開來做、給它記憶跟出錯後爬得起來的韌性、讓它連續跑好幾個小時也不掉線。這些功夫都花在同一件事——把單一 agent 練得又強又耐操。但一個 agent 就只有一個 context window、一條工作線,真正的大型任務它扛不動。這一篇我們把鏡頭拉開,看的是「一群 agent」:多個 agent 怎麼同時開工、彼此傳話、談好分工,而且不會吵起來或卡死。
Layer 5 把這件事拆成三塊,每一塊對應一個很具體的問題:coordination 管「怎麼傳話」、protocols 管「怎麼講好規矩、乾淨收場」、autonomy 管「怎麼自己認領工作、不用有人一直站在旁邊派」。如果你用過 Claude Code 的 subagent 或 Agent Teams,這三塊其實就是你在背後看不到、卻一直在運作的骨架。
Coordination:先給每個 agent 一個信箱
一個大型任務要多個 agent 一起做,第一個問題不是「怎麼分工」,而是更基礎的「它們怎麼講話」。你可以把這一層想成幫團隊接上 Slack,或者更精確一點——接上一組訊息佇列。
機制很樸素:每個 agent 各自擁有一個 inbox。送訊息這個動作,本質就是「寫入收件者的 inbox」;投遞則發生在收件者自己來清空(drain)信箱的那一刻。這裡沒有中央 broker、沒有總機在轉接,只有一組共用慣例——名字、inbox 路徑、訊息形狀。一則訊息三個欄位就夠:sender、recipient、content。
團隊要幾個人、各叫什麼名字,不是寫死在程式裡的。是 lead 這個 agent 的模型在執行當下看著任務自己決定:它先呼叫 TeamCreate 把名單組出來,再對每個成員呼叫 SpawnTeammate。注意 lead 並不會親手去啟動隊友的程式——它只是丟出一個工具呼叫,由 harness 在背景 thread 上把隊友的迴圈跑起來(就是第二篇談的那個核心迴圈)。之後隊友會自己輪詢 inbox、自己行動,沒有任何一段程式在逐步指揮誰該做什麼。
這裡有兩個設計選擇很關鍵。第一,SendMessage 在 TeamCreate 之前是「無作用」的——你得先把團隊組出來,才有對象可以說話,這擋掉了「還沒有名單就開始亂傳訊息」的錯誤。第二,sender 寫完就返回,它不會 block 在那裡等回覆。要對全體廣播就把收件者設成 to="*",訊息會發給除了自己以外的每一位。
權限怎麼處理也很巧妙。隊友跑在自己的 thread 上,前面沒有坐著一個人類可以按「允許」。碰到需要授權的工具呼叫時,它會走 permission bubbling:把一則 permission_request 往上送進 lead 的 inbox,lead 再導向自己那端的審核介面,裁決以 permission_response 回到隊友信箱。整個升級重用同一條訊息管道,沒有另外開後門。這對應到 Claude Code 裡 remote agent 的權限會被橋接回你本機那個核准提示——你在終端機按下的那個 y,其實是幫一個跑在別處的隊友背書。
溝通層的三個常見踩雷。 一是遺失訊息的競態:兩個 sender 同時寫同一個 inbox,沒上 lock 就會蓋掉彼此,所以 read-modify-write 一定要序列化。二是對等 deadlock:兩個 agent 互相等對方回話而卡死,解法是把訊息排進佇列、在 turn 之間 drain,而不是用會 block 的傳送。三是把 chat 當 memory 用:對話裡的東西是一次性的,耐久的共用事實要放進 team memory,不然重要脈絡會隨對話沖掉。
Protocols:光有管道還不夠,得先講好規矩
Coordination 給了 agent 一條管道,但管道只負責搬運文字。單靠文字沒有契約——沒有東西被設閘門,沒有東西能乾淨地停下來,回覆甚至對應不到它到底在回答哪一則請求。protocol 就是疊在管道之上的那層約定,你可以把它想成「開會的議事規則跟散會條件」:什麼樣的發言算是一個提案、誰有權拍板、什麼時候可以宣布散會。
有兩種交換最需要規矩。一種是 lead 想關掉某個隊友:如果直接 kill 掉它的 thread,隊友可能正編輯到一半,會留下一個寫壞的檔案跟一筆開著沒收尾的 task 記錄。另一種是隊友想跑一個有風險的重構:它應該先問過再動手,而不是先斬後奏。這兩件事其實是同一個形狀的正反面——一方請求、另一方回覆、一個 id 把兩者綁在一起。
讓它「成為一個 protocol」而不只是兩則訊息的,是三條規則:
| 規則 | 作用 |
|---|---|
| Typed variants(具型別的訊息) | 每則訊息在 type 欄位上是一個明確的種類,handler 依型別分派,回覆不會被誤認成某個不相干的請求。 |
| Correlation id(關聯編號) | 請求送出時鑄一個 requestId,回覆時原封帶回。sender 就知道這則回覆解的是哪一筆 pending 請求。 |
| State machine(小型狀態機) | 一筆請求從 pending 走到 approved 或 rejected;針對已解析 id 的回覆直接忽略,所以重送是無害的。 |
有了這三條,shutdown 跟 plan approval 就成了彼此的鏡像:shutdown 是 lead 請求、隊友確認;plan approval 是隊友請求、lead 確認。方向相反,骨架一模一樣。
下面這段是隊友的主迴圈,把「散會條件」折了進去。你可以看到它每一輪都先看有沒有 shutdown 請求,有的話先確認、再乾淨地停下來——這正是把「先斬後奏的 kill」換成「先請求、後確認」的關鍵幾行:
1 | def run_teammate(team, me, lead, work, *, poll=0.05): |
效果是:這次停止變成一場 handshake,而不是一次 kill。lead 送出 shutdown_request,隊友把手上這一步做完、把狀態 flush 出去,回一則 shutdown_approved,這時 lead 才真正結束它、把 task 標記成已通知。中間任何一步都對得上同一個 requestId。
為什麼要這麼講究?因為「審核」跟「終止」是多 agent 最容易出人命的兩個環節。你在 Claude Code 裡看到隊友先進 plan mode、等你核准才動手,就是這套 plan approval 在跑;而一個 agent 收工時沒有把檔案寫完就被砍掉,往往就是少了這個 handshake。
protocol 層的踩雷。 用硬 kill 取代 handshake,會丟掉進行中的工作、讓 task 記錄變孤兒。孤兒請求也很致命:一則永遠不到的回覆,會讓請求永遠停在
pending,sender 就一直 block——所以每筆請求都要配一個 timeout 或閒置檢查,把卡住的請求浮出來。還有型別混淆:如果只靠 id 對應回覆,一則 shutdown 回覆有可能誤解掉一筆 plan 請求,所以查表時要順便驗證回覆的種類符不符合當初記下的請求型別。最後別忘了,審核通過不等於放行執行——被核准的計畫仍然需要權限層來為實際動作設閘門。
Autonomy:讓閒置的 agent 自己去找活幹
有了溝通跟規矩,還剩最後一個問題:工作怎麼分下去?最直覺的做法是 lead 把每個 task 逐一交給 worker,但這撐不大——十個還沒認領的 task 就是十次手動指派,lead 變成瓶頸;而一個 worker 做完就閒著,白白浪費剛載入好的 context。
autonomy 這一層的答案是「自我組織,而不是集中指派」。你可以把它想成一個成熟的自組織團隊:桌上擺一塊看板,貼滿待辦事項,誰手空了就自己走過去撕一張下來做,不用組長一張一張發。
做法是在核心迴圈外面再包一圈 outer loop。inner loop 就是第二篇那個普通的 while,當它抵達 end_turn、模型宣告這一輪講完了,agent 不會直接回傳結束,而是進入 poll。poll 會排空兩個來源:一個是定向的 inbox(第一塊 coordination 給的,專門收寄給「我」的訊息),一個是非定向的看板(放著任何閒置 agent 都能認領的 task)。它依固定優先序檢查:先看有沒有 shutdown 請求,再看 inbox 訊息,最後才看看板上的 task。找到什麼,那個東西就成為下一個 prompt,inner loop 再跑一次。
最需要小心的是「認領」這個動作。兩個閒置 agent 很可能同時把看板上同一個 task 讀成「無人擁有」,然後雙雙認領——結果其中一個做的工作被覆蓋掉了。解法是把認領放進一個 file lock 裡做「讀取、檢查、寫入」三步一體的原子操作:搶輸的那個 agent 在 lock 內重新讀,會看到 owner 已經被設定,於是拿到 already_claimed,乖乖去看下一個 task。除此之外還有一條硬規則:只有當一個 task 的相依項全部 completed,它才可以被認領,這樣沒有 agent 會傻傻去搶一個被卡住、根本做不了的工作。
這正是 Claude Code 裡 worker 的行為:它跑一個大約 500ms 的短 poll loop,宣告自己有空,挑一個 pending、沒有 owner、相依項都完成的 task,再用檔案鎖把擁有權寫進去。lead 在這個模式下是「整合者」而不是「task 路由器」——它只做一個主動步驟:把團隊組出來、把 task 貼上看板,然後退場。spawn 之後,拉工作跟決定何時收工全是每個 worker 自己的事。
autonomy 層的踩雷。 認領競爭前面講過了,一定要在 file lock 內裁決。被閒聊餓死也很常見:peer 之間的訊息如果淹沒了 shutdown 請求,一個該停的 agent 會繼續空轉——所以 poll 永遠先檢查 shutdown。還有兩個對稱的坑:一個永遠抵達不了
end_turn的階段會「卡在忙碌」、永遠不釋放;一個沒有出口條件的 poll 會「卡在閒置」、無限空轉燒 token。前者靠 stop 訊號結束,後者靠每次 poll 都檢查 abort,或在有限看板上跑滿一定次數的空 poll 後收工。另外,長時間運行的隊友若被自動 compaction 壓縮掉歷史,可能忘記自己的角色,記得把 system prompt 保留住。
三塊怎麼疊在一起
把這三塊放回一句話:coordination 給你「信箱跟名字」,讓 agent 找得到彼此;protocols 給訊息「型別跟關聯編號」,讓請求跟回覆對得上、讓停止是一場握手而非一刀砍下;autonomy 給團隊「一塊共用看板」,讓閒置的 agent 自己認領工作、把 lead 從派工瓶頸裡解放出來。
這三者也是漸進疊加的:autonomy 的隊友迴圈就是 protocols 的迴圈多加一個 poll 來源(看板),而 protocols 的迴圈又是 coordination 那個 serve_mailbox 多折進一個 shutdown handshake。同一條核心迴圈包了三層,就從「一個等人餵 prompt 的傀儡」長成「一支能自我組織、談好規矩、乾淨收場的團隊」,而 loop.py 本身一行都沒改。這也呼應整個系列的核心命題:模型負責推理與決策,harness 提供工具、狀態與限制。多 agent 不是把模型改聰明,而是幫模型架好一個容得下多條工作線的舞台。
下一篇(第 7 篇)我們會走出團隊內部,看 agent 怎麼連上外面的世界、以及你怎麼確認它真的沒亂來——也就是 MCP、外掛,還有可觀測性。當一群 agent 各自運作、彼此傳話、認領工作,你最需要的就是一雙能看穿黑箱的眼睛:誰呼叫了哪個工具、哪一筆請求還卡在 pending、token 燒在哪。那些機制,下一篇見。














