The Coming Loop

Armin Ronacher

迴圈將至

原文由 Armin Ronacher 發布,訂閱此部落格

「我已經不再對 Claude 下提示了。我讓迴圈去對 Claude 下提示、去決定該做什麼。我的工作就是寫迴圈。」

— Boris Cherny

過去幾個月來,我看到越來越多人在 coding agent 之上打造的東西,感覺上已經跟單純使用 coding agent 有著本質上的不同。有些是在 Pi 之上發生的,看到當然很酷!不過各處的模式都是一樣的:工作被放進某種佇列裡,一台機器把它取走、嘗試執行、停下來,然後由某個 harness 來判斷那是否真的是結束。

如果不是,harness 就會延續同一個 session,注入另一則訊息、用修改過的上下文開啟全新的 session,或是把任務交給另一台機器。任務會在模型原本會說:「我完成了。」的那個時間點之後,繼續存活下去。

這個類型的迴圈,我想得比我願意承認的還要多。

每個 coding agent 裡面本來就有一個 agent 迴圈。模型呼叫工具、整合結果、再呼叫另一個工具、讀取檔案、編輯檔案、執行測試,最後產生某個答案。這個迴圈是我們已經熟悉很久的東西。另一個則是 harness 層級的迴圈:也就是 agent 迴圈之外的迴圈。這個迴圈也並非新東西。從早期的 Claude Code 時代開始,我們就一直在做各種版本的嘗試,但這個迴圈在 agentic engineering 中變得越來越常見,最近幾週甚至開始主宰了 Twitter 上的討論。

我還不擅長這個

就目前的狀況來說,對於那些我真正在乎的程式碼——結果發現數量還不少——我用這種工作方式並沒有獲得太多成功。

一部分是品味,一部分是掌控感。我對自己想要的程式碼樣貌設了很高的標準,而且我想理解自己交付出去的程式碼。在壓力之下,或是在與另一個人討論時,我希望能夠說明系統在做什麼,而不必先去問 clanker 請它幫我解釋。現在當然有個問題是,幾年後我是否還會保有這種想要理解程式碼的渴望。但至少到目前為止,我還沒有跨過「理解不再重要」的那個點。

基於這種渴望,我在面對那些不是在我關注下寫出來的程式碼時,總覺得少了些什麼,尤其是來自迴圈產出的程式碼。現階段的模型傾向於產出過度防禦、過度複雜、推理過於局部的程式碼。它們迴避強大的不變條件(invariant)。它們加上備援機制,而不是讓錯誤狀態根本不可能發生。它們複製程式碼、發明糟糕的抽象,並用更多機制去掩蓋不清晰的設計。更糟的是:到目前為止,我幾乎沒看到這方面有什麼進步。如果有的話,在這方面我甚至覺得我們可能正在往錯誤的方向前進。至少以我的品味來說,現在這種放手不管的 harness,像是搭配 ultracode 的 Claude Code,產出的程式碼比我們去年秋天產出的還要差。那是因為以 Fable 為例,Claude Code 會不間斷地在一個問題上工作三十分鐘甚至更久,而過去這個過程會有更多人在迴圈中參與。

此外,眾所周知模型傾向於觀察到某個局部的失敗,就加上一個局部的防禦。Karpathy 曾提到它們「對例外情況有著致命的恐懼」。在具有重要不變條件的系統中,尤其是持久化的資料格式或核心基礎設施,正確的修復方式不是「處理每一種格式錯誤的狀況」。正確的修復是讓格式錯誤的狀況根本無法被表示、或一開始就無法被寫入。然而,即使經過大量的人工引導,這類程式碼也不會自然地從 LLM 中產生,而且就算程式碼真的自然地寫成那樣,它們還是會試圖去處理那些現在已經不可能發生的錯誤。

當你把這種行為放到迴圈背後,就會傾向於放大它。如果每次迭代都加上另一個小小的防禦,系統會慢慢變得越來越難理解,同時看起來卻越來越強健。你越是放手不管,這種情況就越嚴重。當這類工具在沒有明確指引的情況下交給資淺工程師時,也會教會他們非常糟糕的習慣。因為如果你問他們為什麼要那樣做,他們會很有說服力地為自己的做法辯護。

迴圈有效的領域

同時,假裝迴圈模式不起作用是不誠實的,因為它在某些領域已經展現出驚人的效果。

程式碼移植就是其中之一。已經有令人印象深刻的大型自動移植案例,包括據報導將 Bun 部分從 Zig 移植到 Rust 的工作。我自己也曾成功地將 MiniJinja 移植到 Go。效能探索是另一個運作得非常漂亮的案例。機器可以嘗試各種實驗、進行基準測試、丟棄失敗的嘗試,並持續搜尋。安全掃描也很自然地適用,幾乎任何類型的研究都是如此:要求系統去探索一個複雜的問題空間並回報結果,而不一定需要提交具有持久性的程式碼。這些案例的共同點在於,它們要不是不產生新的程式碼、而是轉換既有的程式碼,要不就是產出的程式碼本來就不打算長期存在。它們產出的是概念驗證或點子、呈現發現,或更接近於機械式的轉換。

我認為,產出不需要長久存在的產物、或是創造某種可被清楚驗證的機械式轉換的迴圈,比起 harness 能否機械化地衡量目標的一般能力更為重要。許多成功的迴圈應用會用另一個 LLM 來擔任評判或協調者。機械式轉換的案例可以用二元的測試案例來驗證,但也可以改由 LLM 來評判!

舉例來說,Claude Code 越來越擅長建立整套實驗性工作流程並加以執行。當然,它產出的程式碼是 slop,但那更多是模型的問題,而不是 harness 無法判斷工作流程中的某個步驟是否帶來了淨改進或已完成。

harness 只需要一個能讓它繼續下去的訊號。它不一定得是客觀或二元的,只要有用處、足以驅動下一次迭代就好。

我已經非常喜歡那些能幫我拿掉日常工作中枯燥部分、幫我做實驗與測量並給我點子的迴圈了。

軟體作為有機體

另一方面,用同樣的迴圈方法來撰寫需要長期維護的程式碼,至今仍讓我感到不太自在。我喜歡用的一個比喻是,從「軟體作為確定性機器」轉向「軟體作為有機體」。

我成為軟體工程師的環境鼓勵我去理解機器。總是還有一層可以剝開來加深理解。那些沒有展現出確定性可觀察行為的機器或許會被接受,但一般來說被視為不夠理想。就軟體架構而言,我認為往更確定性的方向推進是可取的,而不是相反。同樣地,能夠理解程式碼一直是無可否認的目標。在實務上雖然不一定總能做到,我們仍以寫出讓即使是新進工程師也能透過巧妙的架構來駕馭複雜程式碼庫的程式碼為榮。在設計良好的系統中,總會有工程師知道不變條件在哪裡、哪些部分是承重的、哪些改動是安全的。理想上,這一切也都有完善的文件記錄。在缺乏這種理解的地方,通常會被視為需要改進之處。

顯然,這個理想一直都很緊繃。許多軟體系統,尤其是非常成功的那些,曾有過團隊中的工程師能夠保持其整潔的時期。大型軟體系統常常過於龐大、過於動態、也過於依賴外部服務,以至於無法裝進任何一個人的腦袋裡。即使沒有 LLM,我們診斷分散式系統的方式也已經有點像醫生:觀察症狀、形成假設、「安排更多檢測」、嘗試一些補救措施,然後再次觀察。

然而有了 LLM,我們正朝那個方向推得更遠、也更快。我們用它們來寫程式碼,也用它們來做診斷與補救。已經有不少工程師活在這樣的世界裡:線上問題發生後的第一步,就是讓 clanker 去讀日誌、提出根本原因並主動送出一個修補程式。產生的修補程式接著常常又被另一台機器接手審查,有時甚至在沒有任何人為監督的情況下就直接合併到 main 分支上。

顯然那很強大,我也無法否認它聽起來很吸引人。但屈服於這個想法,特別是在人為監督越來越少的情況下,意味著接受我們可能不再以同樣的方式理解整個系統。我們治療它、監控它、穩定它,但不一定真正理解它。

我毫不懷疑,對某些軟體來說,這樣是可以的。不是每一行程式碼都值得由人來撰寫,而且過去也可能寫出過更糟的程式碼。

但我希望所有軟體都以這種方式被創造嗎?

你無法完全選擇退出

非常令人不安的是,選擇退出這個完全由機器驅動的未來,可能根本不是一個選項。

安全是今天最清楚的例子。即使你不用迴圈來建構軟體,其他人也會用迴圈來對付你的軟體。攻擊者會持續不斷地跑機器,即使不是攻擊者,資安研究人員也會這麼做,而這些自動化工作中的一部分會揚起塵土,但也會找到真正的問題。而無論是訊號還是雜訊,都會以一種除非你自己也丟一台機器去處理,否則幾乎無法應對的量級向你襲來。

Daniel Stenberg 關於 curl Summer of Bliss 的文章,就是維護者已經承受的壓力的一個好例子。據我所知,AI 在今天 curl 的核心開發中並沒有扮演非常大的角色。然而即便如此,維護者們還是被大量的回報淹沒,其中大多數現在都是 AI 產生的。

如果攻擊者和回報者都在跑迴圈,防禦者最終也需要跑迴圈才能跟上。或許不是直接去寫修補程式,也許只是為了分類和重現問題,但壓力將會越來越大。

在競爭上也是如此,有些團隊會透過純粹的速度超越其他團隊。有些專案會突然進展得更快,因為一小群人摸索出了如何有效協調機器。有些新創公司用五個人就能完成過去需要五十個人的工作。有些人可能真的會用一台機器對著你的產品跑迴圈,並要求它「把它做得跟另一個一樣」。而如果他們的使用者很滿意,那真的有差嗎?

並非所有軟體都會受到同等的影響。有些領域會懲罰草率並要求信任與責任,但有大量的軟體活在一個純粹速度、快速實驗和廣泛覆蓋至關重要的世界裡。

建立新的依賴

對我來說最可怕的部分是,我們正以新的方式依賴這些新機器。軟體一直都依賴工具。我還記得以往必須付費購買編譯器的日子。這些新工具讓人回想起那個創造軟體伴隨著實質成本的時代。但現在它不再是一次性的付費,而是一種持續的依賴。不只是對荷包飽滿的依賴,也是認知上的依賴。

如果一個程式碼庫是由迴圈產生的、由迴圈審查的、由迴圈修補的、並由迴圈維持運作的,那麼當你不再能存取同等級的系統時會發生什麼?當某些貿易限制讓你無法使用最強大的模型時會怎樣?如果成本變得無法負擔呢?如果你的團隊只是失去了在不使用機器的情況下理解程式碼的最後一點能力呢?

我們可能會創造出一些程式碼庫,它們不僅僅是難以由人來維護,而是其維護模式本身就假設了機器的參與。這已經正在發生了!它並非到處都在發生,甚至可能不是以被視為有問題的方式發生,但我們看到的越來越多。人們越來越常合併他們無法完全解釋的程式碼。人們失去了在不靠 clanker 提供的上下文來補強或改寫訊息的情況下,建立 issue 回報或在聊天中討論事情的能力。越來越多的人依賴機器來幫他們總結或提供脈絡。我越來越常遇到透過 LLM 的間接方式來與我對話的人。

再說一次,或許那甚至不一定是錯的,但這是對我們做事方式的巨大改變。

未來的 Harnesses

我毫不懷疑這就是事情發展的方向,但要走向那裡,我們需要在各處的工具上採取行動,而不只是在 coding agent 上。

光是協調更多的迴圈是不夠的。更好的變更視覺化、協調或 agent 也不會恢復我們的理解。我們要不是需要找到聰明的方法把人猛然拉回迴圈中、並讓迴圈的變更在長期上變得可讀,要不就是需要找到更好的方法來組合這些越來越複雜的系統。

這也是我對 Pi 角色的思考正在改變的地方。Pi 一直很謹慎,我認為這種謹慎是好的。我不想要一個未來,讓每一次互動都變成一群不受控的機器在做出我無法跟上的變更。我不希望 Pi 為了贏得邁向自我撰寫軟體的競賽而變成一個無法維護的爛攤子,我也不希望 Pi 去推崇這類型的工程方式。同時,Pi 是一個 harness,而 harnesses 正處於人們運行這些新型實驗的核心。

用於程式設計任務的工作佇列、agent 的協調、subagent、持久化的 session 將會變得越來越重要。即使是我們這些心存保留、並未盲目擁抱迴圈的人,也將不得不開始做這些實驗。我們必須這麼做,因為我們需要理解如何讓這個未來變得可控且可存續。

控制迴圈

正如你從這篇文章中所讀到的,我對這個未來感到非常不安。不是出於恐懼,而是基於迄今為止與這項技術相處的經驗所帶來的謹慎。

採用 harness 迴圈的想法意味著由 harness 來決定工作何時完成。在 agent 迴圈中,模型最終會說「完成」,然後由我來審查。甚至在那之前,我通常會在一路上進行引導。我有參與其中,而且我享受一路學習的過程。在由 harness 運作的迴圈中,我甚至不確定自己的角色是什麼。就連「完成」的訊號也失去了所有意義,只是被傳達給另一個負責評判的機器。我的角色被縮減成一個傳話者。

今天,我不太喜歡從以這種方式建構的系統中看到的程式碼,我也不太喜歡與太多用 AI 輔助建構的軟體互動。迴圈很強大,但它越來越剝奪了責任感,而且至少在今天,它非常鼓勵我們向機器投降。

然而,儘管我現在對此感到抗拒,我毫不懷疑這個迴圈的未來將會是我們的未來。我已經看到小得驚人的團隊以不可思議的速度在建構,也看到程式碼庫越來越變成晦澀、混亂、只能由更多機器來診斷的有機體。那些程式碼庫同時是有用的,也是一團亂。

所以我想我正在接受,問題不在於我們是否會進入迴圈,因為顯然我們會。或許問題在於,在一個充滿迴圈的未來,我們如何不放棄判斷力、如何保留良好工程的原則、如何確保負責任的人能夠繼續監督、以及我們需要如何重新思考架構程式碼的方式,才能在過程中保持理智。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言