Boris Cherny 那句「我不再對 Claude 下 prompt 了,我有一堆迴圈在跑、由它們去 prompt Claude 並決定下一步——我的工作是寫迴圈」聽起來像生產力捷報。Armin Ronacher 把它當成一道分水嶺:當判定「做完了沒」的權力從 model 移到外層迴圈,軟體會從一台你看得懂的確定性機器,慢慢變成一個你只能診斷、無法理解的有機體。
當 harness 迴圈接管程式碼
這是一篇 opinion essay,不是 benchmark 報告,所以下面每一個判斷我都標清楚是 Ronacher 的、還是我的。先把他要談的東西定義清楚:harness loop 不是 agent 內部那個「想一步、做一步、再想一步」的迴圈,而是包在它外面的另一層控制。用他的話說,這是「the harness level loop: the loop outside the agent loop」。任務丟進 queue,一台機器去嘗試,然後由 harness——不是 model 自己——判定這件事完成了沒;沒完成就改 context、換指令、甚至換一台機器接手。最關鍵的一句他講得很白:「adopting the idea of harness loops means that the harness decides when work is finished.」done 的判定權搬家了,這是整篇文章的支點。
這個搬家有個很具體的後果。傳統上,model 跑到某個點會說「I am done」,那是它對「完成」的自然認知。harness loop 把這個自然停點拿掉了——Ronacher 的原話是「The task stays alive beyond the point where the model by itself would normally have said: 'I am done.'」任務被續命到 model 本來會收手的點之後。對有界任務,這是好事:你知道終點長什麼樣(測試全綠、port 完成、benchmark 過線),harness 就照著那個外部標準把 model 逼到底。問題出在終點本身就模糊、而且軟體要活很久的時候。
先承認它哪裡真的好用
Ronacher 不是 loop 的反對者,這點要先講清楚,否則會把他的論點讀成盧德派。他自己就用這套方法把 MiniJinja 移植到 Go——「I have used it with success myself to port MiniJinja to Go.」這是第一手的成功案例。他也引了更大規模的例子:據報導,有人用類似的 loop 把 Bun 的一部分從 Zig 移到 Rust(他用的是「the reported work around moving parts of Bun from Zig to Rust」,我保留他「據報導」的 hedge,這不是他親自驗證的數字)。
把這些案例排在一起,會看到一條共同線索:它們要嘛是在「轉換既有程式碼」(port 是把一份已經有正確語意、有完整測試的東西換一種語言重寫),要嘛是在產出「非長存」的東西(效能探索、安全掃描、proof-of-concept)。這類工作的「完成」有外部、客觀、機器可判定的定義,所以把判定權交給 harness 沒有損失——反而 harness 的不知疲倦正是優勢。
為什麼 port 這麼適合?因為它把最難的部分外包給了既有的東西。一份要移植的程式碼自帶一個 oracle:原版的行為就是規格,原版的測試就是驗收條件。harness 不需要懂「這個系統該長什麼樣」,它只要反覆比對「新版的行為跟舊版一不一樣」。換句話說,port 任務裡那個本來需要人類品味去回答的「什麼叫做對」,已經被舊程式碼凍結成一個可機器執行的判準。效能探索與 PoC 也是同一個道理的變形——benchmark 數字、能不能跑起來,都是不需要人來裁決的客觀訊號。Ronacher 沒有把這套方法浪漫化,他承認的甜蜜區邊界很清楚:完成有客觀定義、且沒人需要在五年後讀懂這份程式碼。下面這張圖把「適合丟給 loop 的工作」跟「Ronacher 擔心的工作」並排,點任一張卡看它為什麼落在那一邊。
點任一張卡看它為什麼落在那一邊 · 4 類工作
loop 友善的有界任務 vs Ronacher 擔心的長存軟體
code porting · loop 友善
port 把一份有正確語意、有完整測試的程式碼換一種語言。完成有外部、機器可判定的定義,把 done 的判定權交給 harness 沒有損失。Ronacher 用「I have used it with success myself to port MiniJinja to Go」自述這個案例。
非長存產物 · loop 友善
效能實驗、安全掃描、PoC 這類東西本來就不打算長期維護,沒人要去理解它五年後的樣子。loop 在這裡的不知疲倦是純優勢。
長存核心 · 擔憂區
要活很多年、要人持續維護的系統。「完成」沒有客觀終點,於是 harness 只能用「測試還過嗎」之類的代理指標續命任務——每次 iteration 加一層防禦,系統慢慢變得難以理解。
invariant 設計 · 擔憂區
Ronacher 指現在的 model「avoid strong invariants. They add fallbacks instead of making bad states impossible」。長存軟體最需要的恰恰是讓壞狀態不可能的結構,而這是 loop 產出的程式碼傾向繞過的。
分界不是語言或框架,而是「完成」有沒有客觀終點、以及這份程式碼要不要被人理解很多年。
harness 把「完成」搬到外面之後
把機制畫清楚,比較容易看出 Ronacher 的不安從哪裡長出來。下面這張靜態圖是 harness loop 的骨架:work queue → 機器嘗試 → harness 評估 done → 沒過就改 context 續跑。整個結構的重點不在哪一個 box,而在那個判定 done 的菱形——它在 agent loop 外面,由 harness 持有。
把判定權移到外面,對有界任務是把人從「盯著它跑完」解放出來;對長存軟體則是把「什麼叫做好了」這個本來需要人類品味的問題,交給一個只會看代理指標的迴圈。harness 不知道「這段程式碼三年後好不好維護」,它只知道「測試現在過不過」。於是它會一直往「讓測試過」的方向加東西,而不是往「讓系統更容易理解」的方向收東西。Ronacher 對這件事的判斷很直接——這也是全文最該被當成命題、而不是事實去讀的一句。
這裡的關鍵差異不是「機器」對「人」,而是兩種「完成」的定義。一個有經驗的工程師對「做完了」的認知,藏著大量沒寫進測試的東西:這個抽象會不會在下一個需求進來時崩掉、這個邊界條件對不對、半年後接手的人讀不讀得懂。這些都是測試綠燈量不到、卻決定一份長存程式碼長期成本的東西。harness 的 done 判準是可機器執行的,它的代價就是把這一整層隱性品味擠出了迴圈。對 port 這類有 oracle 的任務,這層品味本來就被舊程式碼凍結了,擠掉沒差;對沒有 oracle 的長存核心,擠掉它等於讓系統在「測試一直綠、卻一年比一年難動」的軌道上滑行,而沒有任何一次 iteration 會主動報警。
每次 iteration 加一層小防禦
Ronacher 對現在 AI 程式碼風格的描述是:「Present-day models tend to produce code that is too defensive, too complex, too local in its reasoning.」注意 tend to——這是傾向性陳述,不是「所有 model 都如此」。他接著給出更尖銳的技術判斷:「They avoid strong invariants. They add fallbacks instead of making bad states impossible.」這句很值得一個寫過大型系統的人停下來想:strong invariant 的價值,正在於它讓一整類錯誤狀態在型別與結構層面根本無法被表達;fallback 的代價,是它把錯誤狀態保留下來、然後在運行時補一塊處理。前者讓系統變小、變可證明;後者讓系統變大、變需要被監測。
把這個傾向丟進迴圈裡,就得到全文我認為最強的一句話:「If each iteration adds another small defense, the system slowly becomes less understandable while appearing more robust.」每一次 iteration 單看都合理——多接一個 null check、多包一層 try、多一個防呆——但累積起來,可理解性在掉、表面穩健度在升,兩條曲線往相反方向走。下面這個 hero widget 讓你親手拖 iteration 數,看這兩條線怎麼分岔。它不是 benchmark,是把 Ronacher 那句話的 dynamics 用一個單調模型畫出來:合理的推測是,真實系統的退化不會這麼平滑,但分岔的方向就是他描述的方向。
拖 slider 增加 loop iteration,看可理解性與表面穩健度分岔 · 0–200 次
這裡有個容易誤讀的地方,我得替 Ronacher 補一句也替反方補一句。替他補的:這不是說 AI 寫不出好程式碼,而是說在「harness 用代理指標續命任務」這個結構下,最省力的方向系統性地偏向加防禦、而不是建 invariant。替反方補的:一個有品味的人類在 review 時也會把 fallback 重構成 invariant——所以真正的變數不是 AI 會不會寫 fallback,而是「人還在不在那個 review 的位置上、看不看得懂改了什麼」。文章後半的焦慮,正是這個位置會不會被結構性地擠掉。
就算你不玩,別人會拿它對付你
Ronacher 最讓人不安的一段,是退出可能不是選項:「What's very uncomfortable is that opting out of this fully machine-driven future may not be an option.」他用 may not,是可能性而非斷言,我保留這個語氣。他給的理由不只一個面向,下面這張表把三條壓力對齊,每一條都標出他在文裡的具體依據。
| 壓力來源 | 機制 | Ronacher 的依據 |
|---|---|---|
| 安全攻防 | 攻擊方已經在 loop,防守方不 loop 就跟不上節奏。 | 「Even if you do not use loops to build your software, other people will use loops against your software.」並以 curl 維護者壓力為例(引 Daniel Stenberg 的文章)。 |
| 競爭速度 | 用 loop 的小團隊可以用速度與覆蓋率壓過不用的大團隊。 | Ronacher 論點:raw speed 與 coverage 的優勢正在蓋過傳統品質標準。 |
| 品質標準重定義 | 當「夠快、覆蓋夠廣」成為新門檻,「每行都該由人撰寫」的舊標準被讓步。 | Ronacher 自己也讓步:並非每行程式碼都值得人類作者權,過去也寫過更糟的程式碼。 |
curl 那個例子值得單獨講,因為它把抽象的壓力落到一個有名有姓的維護者身上。Ronacher 寫「Daniel Stenberg's post about curl's summer of bliss is a good example of the pressure maintainers are already under.」——維護者承受的壓力已經是現在式,不是未來式。我的解讀(標明是推測):對一個 OSS 維護者來說,loop 帶來的不對稱很殘酷——產生「看起來像 bug report」「看起來像 patch」的東西幾乎零成本,但判斷它真假、merge 進長存 codebase 的責任全在人這邊。這正好把前面兩個 widget 的論點接起來:loop 擅長產出大量「表面穩健」的東西,而判斷可理解性的負擔留給人。
這個不對稱才是 opt-out 困境真正的引擎。注意 Ronacher 那句安全面的話——「Even if you do not use loops to build your software, other people will use loops against your software.」——它的力量不在「攻擊變多」,而在攻擊與防守的成本結構被掰開了。攻方用 loop,可以把「對你的程式碼反覆嘗試各種輸入、各種路徑」這件事自動化到不睡覺;守方就算完全不碰 AI,也得回應這些湧進來的東西。維護者不是輸在技術,是輸在「我這邊每一個回應都要人來判斷,對面每一次嘗試都不用」。當這個不對稱大到一定程度,「我選擇不用 loop」就不再是一個能單方面成立的選擇——你不下場,對手也會把你拖下場。這就是 Ronacher 說 opting out 可能不是選項的具體機制,而不只是一句修辭。
競爭那一條我覺得是全文最站得住、也最讓人沒退路的論點。它不訴諸「AI 寫得多好」,只訴諸賽局:只要有人靠 loop 跑得更快更廣,不跟的人就承受相對劣勢。這跟程式碼品質好不好無關,純粹是節奏問題——而節奏一旦由能不睡覺的迴圈定義,人類的「我想先看懂再 merge」就成了競爭上的奢侈。
第三條我覺得最弱,因為它其實是 Ronacher 自己讓的一步。他承認並非每行程式碼都值得人類作者權,過去也寫過更糟的程式碼——這個讓步是誠實的,卻也把「品質標準」這個錨點鬆開了。一旦你同意「不是每行都要人寫」,剩下的問題就變成劃線:哪些是可以放手的雜活、哪些是必須守住的核心。麻煩在於這條線本身會移動。當競爭把「夠快夠廣」推成新門檻,原本被劃進「核心、要人懂」的東西,會在一次次趕工裡被重新歸類成「雜活、丟給 loop」。標準不是被誰推翻的,是被一次次合理的讓步慢慢挪走的。我的看法是,前兩條壓力是外生的、你擋不住;第三條是內生的、是你自己同意的——所以也是唯一一條,你還有機會靠刻意劃線守住的。
從機器變成有機體
文章的框架句是把軟體從「確定性機器」重新想像成「有機體」。Ronacher 的核心句是:「We treat it, we monitor it, we stabilize it, but we do not necessarily comprehend it.」治療它、監測它、穩定它,但不必然理解它。工程師不再是讀懂整台機器的人,而更像照顧一個病人——觀察症狀、下假設、做更多測試、試療法、再觀察。
這裡我要替論點挑一個它自己的弱點,因為這恰恰是 Ronacher 誠實的地方。他自己承認這個比喻不是全新的:「Even without LLMs we already diagnose distributed systems somewhat like doctors in that we observe symptoms, form hypotheses, 'order more tests', try some remedies, and observe again.」就算沒有 LLM,我們本來就有點像醫生在診斷分散式系統。這句 somewhat 鬆動了「機器 → 有機體」這個乾淨的二分——任何運維過大型分散式系統的人都知道,「完全理解整個系統」這件事老早就不成立了。所以更精確的說法可能是程度而非種類的轉變:不是從「完全理解」掉到「完全不理解」,而是那條本來就在退的線,被 loop 加速往「不理解」推。把它講成質變,是這篇 essay 修辭上最用力、論證上最該打折的地方。
但程度的轉變一旦過了某個點,後果是質的。Ronacher 把這個後果指向依賴:當系統需要機器持續參與才能維護、而人類已無法獨自讀懂它,「失去那台機器」就變成存亡問題。他用兩個反問把這件事擺上桌:「What if some trade restrictions take away access to the most powerful models? What if just the cost becomes unbearable?」貿易限制拿走最強 model 的存取權怎麼辦?成本變得無法承受怎麼辦?這是提問不是預測——但對一個要為公司技術選型負責的人,這兩個問句該被當成風險登記簿上的條目,而不是科幻。一份只有在持續租用某類 model 才維護得動的 codebase,等於把一個外部、地緣政治可斷的依賴釘進了你最核心的資產裡。
把這條依賴跟前面的競爭壓力疊起來看,會得到一個很不舒服的形狀:競爭逼你用 loop,用 loop 讓 codebase 變得需要 loop 才能維護,於是你對那台機器的依賴只增不減,而退出的成本隨時間複利上升。這不是哪一步不理性,是每一步都理性、合起來把選項收窄。
那把判斷留在哪裡
Ronacher 沒有給藥方,他給的是一個開放式的提問,連文法都是未收尾的,我照引:「Maybe the question is that in a future of loops, how do we don't abdicate judgment, how we can retain rules of good engineering, how we can ensure that responsible human can continue to supervise」。Maybe 開頭——他自己也只是在試探。對一個要在下個 sprint 決定要不要把 agentic loop 接進 production pipeline 的 tech lead,我會把這篇 essay 收斂成三個可操作的判斷。
第一,用他那條二分法分流工作:有客觀終點、且產物非長存的(port、掃描、PoC),放心丟給 loop,這是 Ronacher 親自背書的甜蜜區。第二,對要活很多年的核心,把「人能不能讀懂這次改了什麼」當成 merge 的硬門檻,而不是事後補做——因為 invariant 一旦被 fallback 取代,重新蓋回來的成本遠高於一開始就守住。第三,把「失去某類 model 的存取權」當成真實的供應鏈風險寫進評估,問一句最樸素的問題:如果明天這個 model 用不了,這份 codebase 還維護得動嗎?
The call:Ronacher 沒說 loop 是錯的,他說的是判定「完成」的權力正在從人移到迴圈,而長存軟體最怕的,恰恰是把可理解性換成表面穩健的那種「完成」。值得守住的不是「拒絕 loop」,而是那個「負責任的人類能不能繼續監督」的位置——一旦競爭與依賴的複利把這個位置擠掉,軟體就真的從你看得懂的機器,變成你只能診斷的有機體。