跳转至

把 Harness 砍掉一半

這篇屬於 AI Agents 系列,是〈從零打造 Agent Delivery Harness〉的續篇——嚴格說,是它的拆除記錄。

先講結論:在我檢查的這批 run 裡,反覆載入 context 與大範圍驗證付出了很高的成本,中間的角色交接卻沒有帶來可歸因的額外發現。精簡後的 Agent Delivery Harness 留下一個交付 context、確定性的流程 gate,以及一次獨立的最終 review。

前不久我才寫了〈從零打造 Agent Delivery Harness〉,把 conductor、generator、reviewer 的角色分工、RED/GREEN 循環、兩百多支感測器的架構分享給同事。這套 harness 的程式碼公開在 agent-delivery-harness。結果文章還熱著,我就把 agent 每次要讀的 instruction 行數砍掉一半,也拆掉大部分角色交接。

不是因為它錯了。它在它的時代是對的——而且對得很徹底。它只是過期了。

它曾經是對的:二三月的起點

今年二、三月我開始認真研究 harness 這件事。參考的是當時幾個重要做法:Anthropic 的 long-running agents 模式(initializer 與 coding agent 分離、feature list、增量推進)、Martin Fowler 網站整理的 Guides 加 Sensors 框架,以及 OpenAI/Codex 把 repo 當成知識 system of record 的做法。照著蓋出來之後,成效是非常好的。

那是 Opus 4.5、4.6 的時代。有 harness 的 agent 跑出來的結果,遠比沒有 harness 時好——需求寫在 issue 上、環境預檢擋在前面、角色切開、每一步有感測器把關。我印象最深的是一個專案:一個晚上自主跑下來,一張 issue 大概只要 30 分鐘,而且交付品質是可以直接 merge 的。那時候的結論很清楚:模型會寫 code,但要放手,靠的是 harness。

當時的角色切換也有明確的道理:把工作切成 conductor、generator、reviewer,每次交接生一顆新的 subagent,是為了不讓舊 agent 的記憶污染新的執行——那一代模型的 context 一長就漂移,舊的推理殘渣會滲進新的判斷。用切換換隔離,是划算的。

模型換代後,我重新算了一次成本

到了 Fable 5 和 GPT-5.6,同樣規模的 issue 從 30 分鐘變成 2 小時,甚至更多。這個落差不能直接歸因於模型或 harness:同一段期間,模型、instruction、sensors 和 gate 都在變。但它已經足以讓我停下來,重新檢查整套流程。

還好,當初打造 harness 的時候,我把 tracing 和 log 留得很全——harness 自己的 trace、每張 issue 的執行記錄,加上 GitHub Copilot 本身的原生 log。花了三個晚上比對這批真實 run,我找到兩個主要的成本來源:

其一,harness 帶來太多限制。 三千多行的 instruction 語料、兩百多支感測器、十幾種閘門,每一條規則都是某次事故留下的疤。規則只加不減,harness 越來越臃腫。我後來把這筆帳叫作 harness debt:每加一條規則,往後每個 agent、每張 issue 都得重新支付理解與遵守它的成本。更麻煩的是規則誘發的行為:規則明明寫了「GREEN 只跑相關感測器」,Claude 和 GPT 兩家的模型卻照樣每次改完就把全套跑一遍,因為它們要為閘門交證據,最保守的證據就是全跑。驗證焦慮,跨模型發作。 prompt 級的禁令,永遠輸給激勵結構。

其二,角色切換從資產變成了負債。 當初用切換來隔離舊記憶;但至少在這批 issue 裡,新一代模型用單一 context 撐完整張 issue,既有的品質 gate 沒有觀察到明顯退步。每一次交接都會生一顆新 subagent,每顆重新吞一遍三千行語料,每步中位數 5 到 7 秒的模型延遲,乘上幾千步。再疊上驗證焦慮,agent 之間的切換數量大量成長——trace 顯示 wall clock 的 62–73% 是模型在想,一個晚上 101 顆 subagent,1.69 億 subagent tokens。這些數字不能把全部延遲都歸因於角色切換,但足以確認我設計的乘數成本已經很高。

更關鍵的是,在這批 run 裡,中間的角色交接協議與證據儀式沒有帶來可歸因的額外發現。實際攔下問題的是確定性的邊界腳本與最後一次獨立 review:前者擋住模型略過 PR、直接關閉 issue;後者抓出 crash bug、注入風險,還有一份長達 844 行的過度測試。這裡保留的 review 本身也是一次角色分離,差別在於它有獨立判斷的用途,也有實際發現。trace 則負責還原每個問題最先在哪一關被抓到。

實務對照:我自己下場跑了一天

光靠 postmortem 還不夠,我做了實務對照。同一個 repo、同一套品質 gate(行為型測試、mutation 驗證、CI 綠、驗證 MERGED),兩種跑法:

  • 舊 harness 序列跑:平均每張 2.4 小時,每張燒約 2,800 萬 subagent tokens。
  • 精簡後的 harness(一個 agent 從頭做到尾,中途改跑 scoped sensors,只在 PR 前叫一次獨立 review):每張 35 到 75 分鐘——回到了當時的速度,而且沒有放寬原本的品質 gate。

這不是控制變因完整的 benchmark。兩組 run 的 issue 不完全相同,樣本量也不大;新版同時改了角色切換、instruction 和 sensors 的執行範圍,無法把改善歸功於其中一項。這組數據能支持的結論比較窄:在我這批真實工作裡,整套精簡後的 harness 交付時間大幅下降,而且既有 gate 沒有觀察到品質退步。

這裡的時間來自 issue 執行記錄與 lifecycle trace;subagent 數量、tokens 和模型耗時則取自 GitHub Copilot 的原生 session log。問題歸因以「第一個實際擋下它的 gate」計算,trace 本身只負責還原順序,不算 defect catcher。公開 repo 已提供 trace schemaproduct-quality rubric,但這篇使用的原始 session log 含專案內容,不會公開。

拆完之後:新版 harness 現在怎麼跑

新版不是把流程拿掉,而是讓一個 agent 對整張 issue 負責。為了跟上一篇對照,下面仍用 Stage 0 到 Stage 4 來說明;程式裡真正會擋住流程的,則是 start、scoped sensors、independent review、ship 四道 hard gate。

flowchart TD
  A["GitHub issue"] --> B["Stage 0: preflight + worktree"]
  B --> C["Stage 1: plan + human-input gate + feature list"]
  C --> D["Stage 2: one agent, one context<br/>TDD + scoped sensors"]
  D --> E{"所有 feature 都通過?"}
  E -- "否" --> D
  E -- "是" --> F["Stage 3: fresh-context review"]
  F --> G{"需要修改?"}
  G -- "是" --> D
  G -- "否" --> H["Stage 4: approve + PR + CI + merge + finish"]

Stage 0:先把工作區弄對

start-issue.sh 先跑環境與身分預檢,確認 GitHub 登入、必要工具和專案 gate,再建立 issue 專屬的 branch 與 worktree。這一關的工作很單純:不要讓 agent 在錯的目錄、錯的帳號或壞掉的環境裡開始寫 code。

Stage 1:先規劃,再拆 feature

同一個 agent 讀 GitHub issue 的描述與 comments,把計畫寫進 issue 的本地 tracking 目錄,並列出需要人類決定的問題。問題沒有回答完以前,不能先寫 feature_list.json。確認方向後,agent 才把 issue 拆成通常二到五個 feature;每個 feature 對應一個可觀察的驗收條件和一支 regression sensor,跨到真實 runtime boundary 時再加 e2e sensor。

Stage 2:一個 agent 做到底

這是改動最大的地方。以前是 tester、implementer、conductor 反覆交接;現在由同一個 agent 在同一個 context 裡,一次完成一個 feature。TDD 還在:先寫會失敗的測試,再做最小實作,最後跑 GREEN。差別是中途只跑 declared sensors 加上受 diff 影響的 scoped sensors,不再讓模型自行決定是否要跑整套。每個 feature 通過後就 commit、push,再處理下一個。

我拿掉的是每輪 RED、implementation、GREEN 都要交接和填證據的儀式,不是測試紀律本身。passes:true 仍然要有可執行的 verification,只是 test quality 改由最後的獨立 review 判斷。

Stage 3:只在這裡換一次 context

所有 feature 都通過後,先跑一次完整的 pre-review sensors,再叫出唯一的 subagent。它沒看過前面的工作過程,只根據 issue、驗收條件和整個 branch diff 做 review,並嘗試找出測試沒覆蓋的失敗模式。它可以補最小的測試或 fixture 來證明問題,但不能改 production code。

如果 verdict 是 NEEDS_REVISION,原本的交付 agent 回去修,reviewer 只重審被修改的 feature。這裡保留角色分離,是因為它在 trace 裡真的抓得到問題,而且它的獨立性有明確用途。

Stage 4:用硬邊界收尾

最後跑 pre-PR sensors,把 review approval 綁到目前的 HEAD,再由 create-pr.sh 同步最新的 main、檢查 gate、push 並開 PR。merge-pr.sh 會重新向 GitHub 確認 CI 全綠後才合併;finish-issue.sh 再查一次 PR 的 MERGED 狀態,寫下結論與執行成本,最後移除 worktree。

這一段刻意不交給模型自行判斷。是否審過、CI 是否通過、PR 是否真的 merge,都由腳本和 GitHub 的即時狀態回答。

回頭看,警訊早就在那裡

Cognition 在 2025 年 6 月發表的〈Don't Build Multi-Agents〉,早就提醒過一個跟我這次經驗很接近的問題:多個 agent 如果各自握有不完整的 context,又能做出互相衝突的決策,協調成本很快就會超過分工的收益。它不是反對所有 subagent,而是主張決策應維持單一執行緒;只回答窄問題、不分走決策權的 subagent 仍然有用。這不是新模型帶來的新結論,只是我到了這次 postmortem 才真正看見它發生在自己的 harness 裡。

Anthropic 對 Fable 5 的說明強調 minimal oversight、fewer turns 和更少糾正;OpenAI 的 GPT-5.6 發布報告也提到 tool-heavy tasks 需要更少 model round trips 與 guidance。兩家公司都沒有直接叫使用者砍 harness,但訊號很清楚:模型需要的外部引導正在下降,為前一代模型設計的提示、交接與護欄,應該重新證明自己的價值。

這不代表 multi-agent 一定不好。OpenAI 同一份報告顯示,可以平行探索的任務仍能從多 agent 得到收益;我的問題是反覆在同一張 issue 上重建 context,卻沒有換到平行工作的好處。

我二三月參考的文獻沒有錯,它們描述的是當時的模型與問題。幾個月後,模型完成工作的方式變了,harness 卻還留著。

獨立 review 則有另一組證據支持。研究已觀察到 LLM evaluator 會偏好自己產生的答案,而沒有外部回饋的 self-correction 也可能無效,甚至讓答案變差。這些研究不能證明 fresh-context reviewer 在所有任務上都優於 self-critique;它們支持的是比較保守的工程判斷:不要讓產生 diff 的同一段 context 成為唯一的品質裁判。再加上我自己的 trace 顯示,獨立 review 確實抓到了其他角色沒抓到的問題,所以這是唯一保留的角色分離。

拆法:每一刀都要有 trace 作證

拆的原則跟蓋的時候一樣:不憑感覺,憑證據。評估每個組件時,我會一起看執行成本、實際擋下的問題、失敗後果,以及是否已有其他控制可以取代。零命中只能觸發檢查,不能單獨證明一個 gate 沒有價值。

留下來的就是前面四道 hard gate:預檢、範圍化感測器、一次獨立 review,以及合併與收尾證據。每一道都有明確的失敗模式與可觀測結果,不必靠模型自行宣告成功。

拆掉的,是其餘一切:conductor/generator 角色檔、交接 payload 協議、每個 green 的四重品質儀式加五維自檢、red-first 證據鏈(實測 48 個 log、181 個 feature,它抓到的缺陷數:零)、重複記帳的 log 流、以及五十幾支「用 grep 檢查文件裡有沒有某句話」的偽感測器。

一天下來的帳:淨刪一萬兩千五百行;以 instruction 行數計,agent 每次要讀的內容從 3,333 行降到 1,630 行;感測器從 223 支減到 170 支;每張 issue 的模型 context 從十幾顆變成兩顆——一個交付者,一個審查者。

拆完之後,我留下四個原則

這次改版讓我重新分清楚 harness 裡各種工作的責任:

  • 同一條決策鏈,交給同一個 agent。 規劃、實作和修正需要共享前文,放在同一個 context 裡比較省。只有工作真的能平行、需要不同資訊來源,或需要獨立判斷時,才值得切出 subagent。
  • 模型負責判斷,腳本守住邊界。 測試內容和修正方式可以交給 agent;是否通過 gate、CI 是否全綠、PR 是否已合併,由工具查證。想改模型行為時,先改工具與流程,不要再補一段 instruction。
  • 每一道 gate 都要計算成本、產出與風險。 規則曾經防過事故,不代表應該永久保留;長期沒有額外發現,也不代表可以直接刪除。除了時間、tokens 和實際命中,還要看失敗後果與替代控制。成本低、後果嚴重的確定性邊界,即使命中率低,仍然值得保留。
  • tracing 是 harness 的一部分。 沒有執行記錄,這次只能憑感覺重寫流程。有了時間、tokens、角色切換與第一個攔下問題的 gate,模型換代後才能重新測量,而不是沿用上一代的假設。

這次改版沒有把 single-agent 當成新教條。新版的分工很具體:一個 context 負責交付,確定性的 gate 驗證流程狀態,另一個新 context 做最後 review。往後每次模型換代,我都會再量一次這三者的成本與產出,決定哪些該留、哪些該改。