<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hant"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://blog.aihao.tw/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.aihao.tw/" rel="alternate" type="text/html" hreflang="zh-Hant" /><updated>2026-08-06T07:54:18+00:00</updated><id>https://blog.aihao.tw/feed.xml</id><title type="html">愛好 AI 工程 Blog</title><subtitle>分享 LLM 應用開發的好內容</subtitle><author><name>ihower</name></author><entry><title type="html">「很難 eval」是產品設計的警訊: 讓 AI 輸出可驗證、可信任的 UX 設計</title><link href="https://blog.aihao.tw/2026/08/05/eval-smell-verifiable-ux/" rel="alternate" type="text/html" title="「很難 eval」是產品設計的警訊: 讓 AI 輸出可驗證、可信任的 UX 設計" /><published>2026-08-05T00:00:00+00:00</published><updated>2026-08-05T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/08/05/eval-smell-verifiable-ux</id><content type="html" xml:base="https://blog.aihao.tw/2026/08/05/eval-smell-verifiable-ux/"><![CDATA[<p>做 AI 產品的團隊常常說: 「我們的輸出很難做 eval」。AI Evals 課程講師 Hamel Husain(ihower 也上過這門課)最近這篇 <a href="https://hamel.dev/blog/posts/eval-smell/">“It’s Hard to Eval” Is a Product Smell</a> 給了一個直接的回應: 這句話本身就是警訊。借用 code smell 的講法，「很難 eval」是一種 product smell: 問題通常不在 eval 技術，而在產品設計。</p>

<p>他的理由一句話就能講完: 「你自己很難驗證的產出，使用者通常也很難驗證。」你做 eval 時卡住的地方，就是使用者每天用你產品時卡住的地方。所以與其問「這要怎麼 eval」，不如回頭把產品重新設計成「容易驗證」。</p>

<p>小編覺得這篇對 AI 產品的 UX 設計很有啟發。降低幻覺、提升正確率當然是基本功，但錯誤率永遠不會是零: 就算輸出 95% 是對的，使用者分辨不出哪 5% 是錯的，還是不能放心用。所以除了「讓模型少出錯」，「讓使用者能快速驗證、建立信任」的 UX 也同樣重要。以下先整理 Hamel 的三個案例(Hamel 也錄了<a href="https://www.youtube.com/watch?v=0ASUz7gFRcw">影片版</a>，逐一 demo 每個案例的 UI 設計)，再擴大搜尋補幾篇相關文章一起看。</p>

<h2 id="為什麼驗證變成了瓶頸">為什麼驗證變成了瓶頸</h2>

<p>Hamel 點出一個關鍵變化: 在 AI 之前，驗證是在「做出工作成果」的過程中順帶完成的。自己寫的分析，每個數字怎麼來的自己都清楚。但 AI 讓產出瞬間完成，驗證卻沒有跟著變快，於是驗證成了整個工作流程中最花時間的一步。</p>

<p>這代表 AI 產品設計的重心也得跟著調整: 從「怎麼生成最好的輸出」，轉到「怎麼讓人最快確認輸出可不可信」。</p>

<h2 id="案例-1-資料分析-agent從只給答案改成附上驗證路徑">案例 1: 資料分析 Agent，從「只給答案」改成「附上驗證路徑」</h2>

<p>如果資料分析 Agent 只丟出「淨收入 $4.21M」這種最終答案，資料科學家不會直接採信: 要確認這個數字，等於把整個分析從頭再做一次。</p>

<p>Hamel 先整理資深資料科學家實際上怎麼驗證一個指標: 跟已驗證的儀表板或同事做過的分析對照、確認指標定義、對相關數字做合理性檢查、看聚合數字底下的明細、直接讀 SQL、標記無法驗證的部分。</p>

<p>好的設計就是把這些驗證動作直接做進產品。Hamel 拿 <a href="https://hex.tech/">Hex</a> 的 chat + notebook 雙介面當例子: 聊天介面給結論和圖表(本文封面圖)，旁邊的互動式 notebook 完整呈現分析過程，假設、指標定義、SQL 查詢、中間結果都可以逐一檢查; 數字對照過哪份已驗證分析、作者是誰，都標出來; 沒有可信來源可對照的項目，主動列成清單:</p>

<p><img src="/assets/images/eval-smell-hex-notebook.png" alt="Hex 的 notebook 介面，chat 面板在右側同步顯示" />
<small>圖片來源: <a href="https://hamel.dev/blog/posts/eval-smell/">Hamel 原文</a></small></p>

<h2 id="案例-2-課程生成器從生成改成檢索--diff">案例 2: 課程生成器，從「生成」改成「檢索 + diff」</h2>

<p>一個幫體育老師生成課程計畫的工具，從零生成 50 頁的完整計畫，老師得整份讀完才能放心用，跟自己寫一份差不多累。</p>

<p>Hamel 的解法是換一個問題來問: 不問「這 50 頁要怎麼 eval」，而問「老師實際在乎什麼」。答案是: 看到類似學校的老師已經在用這份計畫，比什麼都有說服力。</p>

<p>於是產品改成: 檢索一份已經被其他學校用過、驗證過的課程計畫(標明原作者、多少學校在用、跑過幾個學年)，再根據這位老師的條件客製修改，並把改動用 diff 標出來、附上修改原因。老師要審的東西，從 50 頁變成幾個修改。</p>

<p>這個設計還有一個附帶好處: 產品本身變簡單了。比起把幾百個範例放進 prompt 從零生成，檢索最接近的已驗證範本再改寫，工程上反而更容易做對。</p>

<h2 id="案例-3-醫療報告從報告生成器變成研究助手">案例 3: 醫療報告，從「報告生成器」變成「研究助手」</h2>

<p>工傷保險的醫療意見書動輒 50 頁以上。如果工具直接生成整份報告，醫生要驗證每一條主張，等於把病歷重讀一遍，省不到什麼時間。</p>

<p>改法是調整工具的角色: 不要一開始就生成報告，先當研究助手，從病歷中提取相關事實，每一條都附上原始頁面的連結; 不同檢查結果有矛盾的地方特別標出來; 缺影像、缺資訊的開放問題也列出來。醫生逐條確認過事實之後，才從這些已驗證的事實生成最終報告。</p>

<p>這個流程讓信任可以逐步累積，驗證單位也變小了: 醫生不用一次判斷「整份報告可不可信」，而是判斷「這條矛盾是不是真的」「這個引用有沒有支持這個主張」這種小問題。</p>

<h2 id="驗證導向設計-四個問題">驗證導向設計: 四個問題</h2>

<p>Hamel 把三個案例整理成通用的設計提問:</p>

<ol>
  <li>使用者實際需要檢查什麼?</li>
  <li>他們手上有什麼可信的東西可以對照?</li>
  <li>領域專家平常用什麼訊號或捷徑來判斷可不可信?</li>
  <li>輸出能不能拆成可以個別接受、編輯、拒絕的小單位?</li>
</ol>

<p>貫穿三個案例的手法也很一致:</p>

<ul>
  <li>「溯源」(provenance): 輸出的每個部分都標明從哪來，附上可以點進去看細節的連結。Hamel 說這是讓輸出變得可檢查最快的方法</li>
  <li>「漸進揭露」(progressive disclosure): 初期把來源和假設清楚展示出來，幫使用者建立信任; 等信任建立之後，這些細節再改成預設收合、需要時展開</li>
  <li>拆成小單位: 不要求使用者一口氣評斷整個輸出，而是拆成可以個別接受、編輯、拒絕的部分</li>
</ul>

<p>Hamel 還特別提到: 連 coding 這種最容易驗證的領域(有測試、型別、diff)，Cursor 和 Devin 都還會多錄一段 UI 操作影片，讓你不用自己重現就能確認改動有效。連條件最好的領域都做到這個程度了，難驗證的領域更沒理由不做。</p>

<h2 id="從-ux-視角看-除了降低錯誤率還要縮短發現錯誤的時間">從 UX 視角看: 除了降低錯誤率，還要縮短發現錯誤的時間</h2>

<p>把這篇跟其他幾篇談 AI 產品 UX 的文章放在一起看，會更清楚「可驗證」為什麼是核心。</p>

<p>Karpathy 在 <a href="https://www.youtube.com/watch?v=LCEmiRjPEtQ">Software Is Changing (Again)</a> 演講裡把人跟 AI 的協作描述成「生成與驗證的迴圈」: AI 負責生成，人負責驗證，這個迴圈轉得越快越好。他認為 GUI 的價值就在這裡: 讓人用視覺快速稽核 AI 的工作，例如 Cursor 的紅綠 diff、Perplexity 的引用來源。這跟 Hamel 講的是同一件事: 縮短驗證那一半的時間，跟提升生成品質一樣重要。</p>

<p>Google 官方的 <a href="https://pair.withgoogle.com/chapter/explainability-trust/">People + AI Guidebook</a> 在 Explainability + Trust 這章把目標講得很清楚: 設計的目的不是讓使用者「盡量信任 AI」，而是幫使用者「校準信任」(calibrate trust): 該信的時候信，該存疑的時候存疑。過度信任和過度懷疑都是設計失敗。溯源、模型的信心程度、解釋，都是幫使用者校準信任的工具。</p>

<p>另外社群也開始把這類做法整理成 pattern library，像 <a href="https://www.shapeof.ai/patterns/citations">Shape of AI</a> 收錄了 Citations 等 AI UX 模式，做產品時可以當 checklist 翻一翻。</p>

<h2 id="小結">小結</h2>

<p>「很難 eval」和「使用者不信任輸出」是同一個問題的兩面。把產品設計成可驗證，好處是一連串的: 使用者驗證成本下降，才會真的把工作交給它; 標註成本也跟著下降，eval 的訊號品質變好。下次團隊再說「我們的輸出很難 eval」，先別急著研究更強的 LLM judge，回頭檢查一下: 產品是不是把使用者當成「只需要一個答案」的人。</p>]]></content><author><name>ihower</name></author><category term="Eval" /><category term="Product" /><summary type="html"><![CDATA[做 AI 產品的團隊常常說: 「我們的輸出很難做 eval」。AI Evals 課程講師 Hamel Husain(ihower 也上過這門課)最近這篇 “It’s Hard to Eval” Is a Product Smell 給了一個直接的回應: 這句話本身就是警訊。借用 code smell 的講法，「很難 eval」是一種 product smell: 問題通常不在 eval 技術，而在產品設計。]]></summary></entry><entry><title type="html">Hex 怎麼做資料 Agent: 難的不是寫 SQL，是沒辦法驗證答案對不對</title><link href="https://blog.aihao.tw/2026/08/05/hex-data-agents-izzy-miller/" rel="alternate" type="text/html" title="Hex 怎麼做資料 Agent: 難的不是寫 SQL，是沒辦法驗證答案對不對" /><published>2026-08-05T00:00:00+00:00</published><updated>2026-08-05T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/08/05/hex-data-agents-izzy-miller</id><content type="html" xml:base="https://blog.aihao.tw/2026/08/05/hex-data-agents-izzy-miller/"><![CDATA[<p>LangChain 開了一個新 podcast 叫 Max Agency，主持人是 LangChain CEO Harrison Chase，第一集找來 <a href="https://hex.tech/">Hex</a> 的 AI 工程師 Izzy Miller，聊他們怎麼做資料 Agent: <a href="https://www.youtube.com/watch?v=Xyh1EqcjGME">How Hex builds AI agents that reason like human data analysts</a>。</p>

<p>先介紹一下 Hex 這家公司。它做的是<a href="https://hex.tech/product/notebooks/">給資料團隊用的協作式 notebook</a>: SQL、Python、文字、圖表混在一格一格的 cell 裡，做完的分析可以直接變成互動式的資料應用給別人用。他們在大家都還沒開始討論 agent 之前，就已經把資料 agent 做成產品上線了，<a href="https://hex.tech/blog/introducing-notebook-agent/">Notebook Agent</a> 是他們最主要的一個。</p>

<p>這集聊到: 哪些當年引以為傲的工程現在反而在拖累 agent、工具太多要怎麼收、不會 SQL 的商業使用者要怎麼驗證答案、使用者的 memory 為什麼會跟資料治理打架、為什麼公開的資料 benchmark 大多不能用、一個「所有模型都答錯」的 eval 題目長什麼樣、1M context 該多早壓縮，還有他自己做的一套 90 回合模擬 benchmark，測 agent 會不會越用越好。</p>

<p>以下是重點整理:</p>

<h2 id="1-從一格-cell到一整本-notebook">1. 從「一格 cell」到「一整本 notebook」</h2>

<p>Hex 大概是第一個把 text-to-SQL 做進真實付費產品的公司，那時候底下跑的還是 GPT-3.5 turbo。但接下來一年多，他們的 AI 功能全部被限制在一個 cell 之內: 你打開一個 SQL cell，描述你要的查詢，查詢就出現，如此而已。</p>

<p>Izzy 說這種 single-shot 模式在資料分析「特別受詛咒」，因為資料工作本質上就是迭代的: 你拿到一個答案，第一反應通常不是結束，而是「喔有意思，那我想再看看另一個切面」。使用者心裡明明有一個很大的問題，卻只能拆成一小塊一小塊丟給 AI。</p>

<p>後來他們在 notebook 旁邊加了一個 sidebar，把使用者在 notebook 裡能做的事全部開放給 agent。<strong>改變的核心就是從「產生一格 cell」變成「一次產生一整串 cell」</strong>: 客戶說「我們剛上線一個新功能，幫我做一份報告看有存取權的使用者反應如何」，agent 工作 20 分鐘，交回來的不只是那個問題的答案，還包含一路上自己冒出來、也順手查完的子問題。</p>

<p>「內部一放出來，所有人都瘋了，大家心想: 天啊，就是這個。」</p>

<p>他們的目標從一開始就很明確: Hex 是一個複雜的專業工具，有上百個按鈕和功能，能不能什麼都不改、不加任何新功能，只是讓這一切都能透過自然語言存取?</p>

<h2 id="2-跟-coding-agent-的差別-資料工作沒有可驗證的獎勵">2. 跟 coding agent 的差別: 資料工作沒有可驗證的獎勵</h2>

<p>主持人問資料 agent 和 coding agent 有什麼異同，Izzy 講了兩件事。</p>

<p>第一是互動的節奏不一樣。現在寫程式越來越像「你先把計畫寫清楚，它去執行」: 你寫 spec、它建出來、你看結果，基本上就是 yes 或 no。但做資料分析的過程中有很多決策點，你在那些點上要說的不是「做得好/做不好」，而是「喔這有意思，要不要換個角度看?」「如果把這個維度拆開，趨勢會不會不一樣?」這種工作完全塞不進「提問、回答、結束」的框架裡。</p>

<p>第二是驗證。「寫程式越來越是一個可驗證的任務，這就是為什麼模型在這件事上進步這麼快，因為可以用可驗證的獎勵去訓練。」你說要一個有五個按鈕的 dashboard，它做了，五個按鈕都在、都能按，就算背後的程式碼是一堆爛東西，任務也算正確完成。</p>

<p>資料就沒這麼清楚。那些決策點的結果非常難驗證，光是「怎麼確認這個答案是對的」本身就是個大問題，然後才輪到「那你要怎麼訓練模型做好這件事、怎麼在 harness 裡用它」。他認為 agent 取代 single-shot 只解決了資料 AI 這個難題的前半段，驗證是還沒解開的後半段。</p>

<h2 id="3-五個-agent-長得越來越像-capabilities-是怎麼拆的">3. 五個 agent 長得越來越像: capabilities 是怎麼拆的</h2>

<p>Hex 現在有好幾個 agent:</p>

<ul>
  <li><strong>Notebook Agent</strong>: 給技術使用者，直接在 notebook 裡寫 SQL / Python cell</li>
  <li><strong>Threads</strong>: 給只想問一個問題、拿一個答案的自助使用者。介面比較像 ChatGPT 對話，程式碼被藏起來，但過程中一樣會冒出可以互動、可以往下探索的資料圖表。Izzy 說它的流程其實不是「問題、答案」，而是「問題、一大堆工作、可能還追問、岔出去探索、最後才是答案」</li>
  <li><strong>語意模型 agent</strong>: 幫你在 Hex 裡寫語意模型 (輸出是 YAML)。重點不在它會寫 YAML，而在它會去工作區裡到處翻: 讀別人的 notebook、讀別人的 Threads 對話，把大家實際在用的算法反過來補進語意模型</li>
  <li><strong>資料應用的問答 agent</strong>: 你用 notebook 做出來的資料應用，也能像 Threads 那樣對它問問題</li>
</ul>

<p>一開始這幾個是各做各的獨立系統，於是使用者的抱怨就來了: 為什麼 Threads 不能寫 Python，但 Notebook Agent 可以? 為什麼語意模型 agent 可以讀我其他專案，Notebook Agent 不行? 既然介面看起來都差不多，使用者就預期能力也一樣。</p>

<p>所以他們現在在做的，是把這些能力拆成可以共用的模組，內部叫 <strong>capabilities</strong>。一個 capability 裡面打包了四樣東西:</p>

<ol>
  <li>這個能力要用的<strong>工具</strong></li>
  <li>相關的<strong>靜態 context</strong> (固定不變的說明、規範)</li>
  <li>相關的 <strong>prompt</strong> 片段</li>
  <li>一些很瑣碎但很難調的<strong>行為設定</strong>，例如「最後一輪該怎麼收尾」</li>
</ol>

<p>用小編的話講，capability 就是一個可以掛上去的功能模組。要讓 Threads 也會寫 Python，就是把那個模組掛給它，而不是在 Threads 裡重寫一份。</p>

<p><strong>那現在到底還有沒有分不同的 agent?</strong> 有。Izzy 說目前「不同的 agent 掛不同的 capability 組合」，還是分開的東西。但他加了一句「目前是這樣啦」，因為他們正在試當這些組合變得幾乎一樣的時候會發生什麼事。他對整體方向講得很直接: 這幾個 agent「基本上都在往變成同一個東西的方向走」，而且「我們的程式碼庫現在非常在變動中」。</p>

<p>所以比較準確的描述是: 底層正在收斂成一套共用的 capability 模組，對外還是不同的入口 (notebook 的 sidebar、聊天介面、資料應用)。他說最未定、也最有意思的反而是 UI/UX 那一層: 如果底層能力都一樣了，這些入口還需要長得不一樣嗎?</p>

<p>第 4 項是 Izzy 特別點出來的，因為它最難調: agent 怎麼知道該收尾了? 我們要讓它跑多久? 他們今天還是有一個硬性的迭代次數上限，撞到上限就強迫它結束。他自己並不喜歡這個設計: 「我希望根本沒有最後一輪，我想讓它一直跑下去。」</p>

<p>至於主迴圈本身，主持人問「最簡單的 agent 就是一個 LLM 在迴圈裡呼叫工具，你們基本上就是這樣嗎?」Izzy 說基本上就是。他們把 context 分成動態 (當下的專案狀態、倉儲 schema) 和靜態兩類，而真正有工程含量的是「context 採集」這條管線，不是那個迴圈。</p>

<p>他們也有 skill: 「我們叫它 guides，但完全比照 skill 的做法，有 progressive disclosure。」agent 執行時看得到工作區裡所有可用 guide 的清單，需要時才取出來讀。</p>

<p>底層他們自建 orchestrator。去年在使用者完全沒感覺的情況下，他們把整套系統從 single-shot、批次佇列導向的架構搬到 Temporal，換成真正的長時間工作流編排。「是個很大的工程，最糟的是你得同時維護兩套。」</p>

<h2 id="4-為模型缺陷寫的補償系統新模型上來就變成技術債">4. 為模型缺陷寫的補償系統，新模型上來就變成技術債</h2>

<p>Hex 裡每個東西 (cell、專案、資料連線) 都有一個又長又唯一的靜態 ID。早期的模型只要看到的 ID 一多就會開始幻覺: 編出不存在的 ID、或把兩個 ID 對調。症狀是 notebook 超過五六十個 cell，agent 就開始亂來。</p>

<p>所以他們寫了一套很複雜的系統，在短參照和真實 ID 之間做映射。這套東西後來變成 agent 的核心基礎之一，也一直有 bug 要修。</p>

<p>「就在這禮拜，有人跑了一次 eval，然後說: 欸，我們不需要這個了。」</p>

<p>「有相當多的東西，是我們當初蓋出來很自豪、而且當時確實必要的，現在反而在拖累 agent。」光是那一週就還有大概五個類似的例子。他給的規模感很具體: 五個這種補償設計，跟著模型演進一路調整很容易; 五百個就完全不是這回事。</p>

<p>一年前正是因為自建 harness，他們才能在那麼底層的位置補上模型的缺陷，把 Notebook Agent 做出來。但現在它就是技術債，而技術債一旦存在就很難清掉。</p>

<h2 id="5-工具太多怎麼收-三個做法">5. 工具太多怎麼收: 三個做法</h2>

<p>主持人問他們有多少工具。「工具定義加起來不到 10 萬 token，但差不多就是那個量級了，太多。講清楚一點: 工具真的太多，我不引以為傲。」</p>

<p>Izzy 提到三條路。</p>

<p><strong>一、合併重複的工具。</strong> 他們本來有 create chart cell、update chart cell、delete chart cell 這種切得很細的一整組工具，每一種 cell 類型都配一套增刪改。「我做了個測試、跑了個 eval，發現可以全部合併成一個 chart 工具，效果一樣好。」他把這種切法叫做「沒有意義的工具膨脹」: 合併掉不會損失什麼，只是他們還沒空全部做完。</p>

<p><strong>二、不要在每個工具裡重複同樣的說明。</strong> 這是很多人都會遇到的問題: 同一句注意事項，要不要在每個工具的描述裡都重複一次? 寫在 system prompt 裡行不行? 只寫在其中一個工具裡，跟寫在全部工具裡一樣好嗎? 他的實測答案是「算是吧」: 可以省，但省了會有一點折損，而且不跑 eval 你根本不會知道折損多少。</p>

<p><strong>三、tool search。</strong> 把工具定義從常駐 context 移出去，agent 需要時再檢索。他說這在主流 coding agent 上已經是必需品了，因為裝了一堆 MCP 之後，Claude Code 理論上可以用上千個工具。Hex 導入之後，context 的壓力小了很多。</p>

<p>他也點出一個結構性的原因: 命令列上的 coding agent 什麼都可以用 bash 解決，所以工具數量天然就少; 但 Hex 這種產品沒有這個選項，每個功能都得包成一個工具，所以特別容易忍不住一直加。</p>

<h2 id="6-明明可以跑任意-python為什麼還要做專門工具">6. 明明可以跑任意 Python，為什麼還要做專門工具?</h2>

<p>Notebook Agent 跑在類似 IPython kernel 的環境裡，可以執行任意 Python。既然如此，為什麼還要特地做一個「檢查某個套件有沒有裝」這種小工具?</p>

<p>Izzy 給了兩個理由。</p>

<p><strong>第一，專門工具可以夾帶使用時機的指示。</strong> 「什麼時候該用這個工具、什麼時候該採取這個行動」這些話，在「這裡有一個 Python 工具，自己看著辦」的設計裡是沒地方寫的。工具的描述欄位本身就是最自然的行為引導位置。</p>

<p><strong>第二，決定這段程式碼要不要被使用者看到。</strong> 這是資料產品特有的問題。Notebook Agent 寫的程式幾乎都會變成 notebook 裡的一格 cell，使用者看得到、可以驗證、事後也能拿去改。</p>

<p>但有一個例外: 他們給了 agent 一個叫 <strong>ephemeral SQL 的工具</strong>，用它跑出來的查詢不會變成 cell，跑完就丟掉，使用者也看不到。</p>

<p>為什麼要留這個例外? 因為回答一個資料問題之前，通常得先搞清楚一堆瑣事: 這張表真的有我要的欄位嗎? 我得 join 哪兩張表? 這個欄位裡的日期是什麼格式?</p>

<p>這些事有兩種問法。一種是邊做邊試: 丟一個查詢、拿到錯誤、發現不對、再改、再改。但這樣使用者就得看著 notebook 裡同一格 SQL 被改五次。另一種是讓 agent 先在背景跑一連串很小的探索性查詢，資訊蒐集夠了再一次把主查詢寫對，然後才放進 notebook。Hex 選第二種，因為對使用者來說體驗好很多。</p>

<p>代價是兩個很典型的副作用:</p>

<p><strong>副作用一: agent 開始口說無憑。</strong> 使用者問一個簡單問題，agent 私下跑了個 SQL 就直接把答案講出來。使用者說「圖呢? 證據呢?」agent 說「相信我」。</p>

<p><strong>副作用二: 模型會在背景查到失控。</strong> 現在的模型拿到可以私下查證的能力之後，會很想在回答你之前先確定自己是對的。「特別是 GPT-5 系列。你問 GPT-5.4 一個問題，如果那是個錯的問題、而且結果很複雜，看它當下的狀況，它可能會先跑 50 個 ephemeral SQL 查詢確認清楚，才開始做真正的工作。」</p>

<p>所以這節的取捨很清楚: 給一個萬用的 Python 工具最有彈性，但你就失去了在工具這一層管理「什麼時候該做什麼」和「什麼該給使用者看」的能力。Hex 選擇多做一些專門工具，換這兩件事的控制權。</p>

<h2 id="7-讓使用者跟著看-agent-工作可能撐不了多久">7. 讓使用者跟著看 agent 工作，可能撐不了多久</h2>

<p>Izzy 對那種 agent 思考時顯示的可愛碎碎念文案很有意見: 「我在公司大概被當成那種愛耍寶的人，而我最像 Grinch 的一個意見，就是我討厭那個 noodling 的小東西。」內部有人做過一版，被他擋掉了。他們的做法很單純: 執行中就寫「thinking」並展開它正在做什麼，做完就收合起來。這其實就是 Hamel Husain 講的<a href="/2026/08/05/eval-smell-verifiable-ux/">漸進揭露</a>: 需要建立信任的時候攤開，信任建立後就收起來。</p>

<p>但這節真正的觀點在後面: <strong>他認為「把過程展開給使用者看」這件事，很快就不需要了。</strong></p>

<p>工程師其實已經不在乎過程了，「手離開鍵盤，讓它跑 50 分鐘，只要做對我不在乎它做了什麼。」他自己用 Codex 是直接把輸出關掉的。商業使用者目前還比想像中更想跟著看，但他預測這是那種今天讓人很滿意、幾個月後會拚命想拆掉的設計。</p>

<p>原因有兩個: 模型越來越快 (他提到 GPT-5.3 Codex Spark 那種速度)，快到根本沒辦法即時呈現; 而且如果要平行跑一百個工具呼叫，硬要讓使用者跟著看，反而限制了 agent 能做多少事。</p>

<h2 id="8-資料-agent-的驗證問題-為什麼展示過程不夠">8. 資料 agent 的驗證問題: 為什麼「展示過程」不夠</h2>

<p><strong>為什麼 notebook 這麼適合 agent?</strong> 因為 notebook 本來就是自我記錄的: 每一格程式碼旁邊就是它的輸出。agent 做完一份分析，整條推導過程攤在那裡，技術使用者可以一格一格檢查。</p>

<p><strong>但商業使用者檢查不了。</strong> 他不會 SQL、不會 Python，你把 20 格 cell 攤給他看，他也判斷不出對錯。Izzy 講了一句蠻重的話: 「以引用和可驗證性來說，展示工作過程不是資料工作的正確概念。它需要的是某種更強的驗證、信心或準確度的形式。」</p>

<blockquote>
  <p>編按: 小編最近也整理過 Hamel Husain 的<a href="/2026/08/05/eval-smell-verifiable-ux/">「很難 eval」是產品設計的警訊</a>，那篇的第一個正面案例剛好就是 Hex: Hamel 拿 Hex 的 chat + notebook 雙介面當「把驗證路徑攤開給使用者看」的模範。結果 Hex 自己的 AI 工程師在這裡說，這對商業使用者還不夠。</p>

  <p>兩邊其實沒有衝突，只是講到了不同層。Hamel 那篇整理出四個設計提問，第二個是「使用者手上有什麼可信的東西可以對照?」對技術使用者來說，攤開的 notebook 就是可以對照的東西; 但商業使用者沒有能力對照，就得換一個東西給他。<strong>語意模型正是資料領域對這個問題的答案</strong>: 對照的對象不是那 20 格 SQL，而是資料團隊已經審核過的指標定義。</p>
</blockquote>

<p><strong>那信心要從哪裡來?</strong> 主持人問他有什麼想法，Izzy 的第一句話是「我們還沒把這件事想通多少」。他接著說，資料領域有一個很明顯的辦法: 語意模型 (semantic model)。</p>

<p>語意模型是一份由資料團隊維護的定義檔。裡面寫清楚: 「營收」到底怎麼算、要用哪張表的哪個欄位、要不要扣掉退款;「活躍使用者」的定義是什麼; 這些指標可以按哪些維度切; 表跟表之間該怎麼 join 才不會算錯。工具上他提到的都是既有的東西: <a href="https://www.getdbt.com/">dbt</a> 的 semantic layer (Hex 可以直接匯入)、Hex 今年新增的語意模型編寫功能，以及他前公司 Looker 那一套。他沒有推薦特定廠商，重點在有沒有這份定義，不在用誰家的。</p>

<p>有了它，agent 產生查詢的時候就不是自己看著 schema 猜「營收大概是 orders.amount 加總吧」，而是照一份已經被資料團隊審核過的定義去組。<strong>換句話說，「這個數字算得對不對」在定義那一層就已經被人類確認過了，agent 只是套用。</strong>這就是為什麼可以大致相信答案是對的。Hex 另外還搭配管理員背書、資產驗證這些機制，作用是一樣的: 讓 agent 只在受治理的範圍內工作。</p>

<p><strong>但他馬上就講了語意模型的三個限制</strong>，所以這不是他給的答案，只是「其中一種辦法」:</p>

<ol>
  <li><strong>人總是有辦法搞死自己。</strong> 他自嘲以前用 Looker 那套完美的語意模型，照樣會出包</li>
  <li><strong>模型很愛自己寫 SQL。</strong> 你給了它定義，它還是會想繞過去自己算</li>
  <li><strong>有時候「超出語意模型」本身就是任務的重點。</strong> 客戶要的分析常常沒有現成定義可用，這時候 agent 得自己決定要 join 哪些表、指標怎麼算，沒有人先幫它把關</li>
</ol>

<p>更麻煩的是資料領域的「真相」本身常常就是模糊的: 兩個團隊對同一個指標的定義不同、一次 pipeline 更新就把你腳下的數字換掉。</p>

<p>所以他的原話是「這真的是一個很難的領域」，而 Hex 實際在做、也打算繼續做的方向不是語意模型，是<strong>大量的回饋迴路: 從使用者流回資料團隊</strong>。這就是下一節。</p>

<h2 id="9-context-studio-讓客戶的資料團隊持續修正-agent">9. Context Studio: 讓客戶的資料團隊持續修正 agent</h2>

<p>Hex 的客戶通常是有資料團隊的公司: <strong>資料團隊</strong>負責維護倉儲和指標定義，其他部門的人 (業務、行銷、產品) 是來問問題的<strong>使用者</strong>。以下講的資料團隊都是指客戶公司的，不是 Hex 自己的團隊。</p>

<p>上一節的問題是: 商業使用者無法自己驗證答案。Hex 的回答不是「讓 agent 變得永遠不出錯」，而是<strong>把錯誤導回客戶的資料團隊，讓他們去修 context</strong>。</p>

<p><strong>Context Studio</strong> 就是 Hex 產品裡做這件事的介面，給客戶公司的資料團隊管理員用。它讓管理員看到整個工作區的全貌: 大家在問什麼問題、拿到什麼答案。</p>

<p>但沒有人想每天讀幾百段對話，所以 Hex 另外跑一個 LLM 當評審，自動標記出可疑的案例: 可能出錯的、agent 看起來搞混的、或是跟語意模型有衝突的。管理員只要看被標記的那些，然後回頭去修 context: 改 guide、改語意模型、補倉儲的說明。下次同樣的問題就不會再錯，也可以順便通知當初問錯的那位使用者。</p>

<div style="border:1px solid var(--border);border-radius:8px;padding:1rem 1.2rem;background:var(--surface);margin:1.5rem 0">
<div style="font-size:.8rem;color:var(--text-secondary);margin-bottom:.8rem">Hex 目前的改善迴路 (人在迴路裡) ✏️ 小編製圖</div>
<div style="display:flex;flex-wrap:wrap;gap:.5rem;align-items:stretch">
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">1. 使用者提問</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">客戶公司的業務、行銷、產品部門在 Notebook / Threads 裡問問題、建專案</span></div>
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">2. LLM 評審標記</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">自動標出可能出錯、agent 搞混、跟語意模型衝突的案例，並做分群</span></div>
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">3. Context Studio</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">客戶的資料團隊管理員只看被標記的部分，不用讀完所有對話</span></div>
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">4. 人去修 context</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">改 guide、改語意模型、補倉儲說明</span></div>
<div style="flex:1 1 195px;border:1px solid rgba(9,105,218,.35);border-radius:6px;padding:.6rem .7rem;background:var(--tag-bg)"><b style="color:var(--accent)">5. 下次不再錯</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">回到第 1 步。Hex 今天主要的改善來源就是這個迴路</span></div>
</div>
<div style="font-size:.8rem;color:var(--text-secondary);margin-top:.8rem">Izzy 想做的下一步: 把第 2 到第 4 步交給一個 context agent 自己跑。</div>
</div>

<p>Izzy 明講今天這整套都是「事後」跑的，人在迴路裡。他們內部已經在原型一個 <strong>context agent</strong>，目標是讓 agent 自己去彙整這些回饋、更新 context。</p>

<p>順帶一提，Hex 自己的 AI 工程師另外還有一套內部的可觀測性和實驗系統，跟 Context Studio 不是同一套。「我有個在公司內部一直吵不完的意見: 它們應該要是同一套。」他認為兩邊都該往更高層次走: 真正該看的不是原始對話，而是 production 裡的問題分群、失敗類型、以及這些分群隨時間怎麼變化。主持人提到 Anthropic 的 Clio 就是這個想法，LangChain 也把類似機制做進了 LangSmith。</p>

<h2 id="10-memory-的難處-使用者記錯的東西會跟治理打架">10. Memory 的難處: 使用者記錯的東西會跟治理打架</h2>

<p>使用者心目中的 memory 是很個人的: ChatGPT 記得你有一隻叫 Rover 的狗。但同樣的東西放到資料團隊眼裡，「有點恐怖，其實不是有點，是非常恐怖」。</p>

<p>想像一個場景: 使用者跟 agent 說「不對，這個指標的定義應該是這樣」，而使用者其實記錯了，或那個定義已經過期。這句話一旦被存進 memory，就跟資料團隊訂下的語意模型直接矛盾。</p>

<p>而模型處理矛盾 context 的能力目前非常差。他們做過一個實驗，測的不是模型能不能正確化解矛盾，而是它會為此花掉多少時間。Claude Sonnet 4.6 在一定範圍內跑得相當有效率; 但只要注入一段跟它已知資訊衝突的 context，它會花 30 分鐘反覆糾結「等等、我看看、嗯、其實⋯」，進入一種完全停不下來的狀態。他說這件事其實一直以更小的規模在發生。</p>

<p><strong>Hex 目前的做法是把 context 按來源分層。</strong> 資料團隊訂的語意模型和 guide 是一層，使用者個人的 memory 是另一層，兩者存在不同的地方，使用者的 memory 不會混進資料團隊管的那一份、也不會覆寫它。Izzy 說他們對推出 memory 很謹慎，「還有更多測試要做」。</p>

<p>他也坦承這件事沒有標準答案。管理員需要能提供夠強的治理，而這有兩種極端: 一種是全部走語意模型，什麼都照規定來，但那樣就失去了彈性分析的空間; 另一種是「你就直接告訴 LLM，然後它會小心處理」，也就是大家現在在做的 guide、rules 檔、skill。真正的難題是當這幾層說法互相矛盾的時候，誰說了算。</p>

<h2 id="11-大多數-eval-set-都是爛的除非有人在持續維護它">11. 「大多數 eval set 都是爛的，除非有人在持續維護它」</h2>

<p>問到 eval 資料集該多大，Izzy 說他意見很多，第一句就是: 「一般來說，大多數 eval set 都是爛的，除非它一直有人在維護。」</p>

<p>他說在資料這個領域，幾乎每次他打開一個 benchmark 去看裡面的內容，都會很失望: ground truth 是錯的、評分方式有問題、有人想做確定性評分 (這是很值得敬佩的追求) 結果腳本有 bug、或是不接受把百分比寫成小數。還有些題目根本就出得不好、或不具代表性。</p>

<p>他舉了一個具體例子: 有個很熱門的 benchmark，他們自己也在用改編過的版本。這個 benchmark 很難，除非你知道它的秘密是什麼。裡面非常多題目其實都在考同一件事，就是 agent 有沒有把空陣列當成 null 看待。這條規則藏在某本手冊的角落，答案對錯全看這個。</p>

<p>「這不是資料 benchmark，這是 needle in a haystack、是 context 注意力 benchmark。」他認為很多 benchmark 把 SQL 語法、檢索、大海撈針這些東西，跟真正該測的分析行為與科學推理混在一起了。</p>

<p>主持人接了一句很準的類比: 這就像拿 tab 補全來評估一個 coding 模型，但大家實際上是要它寫完整的功能。</p>

<h2 id="12-好的-eval-set-小到你自己記得住但跑很多次重複">12. 好的 eval set: 小到你自己記得住，但跑很多次重複</h2>

<p>他的第一個意見: 好的 eval set 應該小到你 (作為那個在乎它的人) 能整份記得住。「這可能很有爭議」，但他要能說出每一題為什麼會失敗。他們有幾組很艱難的目標型 eval set，所有 agent 都做得很差 (Opus 4.6 Max 在超難那一組大概 20%)。他認為這種 eval set 要有用，前提是你講得出它為什麼失敗、失敗有哪四種原因。</p>

<p>所以那些失敗模式是他一題一題手工設計的: 「我很費工地手工打造了這些失敗模式，我把它們叫做陷阱，是我希望模型掉進去的。」相對地，前面提到的空陣列 benchmark 有三四百題，但大多數只是同一個陷阱的變形。他的做法是留幾個你記得住的陷阱題，然後在上面跑很多次重複，而不是把題目擴散成一堆不同案例、讓事情變複雜。他們這類極難題的 eval set 大概就 30 到 50 題。</p>

<p>第二個意見: 現在很多資料 eval set 已經不能代表使用者真正在做的事了。它們大多是那種一問一答的猜謎題，「2019 年 4 月 16 日我們有多少使用者?」，本質上是在問「你能不能把這句英文重排成 SQL 語法」。</p>

<p><strong>Hex 最有意思的那批 eval 則是這樣設計的:</strong></p>

<p>一般的資料 eval 是給一個乾淨的環境加一個問題。Hex 的做法是先準備好一個<strong>已經做了一大半的複雜 notebook</strong>: 裡面已經有十幾格 SQL 和圖表，看起來就像某個分析師做到一半的專案。而使用者的輸入只有一句話: 「這數字很奇怪，跟我預期的不一樣。」</p>

<p>題目在資料和 SQL 裡埋了<strong>三個連環 bug</strong>。從 notebook 目前顯示的狀態，只看得出第一個; 第二和第三個被第一個蓋住了，要先修好第一個，後面兩個才會浮現。所以 agent 沒辦法看一眼就交差，得真的走完一整輪排查。</p>

<p>這跟「把這句英文翻成 SQL」的距離差很遠，但接近真實工作太多了。這些題目多半是照著他們內部的真實情況改的，「因為資料本來就難，我們自己內部也會犯錯」。</p>

<h2 id="13-那題所有模型都答錯的-eval-全公司業務都達成-900-配額">13. 那題所有模型都答錯的 eval: 全公司業務都達成 900% 配額</h2>

<p><strong>fan-out</strong> 是資料工程裡最常見的錯誤之一: 做 join 的時候，如果右邊那張表對左邊的同一列有多筆對應，結果的列數就會被放大。例如一個業務有 3 筆佣金紀錄，跟成交紀錄 join 之後，這個業務的成交金額就被重複算了 3 次。這種 bug 不會報錯，只會讓數字悄悄變大。</p>

<p>Izzy 拿了一個真實的內部 dashboard (內容是業務代表的配額達成率)，刻意在裡面埋了一個 fan-out bug，讓所有業務看起來都超級猛: 每個人至少達成 900% 配額，史上最強一季。</p>

<p>然後你問 agent: 這一季表現最好的人狀況如何?</p>

<p>每個 agent 都說: 天啊，這是史上最好的一季，跟上一季比根本是階躍式成長，Josie 達成了 1200% 配額，大家都超嗨。</p>

<p>他實測過，數字要一路調到幾千個百分比，模型才會開始說「這可能有資料準確性或 pipeline 的問題」。而且幾乎沒有任何一個模型會自己抓到那個 bug。目前測過的所有模型都失敗，比較新的模型也只是撐得比較久而已。</p>

<p>但只要你回一句「這看起來不太對」，它十秒就找到了。</p>

<p>Izzy 的結論是: 這類事情上，有沒有人在迴路裡差很多。用他的說法，agent 的耳朵不會像人類分析師那樣豎起來。</p>

<p>小編覺得這題設計得好的地方在於，它測的不是模型會不會寫 SQL，而是它有沒有那個「等等，這數字不合理」的反應。這是資料分析師最核心的能力之一，剛好也是現在最難用 benchmark 表達的能力。他們當然也維護一組正常的日常 eval，用來防退步和評估新功能，但他認為同時保有一組「專門量測模型現在全都做不好的事」的目標型 eval，價值很高。</p>

<h2 id="14-追新模型的實務-effort-怎麼設context-累積到多長該壓縮">14. 追新模型的實務: effort 怎麼設、context 累積到多長該壓縮</h2>

<p>「資料分析和資料科學是需要通用智慧、而不是領域智慧的任務」，所以 Hex 永遠追最新最聰明的模型。目前 Opus 4.6 和 GPT-5.4 在他們的領域都非常強，但要說誰最好沒那麼容易: GPT-5.4 表現好一點，但花兩倍時間; 把 effort 調低就快一半，可是又沒那麼好了。</p>

<p>Effort 這個從 Opus 4.5 和 GPT-5.3 開始出現的設定，他坦承自己還沒完全搞懂 (「OpenAI 內部好像叫它 juice，蠻好笑的」)。他們做過一次內部測試，給使用者一個 effort 選擇器，收到的回饋是: 「這個有在動嗎?」因為低 effort 的時候模型有時反而搞混、開始反覆試錯; 高 effort 有時候反而一下就答完了。他們現在的做法是固定跑在一個「eval 表現夠好、又不會讓使用者為了一個簡單問題等十分鐘」的 effort 上。</p>

<p>最實用的一段是 1M context 該怎麼用。</p>

<p>agent 跑久了對話歷史會越積越長，撐爆 context 窗口。所以 harness 會在累積到某個長度時做 compaction (壓縮)，把前面的歷史摘要成一小段、騰出空間再繼續跑。<strong>問題是這個門檻該設在哪裡。</strong></p>

<p>Izzy 對 1M context 的判斷是: 幫助有限、甚至有點反效果，因為你沒辦法真的把窗口填滿而不損失智慧，「模型在窗口的尾端會開始做奇怪的事」。所以結論是要很早就壓縮。</p>

<p>但保留 1M 的存取權 (透過 beta header) 還是有價值的，因為這樣他們可以把門檻設在 30 萬而不是 20 萬 token，實測那樣效果最好。<strong>也就是說 1M 的價值不在於真的用到 100 萬，而在於讓你有本錢把壓縮門檻往後推一點。</strong></p>

<p>至於各家 API 一直冒出來的新功能 (tool search API、由 API 端代跑的 compaction)，Izzy 說有幫助的他們都用。自建 harness 加上有 eval 的好處，就是他們可以實際證明「這個有幫助」。</p>

<p>但他對實驗室的另一種說法有保留: 「我覺得他們現在對我們用的心理戰術，就是 in distribution 這個詞。」意思是你用他們的 agent SDK、用他們 server-side 的狀態化執行，你就落在模型訓練的分佈裡。他不否認這可能是真的，但他懷疑: 一旦你加進自己那個畫圖表或寫 SQL 的自訂工具，是不是又出了分佈? 還是掉進那種「不管你的工具，我要用 Perl」的狀況?</p>

<p>而且從實務上看，「模型用我們的 harness 用得相當好」，這些模型的 in-context learning 很強。要為了一個他還沒有量化直覺的好處交出這麼多控制權，對現在的 Hex 來說很難。</p>

<h2 id="15-metric-city-90-個回合的模擬測-agent-會不會越用越好">15. Metric City: 90 個回合的模擬，測 agent 會不會越用越好</h2>

<p>Metric City 是 Izzy 自己做的一套 benchmark，不是 Hex 的產品功能，目前還在他說的「瘋狂科學實驗室」階段。它跑 90 個回合，一個回合代表模擬世界裡的一天，實際跑完不需要 90 天。它測的對象是模型本身的行為，不是 Hex 的 agent: 整個模擬跑在一個他自己寫的、工具很簡單的 harness 裡 (他說很想把 Hex 的 agent 放進去跑，但還沒想出怎麼做)。</p>

<p><strong>為什麼要這樣測?</strong> 因為 Hex 這種產品的價值不在第零天。他甚至講了一句對自家產品很誠實的話: 第零天的 Hex 跟「Claude 接一個 Snowflake connector」比起來，大概也就是差在比較有主見、比較好用而已，「說不定一樣好」。真正的價值是隨時間累積出來的，到第 90 天那是完全不同的局面。</p>

<p>但現在所有的 eval 都只測第零天。「一次性的 eval 對我來說不算誠實地評估 Hex 這種系統。」pass@k 也不算，那只是平行多跑幾次而已。「現在大多數 eval set 就像是問『馬來西亞總統是誰』，答錯了，然後你就再也不會回頭去看它，什麼價值都沒產生。」</p>

<p><strong>環境長這樣</strong>: 他建了一個 Snowflake 倉儲，裡面放一家虛構公司 Shorelane Commerce 的商業資料，量體貼近真實。然後親手注入各種糟糕的資料品質問題: 一堆 null、壞掉的欄位、join 不太對得起來，以及各種誤導和混淆。</p>

<p><strong>每一個回合的流程:</strong></p>

<div style="border:1px solid var(--border);border-radius:8px;padding:1rem 1.2rem;background:var(--surface);margin:1.5rem 0">
<div style="font-size:.8rem;color:var(--text-secondary);margin-bottom:.8rem">Metric City: 一個回合 (= 模擬中的一天)，共 90 回合 ✏️ 小編製圖</div>
<div style="display:flex;flex-wrap:wrap;gap:.5rem;align-items:stretch">
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">1. 世界前進一天</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">dbt 模型跑一次，倉儲更新到當天: 新資料進來、東西壞掉、新產品上線、詐騙發生</span></div>
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">2. Ticket 寄進來</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">模擬的 stakeholder 用 email ticket 提問。有些在問資料，有些在告訴 agent 一些事實</span></div>
<div style="flex:1 1 195px;border:1px solid var(--border);border-radius:6px;padding:.6rem .7rem;background:var(--bg)"><b style="color:var(--accent)">3. Agent 查資料並回覆</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">一邊查一邊撞到這個倉儲的各種資料品質問題</span></div>
<div style="flex:1 1 195px;border:1px solid rgba(9,105,218,.35);border-radius:6px;padding:.6rem .7rem;background:var(--tag-bg)"><b style="color:var(--accent)">4. 回覆完不強制結束</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">給它一個「結束回合」工具，但告訴它: 想先做點主動的知識工作也可以</span></div>
<div style="flex:1 1 195px;border:1px solid rgba(9,105,218,.35);border-radius:6px;padding:.6rem .7rem;background:var(--tag-bg)"><b style="color:var(--accent)">5. 自己決定要不要做筆記</b><br /><span style="font-size:.85rem;color:var(--text-secondary)">追沒查完的線索、記錄發現、整理自己的 wiki。這份 wiki 會帶到下一個回合</span></div>
</div>
<div style="margin-top:1rem;padding-top:.9rem;border-top:1px solid var(--border)">
<div style="font-size:.85rem;color:var(--text-secondary);margin-bottom:.6rem">題目設計成: 如果 agent 一路上有把該記的記下來、而且找得回來，到第 90 天應該 100% 答對</div>
<div style="display:flex;align-items:center;gap:.6rem;margin-bottom:.35rem"><span style="flex:0 0 130px;font-size:.85rem">Sonnet 4.6 第 0 天</span><span style="flex:1 1 auto;background:var(--tag-bg);border-radius:3px;height:16px;position:relative"><span style="display:block;width:4%;height:100%;background:#cf222e;border-radius:3px"></span></span><span style="flex:0 0 46px;font-size:.85rem;text-align:right">4%</span></div>
<div style="display:flex;align-items:center;gap:.6rem;margin-bottom:.35rem"><span style="flex:0 0 130px;font-size:.85rem">Sonnet 4.6 第 90 天</span><span style="flex:1 1 auto;background:var(--tag-bg);border-radius:3px;height:16px;position:relative"><span style="display:block;width:24%;height:100%;background:#9a6700;border-radius:3px"></span></span><span style="flex:0 0 46px;font-size:.85rem;text-align:right">24%</span></div>
<div style="display:flex;align-items:center;gap:.6rem"><span style="flex:0 0 130px;font-size:.85rem">設計上的目標</span><span style="flex:1 1 auto;background:var(--tag-bg);border-radius:3px;height:16px;position:relative"><span style="display:block;width:100%;height:100%;background:#1a7f37;border-radius:3px"></span></span><span style="flex:0 0 46px;font-size:.85rem;text-align:right">100%</span></div>
</div>
</div>

<p><strong>關鍵在第 4、5 步。</strong> 回覆完 ticket 之後 agent 不會被強制結束，而是被告知「你可以結束這一回合了，但如果想先做點主動的知識工作也可以」。它自己寫的 wiki 會帶到下一個回合。所以這裡測的其實是: <strong>agent 會不會主動幫未來的自己做筆記。</strong></p>

<p>計分方式也扣在這件事上: 那些 ticket 不只在問問題，也會順便告訴 agent 一些事實。題目被設計成，如果 agent 有把這些發現記下來、而且之後找得回來，到第 90 天它應該可以 100% 答對。</p>

<p><strong>結果: Sonnet 4.6 第 90 天拿 24%，第零天是 4%。</strong>有進步，但離 100% 很遠。</p>

<p>「如果它拿 100%，我會覺得我的 benchmark 很爛。」他強調重點不是分數本身，而是第零天的分數在真實世界裡根本沒有參考價值。</p>

<p>他真正想觀察的其實也不是分數: 模型喜歡怎麼組織自己的 wiki? 它喜歡存下哪一類的資訊? 它怎麼取回? 它發現了什麼、又漏掉了什麼? 這些觀察是為了回頭讓 Hex 的 harness 變得更好。</p>

<p>跑一次很貴 (「我需要 credits，拜託」)。靈感來自 Andon Labs 的 Vending Bench (就是 Claudius 那台販賣機的相關研究)，那個是閉源的，他希望把 Metric City 開源出來。</p>

<p>小編補一個實作上的難點: 這種模擬允許 agent 在評估過程中動態改變系統 (自己整理 wiki、寫下發現)。Izzy 自己也承認，這讓評分變得極度困難，光是把整個模擬編排起來就很痛苦。這大概就是為什麼幾乎沒人做長時程 eval。</p>

<h2 id="最後-什麼是沙什麼是石頭">最後: 什麼是沙，什麼是石頭</h2>

<p>Hex 的 CEO Barry 最近很愛問一個問題: 如果模型明天變好兩倍，產品裡哪些東西會被沖走 (沙)，哪些會留下來 (石頭)?</p>

<p>Izzy 的答案分兩塊，而且他認為第二塊大得多。</p>

<p><strong>第一塊是把領域專業寫進產品裡。</strong> 他舉視覺化當例子: 團隊裡有兩位他形容為「職人等級」的視覺化專家，一半時間在把自己對「圖表該長什麼樣」的判斷直接寫成 agent 的規則，另一半時間在告訴其他同事哪裡做錯了、應該怎麼做。結果是使用者常常回饋說 Hex 的答案「就是比較好」，卻講不出為什麼。Izzy 認為差別就來自這些被寫進去的專業判斷。這部分模型變強也帶不走。</p>

<p><strong>第二塊、也是更大的一塊，是客戶自己累積的 context。</strong> 你在 Hex 裡做事、產出報表、建立專案，這些東西全部變成 agent 未來可以參考的資訊來源。他講得很直白: 第零天，Claude 加一個 Snowflake connector 大概跟 Hex 一樣好; 差別在於 Hex 是一個整個團隊每天都在裡面工作的平台，用得越久，agent 手上可用的 context 越多。到第 90 天就完全是兩回事了。</p>

<p>回頭看，這個問題其實貫穿了整集: ID 映射系統是沙 (跑個 eval 就發現不需要了)、切得太細的那一整組工具是沙、把過程展開給使用者看，他自己預測幾個月後也會變成沙。這些全都是他們當初做得很自豪、而且當時確實必要的東西。所以每次為了補模型的不足而寫下什麼，都值得先問一句: 這是沙還是石頭? 如果是沙，就別把它做成整套系統的核心依賴。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Data" /><category term="Eval" /><category term="Context Engineering" /><summary type="html"><![CDATA[LangChain 開了一個新 podcast 叫 Max Agency，主持人是 LangChain CEO Harrison Chase，第一集找來 Hex 的 AI 工程師 Izzy Miller，聊他們怎麼做資料 Agent: How Hex builds AI agents that reason like human data analysts。]]></summary></entry><entry><title type="html">2026 年的 RAG 最新發展: 瓶頸不在模型，也不在檢索，在文件解析</title><link href="https://blog.aihao.tw/2026/07/26/beyond-rag-llamaindex-workshop/" rel="alternate" type="text/html" title="2026 年的 RAG 最新發展: 瓶頸不在模型，也不在檢索，在文件解析" /><published>2026-07-26T00:00:00+00:00</published><updated>2026-07-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/26/beyond-rag-llamaindex-workshop</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/26/beyond-rag-llamaindex-workshop/"><![CDATA[<p>LlamaIndex 的 Pierre-Loïc Doulcet 在 AI Engineer Singapore 2026 帶了一場 90 分鐘的工作坊《Beyond RAG: Building Agentic Document Workflows with LlamaIndex》，投影片全長 116 頁，由 LlamaIndex 共同創辦人 Jerry Liu <a href="https://x.com/jerryjliu0/status/2058953208782074127">在 X 上公開</a>: <a href="https://drive.google.com/file/d/1IQ7G0aEyQQNBaxTBFkJ6YD-xvPZ5QM67/view">投影片連結在這</a>。</p>

<p>小編讀完覺得是近期少見的一份完整 RAG 知識整理。它不是產品簡報，而是把 2024 年那份廣為流傳的「12 個 RAG 痛點」清單拿出來，一條一條檢查兩年後各自變成什麼樣子: 哪些已經是例行工程，哪些其實根本不是原本以為的那個問題。</p>

<p>先介紹一下 LlamaIndex 這家公司，因為它的處境跟這份材料的觀點直接相關。它 2023 年以開源框架起家 (最早叫 GPT Index)，是那一批 LLM 應用框架裡跟 LangChain 齊名的兩家之一，<a href="https://github.com/run-llama/llama_index">核心 repo</a> 目前有 51k star。有意思的是這兩家後來走向不同: LangChain 往 agent 編排發展，LlamaIndex 反而把主力收窄到文件解析，現在官網的自我描述直接寫成「給 AI agent 用的文件理解與 OCR 平台」，產品線是 LlamaParse (雲端)、LiteParse (開源、本機跑)、LlamaExtract (schema 抽取)。</p>

<p>小編會持續關注他們，是因為在 RAG 這件事上，他們是少數還把它當主業在做、而且願意公開方法論和評測資料的廠商: 這份 116 頁投影片是一例，2026 年那份 ParseBench 把資料集和評測程式全部開源是另一例。當然他們終究是廠商，榜首是自家產品，這點文章後面談 ParseBench 時會再提。</p>

<p>Jerry Liu 自己在推文裡給的框架也蠻坦白的: 這份材料同時解釋了「為什麼 LlamaIndex 今天的核心聚焦，會窄化到專門做給 agent 用的文件解析」。換句話說，這份投影片的結論 (瓶頸在文件解析) 跟他們公司的產品重心是同一個判斷，看的時候可以把這件事放在心上。</p>

<p>投影片開場有一頁 Disclaimer 寫得很誠實:</p>

<blockquote>
  <p>到 2027 年，這裡幾乎所有東西都會是錯的。這個技術堆疊裡「最佳實務」的半衰期不到一年。所以接下來的內容，請當成一套「判斷問題出在 pipeline 哪一層」的思路來看，而不是一份照抄就能用的做法清單。</p>
</blockquote>

<p>然後是全篇的論點。先說一下 RAG 的處理順序: <strong>解析文件 → 切成 chunk → 建索引 → 檢索 → 排序 → 交給模型生成</strong>，模型在最後面。下面說的「上游」就是指模型之前的那幾個階段。</p>

<blockquote>
  <p>瓶頸是檢索，不是生成。
檢索裡面，瓶頸是解析 (parsing)。
解析裡面，瓶頸是文件結構被丟掉了。
大部分的調校功夫都花在模型這一層，但真正能拿到的改善，多半在更前面的解析、切塊、檢索那幾層。</p>
</blockquote>

<p>這三句是一層一層往前收斂的: 先說問題不在生成而在檢索，再說檢索的問題其實出在解析，最後說解析的問題出在文件結構被丟掉。所以雖然「上游」涵蓋模型之前的所有階段，但一路推到底，最深的那個瓶頸是 parsing。整份投影片就是在論證這條推導。</p>

<p>小編這篇就當導讀，把整份投影片按主題重新組織過，並補上一些脈絡。以下是重點整理:</p>

<h2 id="1-2024-年的-12-個痛點-一份到現在還好用的失敗詞彙表">1. 2024 年的 12 個痛點: 一份到現在還好用的失敗詞彙表</h2>

<p>這份清單來自 Wenqi Glantz 2024 年在 Towards Data Science 的 <a href="https://towardsdatascience.com/12-rag-pain-points-and-proposed-solutions-43709939a28c/">12 RAG Pain Points and Proposed Solutions</a>，其中 7 個出自 Barnett 等人的論文 <a href="https://arxiv.org/abs/2401.05856">Seven Failure Points When Engineering a Retrieval Augmented Generation System</a>，另外 5 個來自實務。</p>

<p>投影片給這份清單的定位蠻準的: 它今天的價值不在那些解法，而在<strong>當詞彙表用</strong>。能把失敗模式叫出名字，除錯就完成一半了。</p>

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>痛點</th>
      <th>層</th>
      <th>2026 的解法</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>內容缺失: 文件裡根本沒有答案，模型還是照答</td>
      <td>檢索 / 合成</td>
      <td>agentic loop + HyDE</td>
    </tr>
    <tr>
      <td>2</td>
      <td>該撈的沒撈到: 對的 chunk 排在第 11 名，但只取前 10 名</td>
      <td>檢索</td>
      <td>reranking</td>
    </tr>
    <tr>
      <td>3</td>
      <td>資訊沒進 context: 答案分散在 5 個 chunk，收攏進 prompt 時只留下 3 個</td>
      <td>檢索</td>
      <td>撈廣一點 + cross-encoder rerank</td>
    </tr>
    <tr>
      <td>4</td>
      <td>沒抽取出來: 對的 chunk 就在 prompt 裡，模型還是答錯</td>
      <td>合成 / 解析</td>
      <td>spatial text 解析</td>
    </tr>
    <tr>
      <td>5</td>
      <td>格式不對: 要 JSON，拿回來一段散文</td>
      <td>合成</td>
      <td>schema-first 抽取</td>
    </tr>
    <tr>
      <td>6</td>
      <td>粒度不對: chunk 切得太細或太粗，跟問題想要的資訊量對不上</td>
      <td>檢索</td>
      <td>多粒度索引 + 路由</td>
    </tr>
    <tr>
      <td>7</td>
      <td>答不完整: 多段式問題只答了第一段</td>
      <td>檢索</td>
      <td>子問題拆解</td>
    </tr>
    <tr>
      <td>8</td>
      <td>資料匯入的規模: 100K PDF 重新索引一次要 36 小時、約 500 美金</td>
      <td>規模</td>
      <td>增量匯入 + 內容雜湊去重</td>
    </tr>
    <tr>
      <td>9</td>
      <td>結構化資料問答: 答案是一個數字，向量檢索只給你關於那個數字的散文</td>
      <td>檢索 / 結構</td>
      <td>SQL + vector router</td>
    </tr>
    <tr>
      <td>10</td>
      <td>複雜 PDF: 多欄、註腳、跨頁表格、圖表、掃描件</td>
      <td>解析</td>
      <td>LiteParse / LlamaParse</td>
    </tr>
    <tr>
      <td>11</td>
      <td>備援模型: 主模型被限流，備援模型對同一個 prompt 回垃圾</td>
      <td>維運</td>
      <td>gateway 路由 + CI 強制走備援路徑</td>
    </tr>
    <tr>
      <td>12</td>
      <td>安全與隱私: 檢索到的內容是不可信輸入</td>
      <td>維運</td>
      <td>內容淨化 + PII 標記</td>
    </tr>
  </tbody>
</table>

<p>幾個值得單獨展開的:</p>

<p><strong>痛點 3 的原名是「合併策略的限制」(consolidation strategy limitations)。</strong> 這裡的「合併」指的是檢索完、送進 prompt 之前的那道手續: 撈回來一堆 chunk，但 context 塞不下全部，所以要排序、去重、截斷，收攏成最終要給模型看的那份內容。</p>

<p>投影片的例子是撈回 30 個 chunk，答案分散在其中 5 個 (Q1 到 Q4 的營收，加上全年合計)，reranker 把 3 個推到前面，另外 2 個在截斷時被丟掉。模型只看到 Q1 到 Q3，就自己加總回答，少了兩季。<strong>問題不在檢索沒撈到，而在收攏那一步把該留的丟了。</strong></p>

<p><strong>痛點 4 特別容易被誤判。</strong> 投影片的例子是問「DBS 2024 年的美元客戶存款是多少?」，檢索完全正確，對的那一頁就在 prompt 裡，但表格被解析成一行文字:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>In $ millions 2025 2024 Singapore dollar 230,646 204,704
US dollar 241,063 223,732 Hong Kong dollar 32,181 33,464 …
</code></pre></div></div>

<p>模型回答 241,063，因為那是緊接在「US dollar」後面的數字。正確答案是 223,732 (2024 那一欄)。對的頁、錯的欄。這種錯誤在 log 上看起來完全像是「模型不夠聰明」，換模型不會好。</p>

<p><strong>痛點 6 的兩難描述得很精準。</strong> 語料庫只會被切一次，但問題的粒度不固定，從「一個數字」到「整份報告」都有。</p>

<p>切成句子: 問「FY24 每股股利是多少」剛好命中那一句; 但問「總結 FY24 表現」就撈回 20 個互不相關的句子，摘要讀起來像一串沒關聯的事實。切成章節則反過來: 問一個數字會撈回兩頁的管理階層討論與分析 (MD&amp;A，年報裡那種長篇營運說明章節)，那個數字只是裡面 200 個事實之一，模型會取個平均而不是把它引出來。</p>

<p>投影片說得很直接: 團隊挑一個 chunk size (512 token、recursive splitter) 就上線，問題分布裡被犧牲的那一半，就成了系統無聲的失敗模式。</p>

<h2 id="2-parsing-只佔-112卻讓其中-7-個跟著壞">2. parsing 只佔 1/12，卻讓其中 7 個跟著壞</h2>

<p><img src="/assets/images/beyond-rag-llamaindex/failure-cluster.png" alt="" /></p>

<p>這張圖是整份投影片的關鍵。12 個痛點裡，歸類在 parsing 的只有 1 個 (複雜 PDF)，但它的錯誤會一路往下污染: 解析 → 檢索 (5 個痛點) → 合成 (2 個痛點)。右邊那三組 (結構化問答、規模、維運) 則是相鄰但不會被連累的問題。</p>

<p>投影片的結論: <strong>parsing 只佔 12 個裡的 1 個，它的錯誤卻會波及其他 7 個。而且很容易把 parsing 的失敗誤判成檢索或合成的毛病。</strong></p>

<h2 id="3-2024-年的修法幾乎都修錯了層">3. 2024 年的修法，幾乎都修錯了層</h2>

<p>當年大家在做的事: 改 prompt、加更多 few-shot、調 top-k、調 chunk size。投影片一句話總結: 這些全都把合成階段當成要修的東西，而對大部分痛點來說那是錯的一層。</p>

<p>配合這個判斷，投影片給了一張時間分配的對照，小編覺得這張最值得貼在牆上:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:1.6em 0"><div style="flex:1 1 300px;border:1px solid var(--accent);border-radius:8px;padding:18px;background:var(--tag-bg)"><div style="font-weight:600;margin-bottom:14px">這一週該怎麼花</div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:96px;height:18px;background:var(--accent);border-radius:3px"></div><span style="font-size:.9em">2 天 · 解析 + chunking</span></div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:96px;height:18px;background:var(--accent);border-radius:3px"></div><span style="font-size:.9em">2 天 · 檢索 + reranking</span></div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:48px;height:18px;background:rgba(9,105,218,.45);border-radius:3px"></div><span style="font-size:.9em">1 天 · 合成的 prompt</span></div><div style="display:flex;align-items:center;gap:10px"><div style="width:4px;height:18px;background:var(--border);border-radius:3px"></div><span style="font-size:.9em;color:var(--text-secondary)">0 天 · 模型</span></div></div><div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:18px;background:#ffebe9"><div style="font-weight:600;margin-bottom:14px">大部分團隊實際上怎麼花</div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:4px;height:18px;background:var(--border);border-radius:3px"></div><span style="font-size:.9em;color:var(--text-secondary)">0 天 · 解析</span></div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:48px;height:18px;background:rgba(207,34,46,.35);border-radius:3px"></div><span style="font-size:.9em">1 天 · 檢索</span></div><div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"><div style="width:96px;height:18px;background:#cf222e;border-radius:3px"></div><span style="font-size:.9em">2 天 · 合成的 prompt</span></div><div style="display:flex;align-items:center;gap:10px"><div style="width:96px;height:18px;background:#cf222e;border-radius:3px"></div><span style="font-size:.9em">2 天 · 換模型</span></div></div></div>

<p>投影片同時給了一個數字支撐: 樸素的 RAG (指沒有 rerank、沒有迴圈，檢索完就直接丟給模型生成的最基本作法) 大約有 40% 的時候在檢索這一步就錯了，而 LLM 會很有自信地根據錯的文件回答。兩年前這還是個論點，現在它就是事實。</p>

<h2 id="4-投資報酬率第一名-cross-encoder-reranking">4. 投資報酬率第一名: cross-encoder reranking</h2>

<p><img src="/assets/images/beyond-rag-llamaindex/cross-encoder.png" alt="" /></p>

<p>投影片給它的定位是「單一改動裡效果最大的一個」，建議也很明確: <strong>原型階段可以跳過，真實使用者開始看到輸出的那一刻就加上去。</strong></p>

<p>差別在模型類別，不在更好的 embedding:</p>

<ul>
  <li><strong>bi-encoder</strong> (就是一般的向量搜尋): 分別把 query 和文件編碼成向量再算餘弦相似度。文件向量可以預先算好，所以 1000 萬份文件撈 50 筆大概 5 毫秒。代價是它從來沒有同時看過 query 和文件。</li>
  <li><strong>cross-encoder</strong> (reranking): 把 query 和文件串在一起丟進 transformer，做完整的注意力計算。它能建模否定、範圍、限定詞這類互動關係。代價是慢，所以只能用在前 50 筆候選上，50 對打分約 100 毫秒。</li>
</ul>

<p>兩階段串起來就是: 檢索負責召回率 (recall)，reranking 負責精確率 (precision)。這兩種模型不是彼此的替代品。</p>

<p>實測的提升幅度典型是 nDCG@10 增加 4 到 14 分。nDCG@10 是檢索領域最常用的排序品質指標，衡量「前 10 筆結果裡，相關的有沒有排在前面」，滿分 100。</p>

<p>投影片引用了兩組數字。一組來自 sentence-transformers 在 BEIR (一套涵蓋多個領域的標準檢索評測資料集) 上的測試: 最大的 cross-encoder 平均勝過最好的 bi-encoder 約 4 分，單一資料集上最多 12 分 (gooaq-dev 從 59.12 到 71.35)。另一組是 <a href="https://blog.voyageai.com/rerank-2-5">Voyage rerank-2.5</a> 在 93 個資料集上比 Cohere rerank-v3.5 高 7.9%。</p>

<p>在 LlamaIndex 裡它就是一個 <code class="language-plaintext highlighter-rouge">NodePostprocessor</code>，撈廣一點再切回來:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">llama_index.postprocessor.cohere_rerank</span> <span class="kn">import</span> <span class="n">CohereRerank</span>

<span class="n">reranker</span> <span class="o">=</span> <span class="nc">CohereRerank</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="sh">"</span><span class="s">rerank-v3.5</span><span class="sh">"</span><span class="p">,</span> <span class="n">top_n</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>   <span class="c1"># rerank 後保留 5 筆
</span>
<span class="n">query_engine</span> <span class="o">=</span> <span class="n">index</span><span class="p">.</span><span class="nf">as_query_engine</span><span class="p">(</span>
    <span class="n">similarity_top_k</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span>              <span class="c1"># 先撈廣
</span>    <span class="n">node_postprocessors</span><span class="o">=</span><span class="p">[</span><span class="n">reranker</span><span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>可以直接替換的選項還有本機跑的 <code class="language-plaintext highlighter-rouge">SentenceTransformerRerank</code> (例如 <code class="language-plaintext highlighter-rouge">ms-marco-MiniLM-L-6-v2</code>)、<code class="language-plaintext highlighter-rouge">VoyageAIRerank</code>，以及 <code class="language-plaintext highlighter-rouge">ColbertRerank</code>。ColBERT 走的是介於兩者之間的第三條路，叫做延遲互動 (late interaction): 它跟 bi-encoder 一樣可以預先算好文件端的向量，但保留每個 token 各自的向量，而不是收斂成單一一個，比對時再讓 query 的每個 token 跟文件的每個 token 逐一配對。準確度接近 cross-encoder，速度快很多。</p>

<p>一個技巧同時處理痛點 2、3，以及部分的 6。成本是多一次呼叫、50 到 200 毫秒。投影片說沒有別的單一改動有這種投入產出比，小編認同。</p>

<h2 id="5-hyde-和-multi-query-被降級了">5. HyDE 和 multi-query 被降級了</h2>

<p>這兩個 2024 年被大量使用的技巧，在這份投影片裡只剩下「知道就好」的地位。</p>

<p>它們要解決的問題本身還在: 使用者用使用者的講法問問題，文件用文件的講法寫。在 agent 出現之前，有兩個技巧試著在單次檢索裡把這個落差補起來:</p>

<ul>
  <li><strong>HyDE</strong> (Hypothetical Document Embeddings): 請 LLM 先草擬一段假的答案段落，把那段話拿去 embedding 再搜尋。補的是「用語落差」，讓查詢的向量落到語料庫裡真實文件所在的那一區。</li>
  <li><strong>Multi-query + RRF</strong>: 請 LLM 產生 N 種不同講法，各自檢索一次，再用 Reciprocal Rank Fusion 依排名融合。補的是「角度落差」，等於從好幾個角度去猜使用者到底想問什麼。</li>
</ul>

<p>2026 的說法是: 兩個都被 agentic loop 吸收掉了。一個會評分再改寫的 agent 是動態在做同一件事，而且第一次沒撈到的時候還能救回來。投影片的建議是「知道就好，你會在 2024 年代的程式碼裡看到 <code class="language-plaintext highlighter-rouge">HyDEQueryTransform</code> 和 <code class="language-plaintext highlighter-rouge">QueryFusionRetriever</code>，但 2026 年優先讓 agent 自己迭代」。</p>

<h2 id="6-投資報酬率第二名-crag-與-agentic-loop">6. 投資報酬率第二名: CRAG 與 agentic loop</h2>

<p>固定的 pipeline 對每個問題都用同一種方式回答。agent 則是在每一步問自己: 我手上的資訊夠嗎? 撈回來的東西相關嗎? 要不要換個說法再搜一次? 要不要換一個資料來源?</p>

<p><strong>CRAG (Corrective RAG)</strong> 是這個想法最小的形狀: 檢索完先評分，再決定要不要生成。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>query → retrieve → grade → 高分: 直接合成
                         → 中間: 改寫 query 再檢索一次
                         → 低分: 退回網路搜尋
</code></pre></div></div>

<p>投影片對「為什麼有效」的解釋值得記下來: 樸素 RAG 永遠會生成，就算 chunk 是垃圾也一樣，模型會用幻覺把缺口補起來。CRAG 強迫在生成之前先做一個決定。而且這個 grader 很便宜:</p>

<ul>
  <li>它是小而快的模型，不是前沿模型</li>
  <li>它看到的只是 (query, chunk) 這種配對，形狀跟 reranker 一模一樣</li>
  <li>輸出只有一個信心分數，不是一段長篇判斷</li>
</ul>

<p>省下來的是花在爛 context 上的生成 token，以及那些聽起來很有自信的錯誤答案。</p>

<p>在 LlamaIndex 裡這是一個 Workflow 而不是一條 chain，每個分支是一個 <code class="language-plaintext highlighter-rouge">@step</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@step</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">grade</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">ev</span><span class="p">:</span> <span class="n">Retrieved</span><span class="p">):</span>
    <span class="n">s</span> <span class="o">=</span> <span class="k">await</span> <span class="n">grader</span><span class="p">.</span><span class="nf">score</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="n">query</span><span class="p">,</span> <span class="n">ev</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">s</span> <span class="o">&gt;</span> <span class="mf">0.7</span><span class="p">:</span> <span class="k">return</span> <span class="nc">Ready</span><span class="p">(</span><span class="n">nodes</span><span class="o">=</span><span class="n">ev</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">s</span> <span class="o">&gt;</span> <span class="mf">0.3</span><span class="p">:</span> <span class="k">return</span> <span class="nc">Refine</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">ev</span><span class="p">.</span><span class="n">query</span><span class="p">)</span>
    <span class="k">return</span> <span class="nc">WebFallback</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">ev</span><span class="p">.</span><span class="n">query</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>什麼時候該加、什麼時候不該加</strong>，這頁小編覺得比技術本身還實用。看之前先講清楚兩個詞: <strong>多跳 (multi-hop)</strong> 是指答案得串起好幾份文件才拼得出來的問題 (例如「A 公司的供應商裡，有誰同時是 B 案的被告?」)，單跳則是一次檢索就找得到; <strong>問題分布的頭部與長尾</strong>是把使用者實際問過的問題按出現頻率排序，最常被問的那一小撮叫頭部，剩下那一長串各只出現一兩次的叫長尾。</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:1.6em 0"><div style="flex:1 1 300px;border:1px solid #1a7f37;border-radius:8px;padding:18px;background:#dafbe1"><div style="font-weight:600;margin-bottom:10px">✓ 該加的時候</div><ul style="margin:0;padding-left:1.2em;font-size:.92em;line-height:1.8"><li>問題分布的頭部已經沒問題，壞掉的是長尾</li><li>失敗集中在多跳或語意模糊的問題</li><li>答錯的代價高過答得慢</li><li>你有辦法在保留不參與調校的測試集上量到提升</li></ul></div><div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:18px;background:#ffebe9"><div style="font-weight:600;margin-bottom:10px">✗ 不該加的時候</div><ul style="margin:0;padding-left:1.2em;font-size:.92em;line-height:1.8"><li>問題分布的頭部都還一團亂，先修檢索</li><li>端到端延遲預算低於 2 秒</li><li>你沒有 eval，迴圈只會把退步藏起來</li><li>你的「迴圈」其實只是在重試，掩蓋一個 bug</li></ul></div></div>

<p>要付的代價也講清楚了: 每次迭代多一次 LLM 呼叫，延遲的中位數 (P50) 典型變成 2 到 4 倍; token 隨迭代次數成長，要設上限; 執行路徑是非決定性的，每一步都要 trace。</p>

<p>投影片的原則是: <strong>先把直線 pipeline 做對，再在 eval 顯示有幫助的那些問題上加迴圈。</strong></p>

<h2 id="7-crag-之後-這個迴圈正在從上下兩邊被擠壓">7. CRAG 之後: 這個迴圈正在從上下兩邊被擠壓</h2>

<p>投影片把 2025 到 2026 加進來的三條研究線放在一起講，小編覺得這是全篇最有價值的判斷之一:</p>

<ul>
  <li><strong><a href="https://arxiv.org/abs/2403.14403">Adaptive-RAG</a></strong> (Jeong et al., NAACL 2024): 用一個小分類器依複雜度路由，分成不檢索 / 單跳 / 多跳三類。<strong>大部分問題根本不需要迴圈。</strong></li>
  <li><strong><a href="https://arxiv.org/abs/2407.08223">Speculative RAG</a></strong> (Google, 2024): 把工作拆成兩個角色，一個小模型當「草稿員」(drafter)，拿到不同的 chunk 子集之後各寫出一份候選答案; 一個大模型當「驗證員」(verifier)，從這些候選裡挑一份。因為所有草稿是同時產生的，這是平行而不是串行，所以贏在延遲。</li>
  <li><strong><a href="https://arxiv.org/abs/2503.09516">Search-R1</a> / <a href="https://arxiv.org/abs/2503.05592">R1-Searcher</a></strong> (2025): 用 RL 訓練推理模型自己學會什麼時候在思考中途呼叫 <code class="language-plaintext highlighter-rouge">search()</code>。檢索迴圈最後被內建進模型本身。</li>
</ul>

<p>再加上 2025 年的 Deep Research (OpenAI、Anthropic、Perplexity): 規劃 → 分支成平行的子搜尋 → 合成。那是一棵樹狀展開的 agentic retrieval，不是一個迴圈。</p>

<p>投影片的總結: 手刻的 CRAG 正在被兩邊擠壓，下面被 Adaptive-RAG 路由掉，上面被 Search-R1 吸進模型裡。<strong>workflow 的形狀留下來了，每一步裡面跑什麼則一直在換。</strong></p>

<h2 id="8-圖表怎麼進索引-caption-and-index">8. 圖表怎麼進索引: caption-and-index</h2>

<p>向量搜尋比對的是文字的 embedding，但 2026 年任何有意思的文件都有圖表、示意圖、截圖，而承載資訊的往往就是那張圖。問「圖 3 裡的年增率營收組合是多少」，答案在長條圖裡，不在旁邊的文字。</p>

<p>兩階段作法:</p>

<ol>
  <li><strong>匯入時</strong>: 用視覺模型幫每張圖產生說明文字，把說明文字跟周圍的文字一起索引。</li>
  <li><strong>合成時</strong>: 回答的當下把原圖抓回來，直接交給模型。</li>
</ol>

<p>用文字檢索拿召回率，再把原圖交給 VLM 做合成。</p>

<p><strong>為什麼不直接把圖片做成 embedding?</strong> 因為圖片 embedding 模型 (最常見的是 CLIP) 產出的向量，跟你原本文字那套向量不在同一個空間裡。你會多出一條 pipeline，兩邊也沒辦法共用同一個 reranker。說明文字留在文字空間裡就沒有這個問題。</p>

<p><strong>說明文字的品質才是瓶頸。</strong> 要用會講出軸名和單位的 VLM，不是只會回「一張顯示資料的長條圖」的那種。而且不要拿說明文字去改寫 VLM 自己讀得到的東西: 說明文字負責檢索，像素負責合成，原圖一定要留著。</p>

<p>適用的領域很明確: 金融、科學、醫療、簡報這類圖表密集的文件。純散文、或者合成階段只有文字模型的話，這一步就是浪費匯入成本。</p>

<h2 id="9-結構化資料-用查的不要用嵌入的">9. 結構化資料: 用查的，不要用嵌入的</h2>

<p>表格被當成文字 embedding 之後就失去形狀了: 列變成句子、欄變成雜訊、聚合運算變成不可能。問「APAC 區 2025 年支出前 5 名的供應商」，向量檢索找得到那張關於供應商的表，但它沒辦法篩選、分組或排名。</p>

<p>投影片的處理方式是把結構化資料當成<strong>可查詢的</strong>而不是<strong>可檢索的</strong>:</p>

<ul>
  <li>索引 schema 加上幾行範例資料，讓 LLM 知道資料長什麼形狀</li>
  <li>用一個 router 依問題形狀分流: 表格形狀的問題送去查詢語言，散文形狀的問題送去向量檢索</li>
  <li>在合成階段把兩邊的結果合併</li>
</ul>

<p>查詢語言依資料形狀挑: JSON 用 JQ、關聯式表格用 SQL、圖用 Cypher。判斷規則是: <strong>數字、清單、篩選 → 查詢。摘要 → 檢索。</strong></p>

<p>這裡有個容易誤會的地方要先講清楚: <strong>投影片說的「結構化資料」，主要不是 PDF 裡的表格，而是本來就已經結構化的東西。</strong> 原文寫得很明白: 「大部分企業資料不是表格，是巢狀 JSON: API dump、webhook、稽核 log、設定檔。」這類資料進來就是 JSON，沒有「解析成寫進資料庫」這一步，你只是不要把它拿去嵌入而已。</p>

<p>所以投影片才特別留了 JQ 這條路: 讓 LLM 寫出 JQ 運算式，再交給 JQ 執行，同一個運算式跑幾次結果都一樣。比起 SQL 的好處是<strong>不用 schema migration、不用 ETL、不用資料庫</strong>，直接在原始 JSON 上跑。</p>

<p>而所謂「索引 schema 加幾行範例資料」，指的是你索引「資料長什麼樣」的描述 (欄位名、型別、3 到 5 筆範例)，不是資料本身。那是每份資料幾百個 token 的小東西，不是一條匯入管線。</p>

<p>投影片對這件事有一句誠實的話: 「router 才是真正的工作。」關鍵字形狀偵測加上一個後備分類器，大部分團隊會跳過這一塊，而它才是讓整件事成立的東西。</p>

<h3 id="小編補充-那-pdf-裡的表格到底要不要進資料庫">小編補充: 那 PDF 裡的表格到底要不要進資料庫?</h3>

<p>這是投影片這一頁跳過的問題，但實務上一定會碰到。答案是<strong>大部分情況不用</strong>，三條路依成本排:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:1.6em 0"><div style="flex:1 1 210px;border:1px solid #1a7f37;border-radius:8px;padding:16px;background:#dafbe1"><div style="font-weight:600;margin-bottom:8px">a. 整張表當 Markdown 塞進去</div><div style="font-size:.9em;line-height:1.75">解析成 Markdown 之後表格是一個原子區塊，表頭跟每個 cell 綁在一起，模型本來就讀得懂。幾十列的表，模型自己篩選排序加總的準確度是夠的。</div><div style="margin-top:10px;font-size:.88em;color:#1a7f37;font-weight:600">匯入成本: 零</div></div><div style="flex:1 1 210px;border:1px solid #9a6700;border-radius:8px;padding:16px;background:#fff8c5"><div style="font-weight:600;margin-bottom:8px">b. 表格轉 JSON，用 JQ 查</div><div style="font-size:.9em;line-height:1.75">parser 給你的表格本來就是列 × 欄，序列化成 list of dicts 存在文件旁邊，查詢時讓 LLM 寫 JQ。塞不進 prompt 但也還沒大到要資料庫的量走這條。</div><div style="margin-top:10px;font-size:.88em;color:#9a6700;font-weight:600">匯入成本: 一份 JSON + schema 描述</div></div><div style="flex:1 1 210px;border:1px solid #cf222e;border-radius:8px;padding:16px;background:#ffebe9"><div style="font-weight:600;margin-bottom:8px">c. 抽成 typed rows 進資料庫</div><div style="font-size:.9em;line-height:1.75">用 LlamaExtract 定義 schema 抽成結構化列再進 DB。只有當同一組欄位跨很多份文件重複出現時才值得，例如幾千張發票、每季同一張財報附表。</div><div style="margin-top:10px;font-size:.88em;color:#cf222e;font-weight:600">匯入成本: 真的要做 ETL</div></div></div>

<p>給 LLM 寫 JQ 的 prompt 大概長這樣 (小編自己寫的示意版，投影片只給了 Python 那幾行呼叫程式碼):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>你會拿到一份 JSON 資料的結構描述和幾筆範例，以及使用者的問題。
請只輸出一行 jq 運算式，不要解釋，不要加上 markdown 標記。

結構描述:
{schema}

範例資料 (前 3 筆):
{sample_rows}

使用者問題:
{question}
</code></pre></div></div>

<p><strong>真正的判斷標準不是「這是不是表格」，而是「答案需不需要跨很多列聚合、資料塞不塞得進 prompt」。</strong> 投影片那個「前 5 名供應商」的例子之所以需要查詢語言，是因為那是幾千筆跨文件的採購紀錄; 如果只是單頁上的 20 列小表，直接把整張表塞進 prompt 就結束了。</p>

<p>順帶一提，這一頁跟投影片自己的 Part IV 有點互相拉扯: 如果解析有好好保留結構、表格是完整的 Markdown，那「表格失去形狀」這個前提就弱掉了。剩下真正成立的理由只有一個: <strong>資料量大到塞不進 context，所以聚合必須在模型外面算。</strong></p>

<p>前面這幾個技巧在投影片裡是按投資報酬率排序的，建議由上往下採用: reranking → query 改寫 → agentic / CRAG 迴圈 → 多模態 caption-and-index → 結構化資料走 JQ / SQL。它們彼此獨立、可以疊加不衝突，但每加一個都要量測提升幅度，然後決定留或不留。投影片特別補了一句: <strong>做完就停，大部分團隊需要的是其中 1 到 3 個，不是五個全上。</strong></p>

<h2 id="10-兩個框架轉變-long-context-沒有取代-rageval-變成獨立的技術堆疊">10. 兩個框架轉變: long context 沒有取代 RAG，eval 變成獨立的技術堆疊</h2>

<p><strong>2024 年那個預測沒有實現。</strong> 當時的說法是「百萬 token 的視窗一出來，檢索就只是小 context 時代的變通做法」。結果視窗確實出來了，檢索反而變得更重要，把整個語料庫塞進 context 這條路輸得很徹底，三個原因:</p>

<ul>
  <li><strong>成本</strong>: 每個 query 一百萬 token，以前沿模型的價格算，快取有幫助，但只要語料庫稍有規模，這筆帳就算不過來。</li>
  <li><strong>治理</strong>: 塞進去等於把使用者不該看到的文件也塞進去了。「模型答應會忽略」不是一個權限邊界。</li>
  <li><strong>可稽核性</strong>: 「AI 為什麼這樣說?」檢索給你一份引用紀錄，long context 只給你一種感覺。</li>
</ul>

<p>投影片的結論: <strong>檢索最後扮演的角色是稽核軌跡，而不是小 context 的變通做法。</strong> 小編覺得這個重新定位比「哪個比較準」的爭論有用多了。</p>

<p><strong>eval 則從一個 notebook 變成一整套技術堆疊。</strong> 2024 年是 10 題手寫問題、肉眼看過就上線; 2026 年是從 production query 抽樣建立 eval set、用三個一組的指標評分，然後把 eval 當成每次部署的關卡，分數掉了就擋下來。</p>

<p>那三個指標分別是忠實度 (faithfulness: 答案有沒有忠於檢索到的內容)、相關性 (relevancy: 答案有沒有回答到問題)、召回率 (recall: 該撈的內容有沒有撈到)。工具上 RAGAS 提供核心 RAG 指標的參考實作、DeepEval 做這組三元指標、Langfuse 和 Phoenix 負責 trace、重播、production 抽樣與回歸把關。</p>

<p>兩個提醒講得很直白:</p>

<ul>
  <li>不要用同一個模型來生成和評分，它會同意自己。</li>
  <li>不要只評你的系統答得好的問題，要另外維護一組明確的長尾題庫。</li>
</ul>

<p>最後一句話值得抄下來: <strong>如果你沒辦法量測一個改動有沒有幫助，你手上的是 demo，不是系統。</strong></p>

<blockquote>
  <p>編按: 關於 LLM-as-judge 怎麼做得更可靠，可以參考小編之前整理的 <a href="https://blog.aihao.tw/2026/07/22/llm-judge-checklist-decomposition/">用檢查清單拆解 LLM-as-Judge</a>。</p>
</blockquote>

<h2 id="11-parsing-的六種壞法">11. Parsing 的六種壞法</h2>

<p>到這裡投影片講了一句很有力的話: 前面四十分鐘講的 reranking、HyDE、agentic loop 全都重要，但<strong>沒有任何一項救得回一份一開始就被解析壞掉的文件</strong>。</p>

<p>被檢查的對象是 2024 年大部分團隊在用的 PyMuPDF 或 pdfplumber。投影片對它們的評價蠻公道的: 免費、快、在簡單文件上意外地堪用，單欄的研究論文沒問題; 但年報、合約、法規申報就不行了，因為它們的輸出是「一串大致照閱讀順序排的文字」，而在多欄頁面上，那個順序就是錯的。</p>

<p>接下來六頁一頁一個失敗模式，全部用同一份真實文件 (DBS 2025 年報) 示範:</p>

<p><strong>1. 多欄被合併成一行。</strong> PyMuPDF 大致照著頁面上的閱讀順序走 token，遇到兩欄就會左右交錯。你拿到的會是「淨利息收入 本集團持續交出強勁 成長 8% 因為存款成長橫跨…」這種東西: 兩句不相干的話被逐字穿插在一起。下游後果是 embedding 帶進了不存在的上下文，檢索會撈出文件裡根本沒寫過的句子。</p>

<p><strong>2. 表格變成一串 pipe 分隔的碎片。</strong></p>

<p><img src="/assets/images/beyond-rag-llamaindex/pipe-soup.png" alt="" /></p>

<p>欄位標頭跟它的列脫鉤，數字失去單位。投影片的例子裡，<code class="language-plaintext highlighter-rouge">Allowances 241 9 &gt;100</code> 讀起來像一個句子，而不是表格的一列。下游後果就是痛點 4: 對的頁、錯的列，agent 很有自信地從錯的那一行拿值。</p>

<p><strong>3. 註腳被內嵌進本文。</strong> 註腳字比較小、通常在頁尾，樸素的 parser 直接把它接在本文後面。結果是「淨利息收入上升 14 bps (1)。本集團 CET1 比率改善 (1) 不含出售子公司的一次性收益。」那句但書讀起來像是下一句話，數字則變成沒有脈絡的數字 (bps 是基點，1 bps = 0.01 個百分點; CET1 是銀行的核心資本適足率)。下游後果: agent 把「+14 bps」當成成長主軸引用，完全不知道還有一次性收益這個前提。</p>

<p><strong>4. 圖表被直接跳過。</strong> 圖是 image object，文字 parser 走過去就沒了。裡面的軸標籤、刻度值、標註全部不會進索引。你拿到的通常只有圖例字串「FY21 FY22 FY23 FY24 FY25」，沒有數值、沒有趨勢。下游後果: 任何答案在圖裡的問題都會失敗，而且不會報錯。</p>

<p><strong>5. 頁首頁尾污染每一個 chunk。</strong> 每一頁上方都是同一個字串「DBS Group Holdings Ltd · Annual Report 2025」，頁尾都是頁碼，每個 chunk 都繼承這兩樣。結果是每個 chunk 的 embedding 都被拉往同一個中心，餘弦相似度的鑑別力下降。下游後果: top-k 的邊界變得很吵，reranker 得把預算花在區分近似重複的 chunk 上。</p>

<p><strong>6. 跨頁的閱讀順序斷掉。</strong> 一個句子在第 99 頁開始、第 100 頁結束; 一張表跨兩頁。沒有做頁面縫合的 parser 會吐出兩個互不相連的片段: chunk A 在子句中間結束，chunk B 在子句中間開始，各自被 embedding。表格底部幾列跟它的表頭在不同的 chunk 裡。下游後果: 一個分頁同時引發痛點 3 (資訊沒進 context) 和痛點 4 (沒抽取出來)。</p>

<p>投影片最後補了一句: <strong>隨便挑一份你沒有親自檢查過的 production PDF，這六種至少有一種正在發生。</strong></p>

<h2 id="12-spatial-text-被跳過的原始型別">12. Spatial text: 被跳過的原始型別</h2>

<p><img src="/assets/images/beyond-rag-llamaindex/spatial-text.png" alt="" /></p>

<p>這是整份投影片的核心概念。PDF 不是一串文字，它是一組<strong>在 2D 畫布上有座標的 token</strong>，每一個都帶著 x、y、寬、高、字體和樣式。只要這些資訊在解析階段被保留下來，下游就還有辦法對版面做推理。</p>

<p>作法很單純: 按 y 分組得到列，按 x 分組得到欄。上圖右邊是 LiteParse 的真實輸出，左邊是從 token 還原回來的表格。</p>

<p>扁平文字失去的東西 vs spatial text 給你的東西:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:1.6em 0"><div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:18px;background:#ffebe9"><div style="font-weight:600;margin-bottom:10px">扁平文字失去</div><ul style="margin:0;padding-left:1.2em;font-size:.92em;line-height:1.8"><li>欄結構: 多欄頁面會交錯</li><li>表格的行與列: 每個 cell 變成孤立的數字</li><li>閱讀順序: 側欄、註腳、圖說跟本文混在一起</li><li>階層: 標題跟它的段落變得無法區分</li><li>位置感: 你沒辦法說「第 4 頁那張表」，因為沒有第 4 頁了</li></ul></div><div style="flex:1 1 300px;border:1px solid #1a7f37;border-radius:8px;padding:18px;background:#dafbe1"><div style="font-weight:600;margin-bottom:10px">spatial text 給你</div><ul style="margin:0;padding-left:1.2em;font-size:.92em;line-height:1.8"><li>可還原的結構: 按 y 分組是列，按 x 分組是欄，按區域分組是章節</li><li>依區域過濾: 不用寫 regex 就能把頁首頁尾丟掉</li><li>可以引用回像素: 每個 chunk 對應一個可以 highlight 的 bounding box</li><li>可以交接給 VLM: 文字不夠用時，退回同一個座標的頁面截圖</li></ul></div></div>

<p>重點是輸出保留了足夠的資訊，讓你在下游想要什麼就推導什麼: 要純文字就攤平、要表格就按 y 分組、要丟掉頁尾就按區域過濾。</p>

<p><strong>LiteParse</strong> 是 LlamaIndex 開源的實作: Apache 2.0、本機執行、不需要雲端也不需要 LLM、單一 binary 沒有 Python 依賴，內建 Tesseract.js 做 OCR (可以換成自己的 HTTP OCR server)，另外可以產生高解析度的頁面截圖給 VLM 用。目前 <a href="https://github.com/run-llama/liteparse">GitHub 上約 11.8k star</a>。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i <span class="nt">-g</span> @llamaindex/liteparse

lit parse document.pdf        <span class="c"># JSON + bounding boxes</span>
lit screenshot document.pdf   <span class="c"># 給 VLM 用的頁面圖</span>
</code></pre></div></div>

<blockquote>
  <p>編按: 小編這篇就是用 LiteParse 解析這份 116 頁投影片的，含 OCR 全部跑完 40 秒。它也可以直接當成 Claude Code 的 skill 安裝: <code class="language-plaintext highlighter-rouge">npx skills add run-llama/llamaparse-agent-skills --skill liteparse</code>。</p>
</blockquote>

<h2 id="13-markdown-是交給下游的格式chunking-沿著結構切">13. Markdown 是交給下游的格式，chunking 沿著結構切</h2>

<p>spatial text 保存的是幾何，Markdown 保存的是結構。投影片把兩者的分工講得很清楚: <strong>spatial text 是你解析成的東西，Markdown 是你交給下游的東西。</strong></p>

<p>一份 PDF 轉成 Markdown 之後保留了: 標題階層 (<code class="language-plaintext highlighter-rouge">#</code> <code class="language-plaintext highlighter-rouge">##</code> <code class="language-plaintext highlighter-rouge">###</code>)、表格網格連同表頭與資料列、清單與巢狀清單、引言與粗體斜體這類排版意圖、掛回錨點的註腳、圖片與它的說明文字。</p>

<p>三個下游好處:</p>

<ol>
  <li><strong>chunking 尊重結構</strong>: 依標題切，每個 chunk 就是一個完整的章節。<code class="language-plaintext highlighter-rouge">MarkdownNodeParser</code> 會保住表格和程式碼區塊不被切開。</li>
  <li><strong>檢索時表格保持原子性</strong>: 一張 pipe 表格是一個區塊，表頭那一列跟每個 cell 都黏在一起，不會再有「對的頁、錯的列」。</li>
  <li><strong>LLM 本來就讀得懂</strong>: 每個前沿模型都在 Markdown 上訓練過，不需要用 prompt 去解釋「這是一張表格」，語法本身就是訊號。</li>
</ol>

<p>整條 pipeline 就是: 解析 PDF 成帶 bbox 的 spatial token → 偵測區域 (標題、表格、圖、註腳) → 輸出保留這些區域的 Markdown → 依 Markdown 結構做 chunking。</p>

<p><strong>chunking 這一步的觀念也換了。</strong> 2024 年的答案是 512 token、50 token overlap、recursive splitter，投影片對它的評語是「永遠是錯的，只是一個站得住腳的預設值，因為它假裝文件是一串字串」。2026 年的答案是沿著文件結構切:</p>

<ul>
  <li>一個章節是一個 chunk</li>
  <li>一張表是一個 chunk</li>
  <li>一張圖加它的說明是一個 chunk</li>
  <li>註腳跟著它的錨點</li>
</ul>

<p>前提是 parser 有把結構留下來，這也是為什麼 chunking 這個主題在投影片裡被放進 parsing 那一章，而不是留在檢索那一章。</p>

<p><strong>metadata 是 chunking 的另外一半。</strong> 投影片給的範例 metadata 是這樣:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">chunk</span><span class="p">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">document_title</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Annual Report 2025</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">section_path</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">IBG &gt; Segment Performance</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">page</span><span class="sh">"</span><span class="p">:</span> <span class="mi">37</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">source_url</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">https://...</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">ingest_ts</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">2026-03-12T09:01:00Z</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">permissions</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span><span class="sh">"</span><span class="s">analyst</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">internal</span><span class="sh">"</span><span class="p">],</span>
    <span class="sh">"</span><span class="s">parser</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">llamaparse@2026-02</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">confidence</span><span class="sh">"</span><span class="p">:</span> <span class="mf">0.94</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>投影片替這些欄位列了三個用途，各自對應 pipeline 的一個階段:</p>

<ul>
  <li><strong>檢索時拿來過濾</strong>: 「只要 2024 年以後的 10-K 章節」應該用 <code class="language-plaintext highlighter-rouge">ingest_ts</code> 這類 metadata 條件直接擋掉，不該丟給向量搜尋去猜。</li>
  <li><strong>合成時拿來引用</strong>: <code class="language-plaintext highlighter-rouge">page</code> 跟 <code class="language-plaintext highlighter-rouge">section_path</code> 一起帶回答案裡，使用者可以點進去看原文。</li>
  <li><strong>稽核時拿來追溯</strong>: 有 <code class="language-plaintext highlighter-rouge">parser</code> 版本加上 <code class="language-plaintext highlighter-rouge">ingest_ts</code>，才能重播任何一個歷史答案當初是怎麼來的。</li>
</ul>

<p>投影片的結論是: 把 metadata 拿掉，檢索、引用、過濾三件事會一起變差。</p>

<h2 id="14-llamaparse-與-llamaextract以及該用哪一個">14. LlamaParse 與 LlamaExtract，以及該用哪一個</h2>

<p><strong>LlamaParse</strong> 負責的是 LiteParse 應付不來的那一類文件: 它是雲端託管服務，以視覺模型為核心，按頁計價，預設輸出 Markdown。投影片列的適用時機是掃描件、密集表格、難搞的多欄版面、需要理解圖表內容、需要跨 10k 份以上文件保持一致，而且延遲預算是分鐘級而不是毫秒級。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">parser</span> <span class="o">=</span> <span class="nc">LlamaParse</span><span class="p">(</span>
    <span class="n">result_type</span><span class="o">=</span><span class="sh">"</span><span class="s">markdown</span><span class="sh">"</span><span class="p">,</span>   <span class="c1"># 這才是重點
</span>    <span class="n">parse_mode</span><span class="o">=</span><span class="sh">"</span><span class="s">agentic</span><span class="sh">"</span><span class="p">,</span>     <span class="c1"># 每頁走視覺模型
</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>LlamaExtract</strong> 走的是另一條路: 你定義一個 Pydantic schema，它回給你符合型別的 JSON，解析、檢索、合成全部在它內部完成。schema 就是契約，不合法的輸出在進來之前就被擋掉; 跨文件的欄位和型別都一致; 而且每個值都帶著頁碼和 bbox 的引用。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Segment</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">total_income</span><span class="p">:</span> <span class="nb">float</span>
    <span class="n">yoy_pct</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="nc">Field</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="sh">"</span><span class="s">YoY %</span><span class="sh">"</span><span class="p">)</span>

<span class="k">class</span> <span class="nc">AnnualReport</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">fiscal_year</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">segments</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Segment</span><span class="p">]</span>

<span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nc">LlamaExtract</span><span class="p">().</span><span class="nf">aextract</span><span class="p">(</span><span class="sh">"</span><span class="s">annual_report_2025.pdf</span><span class="sh">"</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">AnnualReport</span><span class="p">)</span>
<span class="c1"># result.data      → 驗證過的 AnnualReport
# result.citations → 每個欄位的 page + bbox
</span></code></pre></div></div>

<p><strong>Extract 還是 Parse + Retrieval?</strong> 判準很清楚:</p>

<ul>
  <li>你知道自己要什麼、跨很多文件都是同一組欄位、下游是結構化資料 → <strong>Extract</strong>。例子: 合約審查抓固定的 12 個條款。</li>
  <li>你不知道自己要什麼、問法五花八門、問題形狀是長尾 → <strong>Parse + Retrieval</strong>。例子: 語料庫上的研究助理。</li>
</ul>

<p>投影片補了一句: 大部分真實系統兩個都用，已知欄位走 extract，長尾走 parse + retrieval。</p>

<p>還有一個容易忽略的觀念: <strong>parsing 是一個階段，不是一個步驟。</strong> 你會重新解析、會用新的 schema 重新抽取、parser 變好的時候會需要回溯重跑。所以要為這件事設計: 保留原始文件、保留每一個中間產物、讓 parsing 具備冪等性 (idempotent)。</p>

<h2 id="15-parsebench-用公開數字講話">15. ParseBench: 用公開數字講話</h2>

<p><img src="/assets/images/beyond-rag-llamaindex/parse-cost.png" alt="" /></p>

<p>LlamaIndex 在 2026 年做了 <a href="https://arxiv.org/abs/2604.08538">ParseBench</a>，用約 2000 頁人工驗證過的企業文件 (保險、金融、政府) 評五個維度: 表格 (行 × 列的結構有沒有還原)、圖表 (軸、數值、序列對不對)、文字完整性 (有沒有漏字或幻覺)、文字樣式 (標題、清單、強調有沒有保留成語意結構)、bbox grounding (每個抽出來的元素能不能指回頁面上的位置)。</p>

<p>評的是語意正確性，不是文字重疊率，這一點是它跟舊式 OCR benchmark 最大的差別。</p>

<p>兩個榜:</p>

<ul>
  <li><a href="https://huggingface.co/datasets/llamaindex/ParseBench">Hugging Face 上的資料集與 leaderboard</a> 給開放權重模型，可以下載下來在自己機器上跑 Qwen-VL、Dots OCR、Docling 或自己的 fine-tune，不用 API key。</li>
  <li><a href="https://www.kaggle.com/benchmarks/llamaindex-org/parsebench">Kaggle 上的競賽榜</a> 給前沿與商用模型，目前前段是 Gemini 3 Flash、GPT-5.4、Claude Sonnet 4.6。兩邊用的是同一套評測程式 (eval harness)，所以開放模型跟前沿模型的數字可以直接比。</li>
</ul>

<p>成本這頁的結論是: 好的解析確實比較貴，但<strong>曲線大部分是平的</strong>，多花的錢沒有想像中多。</p>

<p>效率前緣 (Pareto frontier，指的是在同樣成本下沒有別的選項更準、在同樣準確度下沒有別的選項更便宜的那些點) 落在 LlamaParse Agentic (84.9%，每頁約 1.2 美分) 和 LlamaParse Cost-Effective (71.9%，每頁約 0.4 美分)。前沿 VLM 拿來當 parser 反而不划算: GPT-5 Mini 和 Haiku 4.5 只有 40 出頭的分數，價格卻是高階的。</p>

<p>小編要提醒一下利益關係: <strong>這是 LlamaIndex 自己做的 benchmark，論文作者名單裡就有這場工作坊的講者，而榜首是自家產品。</strong> 看數字的時候把這件事放在心上。不過資料集和評測程式都是公開可重跑的，這一點比大部分廠商 benchmark 誠實; 投影片自己也寫了「上線前先自己驗，數字會比廠商報的低」。</p>

<h2 id="16-四個文件工作流的實際案例">16. 四個文件工作流的實際案例</h2>

<p>投影片第五部分講的是 LlamaIndex 自家 Workflow 框架的設計 (事件驅動的圖、typed event、扇出與收斂的 API)，那屬於框架選型的範疇，小編這裡跳過。真正值得帶走的是四個實際案例，以及背後那些跟框架無關的架構判斷。</p>

<p><img src="/assets/images/beyond-rag-llamaindex/contract-workflow.png" alt="" /></p>

<p><strong>合約審查。</strong> 幾千份合約，每一份都有同樣那 12 個高風險條款，律師每次都要重讀一遍。改造之後的流程是: 解析 → 判斷文件型別 → 逐條抽取條款 → 逐條打風險分數 → 只有被標記的送到人面前 → 人的決定回饋進最終報告。</p>

<p>這個案例的分解問答很值得抄:</p>

<ul>
  <li><strong>為什麼解析要獨立成一個步驟?</strong> 因為你一定會換 parser，要先解耦。</li>
  <li><strong>為什麼判斷型別要排在抽取前面?</strong> 因為抽取用的 schema 取決於文件是 MSA、NDA 還是 SOW。</li>
  <li><strong>為什麼打分要排在人工審查前面?</strong> 因為你只想讓人看被標記的條款，不是全部 12 條乘以幾千份。</li>
  <li><strong>為什麼人工審查要夾在打分和報告中間?</strong> 因為人的修改要能回饋進報告，不能只是旁邊做個記號。</li>
</ul>

<p>投影片的總結是: <strong>流程通常比較像決策流程的鏡像，而不是資料流程的鏡像。</strong></p>

<p><strong>人工審查應該設計成系統的一部分，而不是掛在系統外面。</strong> 這是這一章小編覺得最有價值的一句。真實的文件系統 (合約審查、盡職調查、病歷摘要) 不可能全自動，而常見的做法是流程跑到一半暫停、寄一封信給審查者、期待人會回來，這種做法脆弱、沒有狀態、事後也追不回來。</p>

<p>把人工審查做成流程裡一個正式的狀態 (待審 → 已審，中間的狀態持久保存，人可以隔幾分鐘也可以隔幾天才回來)，好處是<strong>你可以對它做 eval</strong>: 量測人什麼時候同意、什麼時候不同意、他們具體改了什麼，然後把這些回饋進下一版的 eval set。人變成你會量測的系統的一部分，而不是一個掛在外面的保險機制。</p>

<p><strong>Deep research。</strong> 「DBS 在 FY26 的定位如何?」其實是五個子問題 (利差、信用、財富管理、地區組合、資本)。子問題要平行跑 (延遲預算)、負責彙整成文的那一步要等全部子問題有結果、引用要一路串到底，而且其中一個子問題失敗不應該讓其他的一起失敗。</p>

<p><strong>盡職調查。</strong> 五千份文件的資料室，合約、財報、股權表、聘僱合約各是不同形狀，要抽的事實也不一樣。作法是逐份分類 → 路由到型別專屬的抽取 schema → 累積成一個結構化的事實資料庫 → 跑跨文件一致性檢查 → 把異常送給人看。這裡的重點是「事實資料庫」: 抽出來的東西要是可以查詢的結構化資料，跨文件比對才做得起來。</p>

<p><strong>活的知識庫。</strong> 來源文件會被編輯，索引就會漂移，而相關的人往往是從一個壞掉的答案才發現這件事。這套流程監看來源、變更時重新解析、增量更新索引，並把事實層級的差異發布給訂閱者。它跟前三個最大的差別是<strong>沒有終點</strong>: 這是一個持續運轉的系統，不是一個跑完就結束的腳本。</p>

<p><strong>什麼時候不需要這一套?</strong> 投影片講得很乾脆: 如果你的任務是「對單一文件做單次問答」，一個函式就夠了。這種流程編排值得它的複雜度，是在你有分支、需要平行、需要中斷後續跑、或者需要人工審查的時候。</p>

<h2 id="17-附錄裡幾條值得單獨挑出來的">17. 附錄裡幾條值得單獨挑出來的</h2>

<p>投影片後面的附錄有幾條實務模式，小編覺得比正文某些段落還實用:</p>

<p><strong>版本化每一樣東西</strong> (parser、index、prompt、schema)。理由講得很好: 「我們星期二換了 parser，星期三準確率掉了 8 個百分點」這句話，只有在星期二那版 parser 還拿得到的時候才 debug 得動。</p>

<p><strong>每個 query 設成本預算。</strong> 大部分 production 的成本超支不是「模型變貴了」，而是「某個 agent 陷進檢索迴圈，在一個本來該花 2k token 的問題上用掉 30k」。加一個檢查累計 token 的上限，30 秒就寫得完，也就抓得到; 沒有的話，你會在月底帳單上才發現。</p>

<p><strong>把信心分數當成一級輸出。</strong> 高信心的答案直接出，低信心的標記待審。投影片的說法是: 沒有信心分數，每個答案都得審，那你做出來的是很貴的自動完成。</p>

<p><strong>把回饋當成資料。</strong> 使用者按倒讚就記下來、審查者改了抽取結果就記下修改前後、agent 重試就記下為什麼。這些 log 會變成下一版的 eval set、下一批 fine-tuning 資料、以及下一個「錢該投哪裡」的判斷依據。</p>

<p><strong>匯入是串流，不是批次。</strong> 痛點 8 (資料匯入的規模) 其實已經被現代向量儲存悄悄解決了: pgvector + HNSW、LanceDB、Turbopuffer、Pinecone 都支援增量 upsert (同一筆資料有就更新、沒有就新增，不用整個索引重建)，再配上內容雜湊去重和一個工作佇列就夠了。投影片的觀察是大部分團隊還在跑批次，純粹出於習慣。</p>

<p><strong>備援模型是維運問題，不是模型問題。</strong> 痛點 11 的處理方式也很直接: 用 gateway 路由 (LiteLLM、OpenRouter 或自己寫的)、主力與備援放在不同供應商、積極快取，然後<strong>在 CI 裡強制走一次備援路徑做測試</strong>。最後這條是最常被漏掉的，因為備援路徑平常不會被執行到，壞了也沒人知道。</p>

<p><strong>Graph RAG 的取捨。</strong> 適合的情況是多跳本來就是問題的本質、實體與關聯的 schema 穩定、實體在整個語料庫裡被重複使用、答錯的代價很高 (盡職調查、法遵、臨床)。不適合的情況是單跳問答 (過度設計) 和 schema 漂移得比語料庫還快。隱藏成本是實體抽錯就會產生錯的關聯，然後走訪出錯誤的結果; 延遲要抓樸素 RAG 的 2 到 3 倍。投影片的建議是: 因為 eval 顯示單跳會漏掉才採用，不是因為它看起來有趣。</p>

<p><strong>多粒度索引。</strong> 句子層級適合精準查找和引用，但拿來當合成用的 context 很糟; 章節層級是合成脈絡的最佳選擇，只索引一種粒度的話就選它; 頁層級適合引用和當退路，本身精確度很差。實務建議是三種都索引，再依問題形狀路由或跨粒度 rerank。儲存變 3 倍，但向量很便宜，這個成本值得付。</p>

<p><strong>安全那頁值得所有人看一次。</strong> 核心觀念是: 檢索回來的內容是不可信輸入。攻擊者不需要碰到你的 prompt，他只需要在你的語料庫裡放一份文件。</p>

<p>具體要做的有四件事: 把每個檢索到的 chunk 當成使用者填的表單欄位看待，在匯入時就剝除或標記那些長得像指令的文字 (不是等到合成階段才處理); 限制模型能碰的工具，會做檢索的 agent 不應該同時具備把資料送出去的能力; 權限跟著 chunk 走，把授權 metadata 放在每個 chunk 上、檢索時就過濾掉，而不是多租戶共用一個索引然後期待 prompt 守得住; 維護一份紅隊語料庫 (裡面放刻意埋了惡意指令的文件)，每次發版都像 eval 一樣跑一次。</p>

<p>工具都是現成的 (Presidio、Lakera、Protect AI、NeMo Guardrails)，缺的是「每次檢索都跑」的紀律。</p>

<h2 id="18-星期一可以做的事">18. 星期一可以做的事</h2>

<p>投影片結尾給的行動清單只有四步，刻意做得很小:</p>

<ol>
  <li>挑一種你目前 pipeline 處理得不好的文件型別</li>
  <li>把它丟進 LlamaParse 或 LiteParse 跑一次</li>
  <li>跟你現在 parser 的輸出做比對</li>
  <li>找出一個你現在的 parser 漏掉的具體東西</li>
</ol>

<p>理由是這樣你手上會有一個具體的失敗可以對付，通常比漫無目標地優化整條 pipeline 容易得多。一次處理一種文件型別，成效通常比較好。</p>

<hr />

<p>整份投影片最後收在一句話: <strong>瓶頸換位置了。離開模型，離開檢索器，進到文件本身。</strong></p>

<p>小編覺得這份材料最大的價值不是任何單一技巧，而是它把「力氣該投在哪一層」講清楚了。RAG 的討論很容易集中在向量資料庫、embedding 模型、chunk size 這幾個題目上，但那些都是檢索層的參數。如果 parsing 這一層就已經把表格拆散、把註腳接錯位置、把圖表整個丟掉，那後面每一層調得再細，都是在錯誤的文字上做功。更麻煩的是這種錯誤在監控上完全看不出來: 它不會報錯，只會給你一個很有自信的錯答案。</p>

<p><strong>Sources:</strong></p>

<ul>
  <li>投影片: Pierre-Loïc Doulcet, <a href="https://drive.google.com/file/d/1IQ7G0aEyQQNBaxTBFkJ6YD-xvPZ5QM67/view"><em>Beyond RAG: Building Agentic Document Workflows with LlamaIndex</em></a>, Workshop @ AI Engineer Singapore 2026 (<a href="https://x.com/jerryjliu0/status/2058953208782074127">Jerry Liu 的分享推文</a>)</li>
  <li><a href="https://towardsdatascience.com/12-rag-pain-points-and-proposed-solutions-43709939a28c/">12 RAG Pain Points and Proposed Solutions</a> (Wenqi Glantz, 2024)</li>
  <li><a href="https://arxiv.org/abs/2401.05856">Seven Failure Points When Engineering a Retrieval Augmented Generation System</a> (Barnett et al., 2024)</li>
  <li><a href="https://arxiv.org/abs/2604.08538">ParseBench: A Document Parsing Benchmark for AI Agents</a> (arXiv 2604.08538) · <a href="https://www.llamaindex.ai/blog/llamaindex-and-kaggle-launch-a-new-document-ocr-leaderboard-for-ai-agents">官方發布說明</a></li>
  <li><a href="https://github.com/run-llama/liteparse">LiteParse</a> (Apache 2.0，本機執行的 PDF spatial text parser)</li>
</ul>]]></content><author><name>ihower</name></author><category term="RAG" /><category term="Workflow" /><category term="Agent" /><summary type="html"><![CDATA[LlamaIndex 的 Pierre-Loïc Doulcet 在 AI Engineer Singapore 2026 帶了一場 90 分鐘的工作坊《Beyond RAG: Building Agentic Document Workflows with LlamaIndex》，投影片全長 116 頁，由 LlamaIndex 共同創辦人 Jerry Liu 在 X 上公開: 投影片連結在這。]]></summary></entry><entry><title type="html">Matt Pocock 的 Agent Skill 設計哲學</title><link href="https://blog.aihao.tw/2026/07/25/mattpocock-skills-design-philosophy/" rel="alternate" type="text/html" title="Matt Pocock 的 Agent Skill 設計哲學" /><published>2026-07-25T00:00:00+00:00</published><updated>2026-07-25T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/25/mattpocock-skills-design-philosophy</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/25/mattpocock-skills-design-philosophy/"><![CDATA[<p>Matt Pocock 是 TypeScript 社群相當知名的教育者 (Total TypeScript)，這一年轉做 AI 工程教學。他今年 2 月開的 <a href="https://github.com/mattpocock/skills">mattpocock/skills</a> 現在超過 18 萬顆星，是目前最多人用的工程類 skill 集合之一。</p>

<p>他最近放了兩支影片，一支是這套 skills 的完整教學，另一支原本要在 AI Engineer World’s Fair 現場講，因故改成錄影。小編覺得第二支特別有料，因為它談的不是「我做了哪些 skill」，而是「怎麼判斷一個 skill 好不好」。</p>

<p>他把現在的處境叫做 skill hell: 免費的 skill 到處都是，你可以下載、可以自己寫，但你分不出好壞，也不知道它們該怎麼組在一起。缺的不是 skill，是一套判斷 skill 的共用標準。</p>

<ul>
  <li>影片一: <a href="https://www.youtube.com/watch?v=M6mYodf0dJM">mattpocock/skills: A complete AI Coding workflow, end-to-end</a></li>
  <li>影片二: <a href="https://www.youtube.com/watch?v=UNzCG3lw6O0">Building Great Agent Skills: The Missing Manual</a></li>
</ul>

<p>這套標準他自己也寫成了一個 skill 放在 repo 裡: <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/writing-great-skills</code></a>，旁邊附一份 <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/GLOSSARY.md"><code class="language-plaintext highlighter-rouge">GLOSSARY.md</code></a> 定義每個術語。小編這篇整理自這兩支影片、repo 原始碼、還有他這半年的 X 貼文。第一節先看這個 repo 有什麼，後面談設計思路。</p>

<p>以下是重點整理:</p>

<h2 id="1-mattpocockskills-是什麼">1. mattpocock/skills 是什麼</h2>

<p>這個 repo 就是他自己 <code class="language-plaintext highlighter-rouge">.agents</code> 目錄的公開版本，副標寫著「Skills for Real Engineers」(給真正的工程師用的 skills)。他在 README 裡的態度也很明確: 「拿去亂改，改成你自己的」。</p>

<p>「給真正的工程師」這句不是隨口說的。他在 <a href="https://x.com/mattpocockuk/status/2073711736838918436">X 上</a>談過「該不該讀 code」這個爭論，認為它根本不是二選一，而是一條光譜，他列了七段: 每一行 diff 都讀 → 掃過每個 diff、只細看重要的行 → 不看 diff 但理解每個 PR 的為什麼 → 抽查 PR → 不看 PR 但定期抽查 codebase → 不看程式碼、只抽查 agent 的執行軌跡 → 程式碼跟系統都不看。他自己的立場是<a href="https://x.com/mattpocockuk/status/2077459653282193756">「程式碼就是 agent 執行的環境。忽視它，agent 周圍的世界就會瓦解」</a>，所以他的 skill 有很大一部分在管程式碼本身的設計品質。</p>

<p>repo 裡總共 41 個 skill，分在好幾個資料夾。只有 <code class="language-plaintext highlighter-rouge">engineering/</code> 跟 <code class="language-plaintext highlighter-rouge">productivity/</code> 這兩個是他認可推出的，共 22 個; 其餘放在 <code class="language-plaintext highlighter-rouge">misc/</code>、<code class="language-plaintext highlighter-rouge">personal/</code>、<code class="language-plaintext highlighter-rouge">in-progress/</code>、<code class="language-plaintext highlighter-rouge">deprecated/</code>，他說那些還在實驗、以後可能會刪。</p>

<p>這 22 個照他自己的 user-invoked / model-invoked 分法是:</p>

<p><strong>工程類，你自己打指令叫的:</strong></p>

<ul>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/ask-matt/SKILL.md"><code class="language-plaintext highlighter-rouge">/ask-matt</code></a> 不知道該用哪個 skill、走哪條流程就問它，它是整組的路由</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-with-docs</code></a> 訪談式追問，同時建立專案的領域模型，更新 <code class="language-plaintext highlighter-rouge">CONTEXT.md</code> 跟 ADR</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a> 把目前的對話整理成 spec 發到 issue tracker。它不會再訪談你一次，只做整理</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-tickets/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-tickets</code></a> 把計畫或 spec 拆成一張張 tracer bullet 票，每張標明要等哪幾張先完成</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/implement/SKILL.md"><code class="language-plaintext highlighter-rouge">/implement</code></a> 照 spec 或票實作，在事先講好的接縫上用 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a>，收尾跑 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a></li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/wayfinder/SKILL.md"><code class="language-plaintext highlighter-rouge">/wayfinder</code></a> 規劃一個 session 裝不下的大工程，在 issue tracker 上做一張決策票的共用地圖</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/triage/SKILL.md"><code class="language-plaintext highlighter-rouge">/triage</code></a> 用一套狀態機把進來的 issue 推進到可以動手的狀態</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/improve-codebase-architecture/SKILL.md"><code class="language-plaintext highlighter-rouge">/improve-codebase-architecture</code></a> 掃描 codebase 找可以改善模組設計的地方，用 HTML 報告呈現</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/setup-matt-pocock-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/setup-matt-pocock-skills</code></a> 設定 issue tracker、triage 標籤、領域文件位置，每個 repo 跑一次</li>
</ul>

<p><strong>工程類，agent 也能自己叫的:</strong></p>

<ul>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a> red-green 迴圈，一次一個垂直切片</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a> 兩軸 review (規範 + 需求)，跑在兩個並行的 subagent 裡</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnosing-bugs/SKILL.md"><code class="language-plaintext highlighter-rouge">/diagnosing-bugs</code></a> 難 bug 跟效能退化的診斷流程，沒有一個會 red 的回饋迴圈就不准提假設</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/codebase-design/SKILL.md"><code class="language-plaintext highlighter-rouge">/codebase-design</code></a> deep module 的共通詞彙與設計紀律</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/domain-modeling/SKILL.md"><code class="language-plaintext highlighter-rouge">/domain-modeling</code></a> 主動挑戰、釐清專案的術語，寫進 <code class="language-plaintext highlighter-rouge">CONTEXT.md</code> 跟 ADR</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/prototype/SKILL.md"><code class="language-plaintext highlighter-rouge">/prototype</code></a> 做一個用完就丟的原型來回答某個設計問題</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/research/SKILL.md"><code class="language-plaintext highlighter-rouge">/research</code></a> 針對第一手來源查證，結果寫成帶引用的 markdown，跑在背景 agent</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/resolving-merge-conflicts/SKILL.md"><code class="language-plaintext highlighter-rouge">/resolving-merge-conflicts</code></a> 逐個 hunk 解 merge 或 rebase 衝突，絕不 <code class="language-plaintext highlighter-rouge">--abort</code></li>
</ul>

<p><strong>非程式類:</strong></p>

<ul>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-me</code></a> (你叫) 跟 <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grilling/SKILL.md"><code class="language-plaintext highlighter-rouge">/grilling</code></a> (agent 也能叫) 就是那個訪談迴圈，不限程式用途</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/handoff/SKILL.md"><code class="language-plaintext highlighter-rouge">/handoff</code></a> (你叫) 把目前的對話壓縮成一份交接文件，讓另一個 session 接手</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/teach/SKILL.md"><code class="language-plaintext highlighter-rouge">/teach</code></a> (你叫) 跨多個 session 教你一件事，用當前目錄當有狀態的教學工作區。他拿它學會了魔術方塊</li>
  <li><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/writing-great-skills</code></a> (你叫) 就是這篇文章大半內容的來源</li>
</ul>

<p>這些 skill 怎麼銜接成一條流程，第 13 節會講。不知道從哪開始就打 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/ask-matt/SKILL.md"><code class="language-plaintext highlighter-rouge">/ask-matt</code></a>，它會依你當下的狀況指出該走哪條路。</p>

<p>最後補一個他常被問、也常被誤會的問題: 「怎麼讓你的 skill 支援 Jira / Linear / beads?」答案是<strong>本來就支援</strong>。跑 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/setup-matt-pocock-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/setup-matt-pocock-skills</code></a> 的時候跟 agent 說「幫我設成 Jira」就好。skill 讀的是 repo 裡那份本地設定，而不是寫死的整合，所以任何有 CLI 的 issue tracker 它都接得上。</p>

<h2 id="2-skill-存在的目的是從隨機的系統得到可預測的流程">2. skill 存在的目的，是從隨機的系統得到可預測的流程</h2>

<p><a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/writing-great-skills</code></a> 第一句話就給了定義:</p>

<blockquote>
  <p>skill 存在的目的，是從一個隨機的系統裡硬是逼出確定性。</p>
</blockquote>

<p>這裡的「可預測」有一個明確的限定: 指的是 agent 每次走<strong>同樣的流程</strong>，不是每次產出同樣的結果。GLOSSARY 特別註明，一個 brainstorming skill 應該要「可預測地發散」，它的 token 每次都不一樣，但行為不變。</p>

<p>這個定義決定了後面所有取捨。對他來說，成本跟可維護性都不是跟可預測性並列的目標，而是它的附帶結果。</p>

<h2 id="3-每一個-skill-都對準一個軟體工程的老問題">3. 每一個 skill 都對準一個軟體工程的老問題</h2>

<p>README 有一段定位講得很直接: GSD、BMAD、Spec-Kit 這類做法會幫你把整個流程接管過去，但接管的同時也拿走了你的控制權，而且流程本身出問題時很難查。他要的是小、好改、可組合。</p>

<p>接著他把整套 skill 對應到四個軟體工程的老問題，每個都配一段經典引文:</p>

<ul>
  <li><strong>agent 做出來的不是我要的</strong>。引 The Pragmatic Programmer 的「沒有人確切知道自己要什麼」。他認為這件事在 AI 時代沒有改變，只是溝通落差換了對象。對策是 grilling session，讓 agent 反過來訪問你。</li>
  <li><strong>agent 講話太囉嗦</strong>。引 Eric Evans 的 Domain-Driven Design。agent 被丟進一個專案，術語只能邊做邊猜，於是一件事要用 20 個字講完。對策是 <code class="language-plaintext highlighter-rouge">CONTEXT.md</code> 這份共通詞彙表。他舉的前後對照很清楚: 沒有詞彙表時得說「course 的 section 裡的 lesson 被『變成真的』(也就是在檔案系統裡拿到位置) 的時候會出問題」，有了詞彙表就只要說「materialization cascade 會出問題」。</li>
  <li><strong>程式不能動</strong>。引 The Pragmatic Programmer 的「回饋的速度就是你的速度上限」。對策是 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a> 的 red-green 迴圈跟 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnosing-bugs/SKILL.md"><code class="language-plaintext highlighter-rouge">/diagnosing-bugs</code></a>。</li>
  <li><strong>做出一個大泥球 (ball of mud)</strong>。引 Kent Beck 跟 Ousterhout 的 deep module。他認為 agent 在加快寫程式的同時也加快了軟體的熵增，所以 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/improve-codebase-architecture/SKILL.md"><code class="language-plaintext highlighter-rouge">/improve-codebase-architecture</code></a> 建議每隔幾天就跑一次。</li>
</ul>

<p>小編覺得這組對應就是他設計哲學的核心: 他不是在發明 agent 時代的新流程，而是把已經驗證幾十年的工程紀律翻譯成 agent 讀得懂的格式。這也解釋了為什麼他的 skill 裡到處是 seam、tracer bullet、vertical slice、deep module 這些詞: 它們既是好的工程術語，也是好的 leading word (第 8 節會講這是什麼)。</p>

<p>同一個立場延伸出另一個判斷: <a href="https://x.com/mattpocockuk/status/2079506509411635549">greenfield 跟 brownfield 的區分已經不真實了</a>。他常被問「你的 skills 在全新專案上能用嗎」，他的回答是差別只在你的 repo 有沒有設好慣例、有沒有前例可循; 而在程式碼產出速度這麼快的現在，一個新 repo 能維持「新」的狀態頂多兩天到一週。預設就當它是 legacy codebase，即使它才一週大。</p>

<h2 id="4-trigger-兩種呼叫方式兩種完全不同的成本">4. Trigger: 兩種呼叫方式，兩種完全不同的成本</h2>

<p>從這節開始進入他那份檢查清單的四個項目。第一項是「這個 skill 怎麼被叫起來」，只有兩種選擇:</p>

<ul>
  <li><strong>model-invoked</strong>: 保留 <code class="language-plaintext highlighter-rouge">description</code>。agent 看得到、可以自己決定要不要用，別的 skill 也可以呼叫它。代價是這段 description 每一輪都在 context 裡，這個成本他叫 <strong>context load</strong>。</li>
  <li><strong>user-invoked</strong>: 在 frontmatter 加 <code class="language-plaintext highlighter-rouge">disable-model-invocation: true</code> (Codex 那邊是 <code class="language-plaintext highlighter-rouge">agents/openai.yaml</code> 裡的 <code class="language-plaintext highlighter-rouge">policy.allow_implicit_invocation: false</code>)。agent 看不到這段 description，只有人打指令才叫得動，別的 skill 也叫不動。context load 是零，但成本改由人承擔: 你自己就是那個索引，你得記得它存在、記得什麼時候該用。這個成本他叫 <strong>cognitive load</strong>。</li>
</ul>

<p>小編覺得他這裡最好的一句話是: cognitive load <strong>不是一個要最小化的成本，而是人保有主導權的代價</strong>。該讓人判斷的地方就花這個成本，不該的地方才拿掉。這跟一般直覺相反，多數人會預設「能自動就自動」。</p>

<h2 id="5-他為什麼偏好-user-invoked">5. 他為什麼偏好 user-invoked</h2>

<p><img src="/assets/images/mattpocock-skills/vs-superpowers.jpg" alt="" /></p>

<p>他常被問的一個問題是「你的 skills 跟 <a href="https://github.com/obra/superpowers">superpowers</a> 差在哪」，答案就在這個軸上: superpowers 以 model-invoked 為主，他的以 user-invoked 為主。他自己在 <a href="https://x.com/mattpocockuk/status/2077789613691699629">X 上</a>的總結是:</p>

<blockquote>
  <p>Superpowers 是給 agent 超能力，我的 skills 是給你超能力。</p>
</blockquote>

<p>他的理由是這樣: 每多一個 model-invoked skill，就多了一個 context pointer，而 pointer 有機率不被跟隨。就算這個 skill 完全符合當下的任務，模型還是可能不叫它。與其想辦法提高命中率，他寧可讓這一整類問題不存在。他還補了一個實務上的考量: 這種不確定性會逼你去替 skill 做 eval，確認它有在對的時機被觸發，那是他寧願避開的麻煩。</p>

<p>代價是他自己得記得這幾十個 skill，這點他也承認。</p>

<p><img src="/assets/images/mattpocock-skills/context-660-tokens.jpg" alt="" /></p>

<p>換來的是什麼? 影片裡安裝器列出 38 個 skill (錄影當時的數量)，他把官方那批全裝上，跑 <code class="language-plaintext highlighter-rouge">/context</code>，skills 那一欄只佔 <strong>660 tokens</strong>。</p>

<p>user-invoked skill 一多，人就記不住了。他的解法是加一個 <strong>router skill</strong>: 一個 user-invoked skill，內容就是告訴你有哪些 skill、什麼時候該用哪個，也就是 repo 裡的 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/ask-matt/SKILL.md"><code class="language-plaintext highlighter-rouge">/ask-matt</code></a>。要注意 router 只能告訴你有什麼，不能替你叫起來，因為 user-invoked skill 沒有 description，誰都叫不動它。</p>

<p>還有一條分工規則: <strong>user-invoked 可以呼叫 model-invoked，但不能呼叫另一個 user-invoked</strong>。這條規則直接決定了他 repo 的分層: user-invoked 是編排層 (<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-with-docs</code></a>、<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a>、<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/implement/SKILL.md"><code class="language-plaintext highlighter-rouge">/implement</code></a>)，model-invoked 是可重複使用的紀律層 (<a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grilling/SKILL.md"><code class="language-plaintext highlighter-rouge">/grilling</code></a>、<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a>、<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a>、<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/codebase-design/SKILL.md"><code class="language-plaintext highlighter-rouge">/codebase-design</code></a>)。</p>

<p>同一個偏好也解釋了他為什麼<a href="https://x.com/mattpocockuk/status/2066612676520779867">不信任自動的 self-improvement 迴圈</a>，也就是自動產生的記憶、每次 session 結束自動加進 <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> 的建議。他的理由有兩層: 建議本身常常很爛; 就算建議是好的，agent 也常常過度依賴它們，讓整個東西變得難以引導。而且這些記憶是 per-project 的，每個專案會用它自己的方式變得難以引導。他想把這個現象叫做 instruction rot。</p>

<blockquote>
  <p>編按: 這個取捨沒有標準答案，他自己也說兩邊各有各的成本，不是容易的決定。但小編覺得值得注意的是，他把「要不要讓模型自動觸發」當成一個要主動設計的參數，而不是預設值。</p>
</blockquote>

<h2 id="6-description-要寫得比本體更省">6. description 要寫得比本體更省</h2>

<p><img src="/assets/images/mattpocock-skills/description-example.jpg" alt="" /></p>

<p>description 只有 model-invoked 才要寫。它常駐在 context 裡，每個字的成本比本體高，所以要刪得更徹底。他的三條規則:</p>

<ul>
  <li><strong>把 leading word 放到最前面</strong>。description 就是這個詞做觸發工作的地方。</li>
  <li><strong>一個 branch 一個 trigger</strong>。用同義詞換句話說同一件事就是 duplication，例如「build features using TDD… asks for test-first development」其實是同一個 branch 寫了兩次，該合併。</li>
  <li><strong>本體已經講過的身份說明就刪掉</strong>。description 只留觸發條件，加上「當別的 skill 需要⋯」這種被呼叫的條件。</li>
</ul>

<p>成效有數字可以看: 他在 <a href="https://x.com/mattpocockuk/status/2067259590488510471">v1 的發布說明</a>裡提到，這一輪整理讓 skill description 的 token 成本降了 63%。</p>

<h2 id="7-structure-stepsreference和三層的資訊階層">7. Structure: steps、reference，和三層的資訊階層</h2>

<p>第二項檢查是內部結構。他認為 skill 只由兩種東西組成:</p>

<ul>
  <li><strong>steps</strong>: 照順序做的動作，每個 step 結束在一個「完成標準」上。</li>
  <li><strong>reference</strong>: 隨時查的定義、規則、範本、參數。</li>
</ul>

<p>兩種可以任意搭配。<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a> 幾乎全是 reference，<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a> 是 steps 加一份範本，也有 skill 兩種都有。</p>

<p><img src="/assets/images/mattpocock-skills/context-pointer.jpg" alt="" /></p>

<p>接著這些內容排在一個三層的<strong>資訊階層</strong>上，依 agent 需要它的急迫程度排序:</p>

<div style="display:flex;flex-direction:column;gap:10px;margin:1.6em 0"><div style="border:1px solid var(--accent);border-radius:8px;padding:14px 16px;background:var(--tag-bg)"><div style="font-weight:600">1. SKILL.md 裡的 step</div><div style="font-size:.92em;color:var(--text-secondary);margin-top:4px">最上層。agent 要做什麼、按什麼順序做。每個 step 有一個可判定的完成標準。</div></div><div style="border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--surface);margin-left:24px"><div style="font-weight:600">2. SKILL.md 裡的 reference</div><div style="font-size:.92em;color:var(--text-secondary);margin-top:4px">次要層。隨時查的定義與規則。很多時候本來就該是平的一組同級規則，這不是壞味道。</div></div><div style="border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--bg);margin-left:48px"><div style="font-weight:600">3. 推到外部檔案的 reference</div><div style="font-size:.92em;color:var(--text-secondary);margin-top:4px">用 context pointer 指過去，只有 pointer 被觸發時才載入。可以是 skill 資料夾裡的兄弟檔 (例如 GLOSSARY.md)，也可以是完全在 skill 系統外、任何 skill 都能指的一般檔案。</div></div></div>

<p><strong>要不要往下推，判準不是「檔案太長」，而是 branch。</strong> 這是小編覺得整套結構觀念裡最實用的一條。一個 skill 如果有多種用法，每種用法就是一個 branch。<strong>每條 branch 都會用到的留在 SKILL.md 裡，只有部分 branch 用得到的推出去。</strong></p>

<p>他舉的兩個例子剛好對照。<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a> (演講時還叫 <code class="language-plaintext highlighter-rouge">/to-prd</code>) 只有一條路徑，永遠會寫 spec、永遠會確認測試接縫，所以「什麼是測試接縫」跟「spec 範本」兩份 reference 都留在本體。而 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/domain-modeling/SKILL.md"><code class="language-plaintext highlighter-rouge">/domain-modeling</code></a> 會做兩件事 (更新詞彙表、寫 ADR)，也可能兩件都不做，等於有兩到三條 branch，所以 ADR 範本跟詞彙表範本都推到外部檔案，本體只留一行 pointer 說「如果需要範本，去這個檔案」。</p>

<p>repo 版本比演講多寫了兩點:</p>

<ul>
  <li><strong>共置 (co-location)</strong>。階層決定一段內容放在第幾層，共置決定它旁邊放什麼。一個概念的定義、規則、例外要放在同一個標題底下，讀到其中一段就會順便帶到相鄰的部分。</li>
  <li><strong>pointer 沒被跟隨的時候，先修措辭</strong>。他寫得很明確: 決定 agent 什麼時候去讀、讀得多可靠的是 <strong>pointer 的措辭，不是它指向什麼</strong>。一份必讀的材料放在 pointer 後面卻常常被跳過，那是措辭問題，先改措辭，改不動才搬回本體。</li>
</ul>

<h2 id="8-steering-的核心-leading-words">8. Steering 的核心: leading words</h2>

<p><img src="/assets/images/mattpocock-skills/leading-word.jpg" alt="" /></p>

<p>第三項檢查是 steering，也就是怎麼讓 agent 真的照你想的做。他說這是整場演講他最想給的一件事。</p>

<p><strong>leading word (Leitwort)</strong> 是一個已經存在於模型預訓練裡的壓縮概念。你把它放進 skill 的文字裡，agent 會在思考過程跟輸出裡把這個詞複述出來，複述的同時就改變了行為。它用最少的 token 讓一整類行為固定下來，因為它調用的是模型本來就有的先驗。</p>

<p>他舉的例子是一個很常見的通病: agent 習慣一層一層寫程式，先寫完整個資料庫層、再寫完 schema、再寫完 API、最後才寫前端，不會像人一樣先做出一小塊能動的東西然後求回饋。你當然可以寫「不要一層一層做，先做出一小塊完整能動的東西再往外擴」。但更好的做法是直接用「<strong>vertical slice (垂直切片)</strong>」這個詞: 這是開發圈本來就通行的說法，模型的先驗會被叫起來。</p>

<p>這個技巧有兩件事值得注意:</p>

<ol>
  <li><strong>它是可驗證的。</strong> 你在 skill 裡寫了 vertical slice，就去 reasoning trace 裡看 agent 有沒有把這個詞說回來。有，代表這個詞有效; 沒有，代表它不夠強。</li>
  <li><strong>它同時作用在觸發上。</strong> 當同一個詞出現在你的 prompt、你的文件、你的程式碼裡，agent 會把這個共通語言連到那個 skill，觸發得更準。所以 description 要用你「真的會講的那些詞」來寫。</li>
</ol>

<p>自創的詞也可以用，但它沒有先驗可以調用，你得多花 token 去定義。優先找既有的詞。</p>

<p>repo 裡把這件事做得很徹底: 幾乎每個術語底下都附一行 <code class="language-plaintext highlighter-rouge">_Avoid_:</code>，列出<strong>不該用的同義詞</strong>。<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/codebase-design/SKILL.md"><code class="language-plaintext highlighter-rouge">/codebase-design</code></a> 的詞彙表直接寫「這些詞要照字面用，不要換成 component、service、API 或 boundary，語言一致就是整件事的重點」; <code class="language-plaintext highlighter-rouge">seam</code> 那條註明「不要用 boundary，這個詞跟 DDD 的 bounded context 撞在一起」。光是「我用了這個詞」還不夠，還要確保它的意思不會被一堆同義詞換來換去而變模糊。</p>

<h2 id="9-完成標準決定-agent-做多少前置工作">9. 完成標準決定 agent 做多少前置工作</h2>

<p>steering 的第二個手段是「完成標準 (completion criterion)」，他拆成兩個軸:</p>

<ul>
  <li><strong>清晰度</strong>: agent 分不分得出做完了沒? 模糊的標準 (例如「達成共識」) 會讓它宣稱做完然後進到下一步，這叫 <strong>premature completion</strong>。</li>
  <li><strong>要求強度</strong>: 標準要求多高，決定 agent 願意做多少 legwork。legwork 指的是它在一個 step 裡自己去讀檔案、探索程式碼、把需要的資訊查出來，而不是丟回來問人。「每一個被改到的 model 都要有交代」跟「產出一份變更清單」，agent 做的前置工作深度差很多。</li>
</ul>

<p><img src="/assets/images/mattpocock-skills/split-steps.jpg" alt="" /></p>

<p>他拿 plan mode 當反例，而且說他試過的每一種 plan mode 實作都有這個問題: plan mode 有兩步，「問澄清問題」跟「產出計畫」。agent 看得到終點是產出計畫，所以澄清問題那步永遠做得敷衍，問你兩三題就急著開始寫計畫。</p>

<p>他的解法是拆成兩個 skill: <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-with-docs</code></a> 負責問，結束之後你才手動叫 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a>。<strong>agent 一次只看得到一步</strong>，看不到後面，就不會提早結束當前這一步。</p>

<p>repo 版本比演講多補了兩點，都很重要:</p>

<ul>
  <li><strong>順序是先把完成標準寫明確，再考慮拆。</strong> 改標準便宜又局部; 只有當標準怎麼寫都模糊、而且你<strong>真的觀察到</strong>它在趕的時候，才動手拆。</li>
  <li><strong>藏後續步驟只在真正的 context 邊界才有效。</strong> 交給人手動接續、或丟給 subagent 才算。在同一個 context 裡 inline 呼叫一個 model-invoked skill，後面的步驟還留在 context 裡，什麼都沒藏到。</li>
</ul>

<h2 id="10-不要用禁止句">10. 不要用禁止句</h2>

<p>steering 還有一個獨立的失敗模式叫 <strong>negation</strong>。用禁令來引導會有反效果，因為禁令把被禁的行為帶進了 context，反而讓它更容易出現。他的說法是「不要想大象」: 講完這句，大象就是唯一在場的東西。</p>

<blockquote>
  <p>「不要寫冗長的註解」，而模型剛剛讀到的模式就是冗長。</p>
</blockquote>

<p>修法是<strong>改成正面描述目標行為</strong>，讓被禁的行為根本不被提到 (「註解寫一行」)。真的只能用禁令當硬性護欄時，也要配一句「那該怎麼做」，讓注意力落在該做的事情上。</p>

<h2 id="11-pruning-no-op-測試跟另外三種讓-skill-變長的原因">11. Pruning: no-op 測試，跟另外三種讓 skill 變長的原因</h2>

<p><img src="/assets/images/mattpocock-skills/no-ops.jpg" alt="" /></p>

<p>第四項檢查是刪。他在 <a href="https://x.com/mattpocockuk/status/2069784839474032896">X 上</a>把 no-op 講得最直白:</p>

<blockquote>
  <p>看一下你最喜歡的那個 skill。檢查有沒有這種句子:「commit message 要寫得很詳細」「要徹底」「實作要好讀」。這些句子的共通點是: 它們是 no-op，對 agent 的行為毫無影響。Agent 本來就會寫好的 commit message、本來就會試著徹底、本來就會試著寫好讀的實作。刪掉試試，輸出有變嗎? 沒有? 那這行就是 no-op。Agent 自己寫的 skill 尤其滿是 no-op。</p>
</blockquote>

<p>有兩個延伸判斷，小編覺得比 no-op 本身更有價值:</p>

<ul>
  <li><strong>no-op 是相對於「模型的預設行為」，不是相對於讀者。</strong> 所以兩個人爭論某一行是不是 no-op，其實是在爭論模型的預設行為長什麼樣。這件事該用跑一次來解決，不是用辯的。</li>
  <li><strong>太弱的 leading word 本身就是 no-op。</strong> agent 本來就還算徹底，你寫「be thorough」等於沒寫。修法是換一個更強的詞 (relentless)，不是換一種技巧。順帶一提，<a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grilling/SKILL.md"><code class="language-plaintext highlighter-rouge">/grilling</code></a> 的本體真的就是用 <code class="language-plaintext highlighter-rouge">relentlessly</code> 這個詞。</li>
</ul>

<p>另外三種讓 skill 變長的原因，各有各的修法:</p>

<ul>
  <li><strong>duplication (同一個意思出現在兩個地方)</strong>: 除了維護成本跟 token 成本，還有一個少人提的副作用: 重複會<strong>不當抬高這個意思在資訊階層上的位階</strong>，讓它看起來比實際重要。它剛好是 leading word 的反面: leading word 是刻意重複同一個<strong>詞</strong>來提高注意力，duplication 是不小心重複了同一個<strong>意思</strong>。</li>
  <li><strong>sediment (沉積)</strong>: 多人共同維護一份文件的預設結局。每個人都往裡面加自己的東西，沒有人有把握去刪別人寫的，內容越積越多。修法是先看結構，把只屬於某條 branch 的東西移到那條 branch 去，過期的直接刪掉。</li>
  <li><strong>sprawl (單純太長)</strong>: 每一行都還有效、都不重複，但整份就是太長。修法就是資訊階層: 該推到下層的推出去，該按 branch 拆的拆掉。</li>
</ul>

<h2 id="12-他的-skill-實際上有多小">12. 他的 skill 實際上有多小</h2>

<p>理論講完，看實際的檔案最有說服力。這是 <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-me</code></a> 的<strong>全文</strong>:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">grill-me</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">A relentless interview to sharpen a plan or design.</span>
<span class="na">disable-model-invocation</span><span class="pi">:</span> <span class="kc">true</span>
<span class="nn">---</span>

Run a <span class="sb">`/grilling`</span> session.
</code></pre></div></div>

<p>含 frontmatter 20 個英文字。<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-with-docs</code></a> 34 個字，多的部分是「用 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/domain-modeling/SKILL.md"><code class="language-plaintext highlighter-rouge">/domain-modeling</code></a> skill 一起跑」。<a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/implement/SKILL.md"><code class="language-plaintext highlighter-rouge">/implement</code></a> 70 個字，內容是「照 spec 或 tickets 做、盡量在事先講好的接縫上用 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/tdd/SKILL.md"><code class="language-plaintext highlighter-rouge">/tdd</code></a>、常跑型別檢查、最後跑 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a>、commit」。</p>

<p>真正有內容的是被它們呼叫的 model-invoked skill。<a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/grilling/SKILL.md"><code class="language-plaintext highlighter-rouge">/grilling</code></a> 全文 136 個字:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Interview me relentlessly about every aspect of this until we reach a shared
understanding. Walk down each branch of the decision tree, resolving dependencies
between decisions one-by-one. For each question, provide your recommended answer.

Ask the questions one at a time, waiting for feedback on each question before
continuing. Asking multiple questions at once is bewildering.

If a <span class="ge">*fact*</span> can be found by exploring the environment (filesystem, tools, etc.),
look it up rather than asking me. The <span class="ge">*decisions*</span>, though, are mine — put each
one to me and wait for my answer.

Do not act on it until I confirm we have reached a shared understanding.
</code></pre></div></div>

<p>(中譯: 針對這件事的每一個面向不留情地訪問我，直到我們達成共識。走過決策樹的每一條分支，一個一個解掉決策之間的相依。每個問題都給出你建議的答案。一次問一個問題，等我回覆再繼續，一次丟一堆問題會讓人不知所措。如果一個<strong>事實</strong>可以靠探索環境 (檔案系統、工具等) 找到，就自己去查，不要問我。但<strong>決策</strong>是我的，一個一個丟給我，等我回答。在我確認達成共識之前，不要動手。)</p>

<p>這就是「user-invoked 負責編排、model-invoked 負責紀律」的具體樣子，也是「我的 skill 為什麼這麼小」的答案。他自己列的三個理由是: 短的好稽核 (所以才信得過)、好維護、跑起來便宜。</p>

<p>不過他的長 skill 也確實存在: <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/wayfinder/SKILL.md"><code class="language-plaintext highlighter-rouge">/wayfinder</code></a> 兩千多字、<a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/writing-great-skills</code></a> 一千五百多字。所以他的標準不是「一律要短」，而是「每一行都得改變 agent 的行為」。</p>

<h2 id="13-主流程長什麼樣">13. 主流程長什麼樣</h2>

<p><img src="/assets/images/mattpocock-skills/main-flow.jpg" alt="" /></p>

<p>第一支影片走完整條流程，就是這五步:</p>

<p><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/SKILL.md"><code class="language-plaintext highlighter-rouge">/grill-with-docs</code></a> → <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-spec/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-spec</code></a> → <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/to-tickets/SKILL.md"><code class="language-plaintext highlighter-rouge">/to-tickets</code></a> → <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/implement/SKILL.md"><code class="language-plaintext highlighter-rouge">/implement</code></a> → <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a></p>

<p>幾個設計細節值得參考:</p>

<ul>
  <li><strong>前三步要在同一個沒中斷過的 context window 裡跑完。</strong> 不要 compact 也不要 clear，讓訪談、spec、tickets 建立在同一份思考上。上限是他說的 smart zone: 影片裡他抓 140k，repo 的 <a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/ask-matt/SKILL.md"><code class="language-plaintext highlighter-rouge">/ask-matt</code></a> 寫約 120k，超過就開始出現注意力衰退。快到上限就用 <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/handoff/SKILL.md"><code class="language-plaintext highlighter-rouge">/handoff</code></a> 換一個新對話，不要硬撐著在退化的狀態下往下做。</li>
  <li><strong>spec 是終點，tickets 是路線。</strong> 他在 <a href="https://x.com/mattpocockuk/status/2079926855788855524">X 上</a>把這件事講得最清楚: 分成兩份文件的好處是，要改方向時你只改 spec、刪掉還沒做的 tickets，不用重寫一整份計畫。每個 ticket 剛好一個 context window 的大小，做完一個就 clear。</li>
  <li><strong><a href="https://github.com/mattpocock/skills/blob/main/skills/engineering/code-review/SKILL.md"><code class="language-plaintext highlighter-rouge">/code-review</code></a> 一定跑 subagent。</strong> 主 agent 剛剛才把那段程式寫出來，它會覺得自己寫得很好，改不動也審不動。開一份乾淨的 context 才審得出東西。</li>
  <li><strong>review 分兩軸，而且不合併。</strong> 一軸是 Standards: 符不符合這個 repo 自己寫的規範，另外永遠附帶一組 Martin Fowler 的 code smell 當基本檢查 (repo 的規範優先，衝突時以 repo 為準; 工具已經在檢查的就跳過)。另一軸是 Spec: 有沒有真的做到需求要的東西，以及有沒有做多餘的。兩軸並行跑在各自的 subagent 裡免得互相干擾，最後並排呈現，<strong>不重新排名也不合併</strong>。理由很直白: 完全符合規範但做錯東西、跟做對東西但違反慣例，這兩種情況都很常見，合併排名會讓一軸遮住另一軸。</li>
  <li><strong><a href="https://x.com/mattpocockuk/status/2077329609381581156">effort 從低往高調，不是從高往低</a>。</strong> 這條不屬於流程本身，但會影響上面每一步。大部分人拿到新模型會先開高 effort、不行再降，他認為順序反了: effort 本質上就是丟更多 token 進去，這在 benchmark 上很划算 (多花 20% token 換 2% 分數對行銷很好看)，但在探索 repo、改一個測試這種日常任務上就只是浪費，而且 token 耗得越多，注意力衰退來得越快。他還提醒: 講你用什麼模型的時候<strong>請一起講 effort 等級</strong>，同一個模型不同 effort 行為完全不同。</li>
</ul>

<h2 id="14-repo-本身的幾個做法也值得學">14. repo 本身的幾個做法也值得學</h2>

<p>除了 skill 之外，這個 repo 在組織上有幾個做法小編覺得同樣有用:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">.out-of-scope/</code> 資料夾</strong>: 把「被拒絕的功能請求」跟「為什麼拒絕」寫成文件。例如有人開 issue 說「Codex 一口氣問了我 200 個問題」，要求給 grilling 加問題數量上限。他的回覆文件寫得很好: 加上限會混淆兩種不同的失敗，一種是計畫本身真的沒講清楚所以該多問 (這是正常運作)，另一種是模型問了重複或低價值的問題 (這是 prompt 品質問題)，後者的修法在 prompt 裡，不在計數器裡。</li>
  <li><strong>ADR 三原則</strong> (<a href="https://x.com/mattpocockuk/status/2077092609038897459">來自他的一則回覆</a>): 決定的當下就寫，正在討論就直接寫進 codebase，不開 PR 直接進 main; 不要怕刪掉或標記過期，一份死掉的 ADR 對誰都沒好處; 只有當一個決定「難以回頭、沒有脈絡的人會覺得奇怪、而且是真的有取捨」時才寫。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">CONTEXT.md</code> 當專案詞彙表</strong>: 每個詞附定義加 <code class="language-plaintext highlighter-rouge">_Avoid_</code>，還有一節 <code class="language-plaintext highlighter-rouge">Flagged ambiguities</code> 記錄「哪個詞曾經同時代表兩件事，後來怎麼解決的」。他在 README 裡說這可能是整個 repo 最好用的技巧。好處不只是 agent 講話變短: 變數、函式、檔案的命名會跟著一致，agent 在 codebase 裡也更好導航，思考用掉的 token 也更少。</li>
</ul>

<h2 id="15-他這套跟主流的-skill-寫法差在哪">15. 他這套跟主流的 skill 寫法差在哪</h2>

<p>前面十四節都在講他自己的主張，小編也擴大搜尋把目前主流的 skill 寫作建議看了一輪 (<a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices">Anthropic 官方的 skill authoring best practices</a>、<a href="https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills">Anthropic 工程部落格談 Agent Skills 那篇</a>、<a href="https://research.perplexity.ai/articles/designing-refining-and-maintaining-agent-skills-at-perplexity">Perplexity 團隊寫的 skill 維護經驗</a>，加上一批社群指南)，放在一起比有三個地方明顯不一樣:</p>

<ol>
  <li><strong>主流在教你怎麼讓模型選對 skill，他直接把模型觸發關掉。</strong> 官方那份 best practices 有很大篇幅在教 description 怎麼寫，前提是「Claude 要從可能上百個 skill 裡挑出對的那一個」，整份文件沒提過關閉模型觸發這個選項。他反過來做: 22 個核心 skill 有 13 個設了 <code class="language-plaintext highlighter-rouge">disable-model-invocation: true</code>，你會親手叫的那條主流程從頭到尾都是這種; 模型能自己叫的只剩下 TDD、code review、領域建模這些紀律型的 skill。(第 4、5 節)</li>
  <li><strong>主流用長度決定要不要拆檔案，他用分支決定。</strong> 官方建議是「SKILL.md 本體控制在 500 行以內，接近上限就拆成獨立檔案」。他不看長度，看的是這段內容在當前這條路上是不是必經步驟; 而且他認為真正決定 agent 會不會去讀那個檔案的，是指標那句話怎麼寫，不是檔案放在哪裡。(第 7 節)</li>
  <li><strong>主流傾向持續累加，他把「刪」列為四個檢查項之一。</strong> Perplexity 團隊的結論是「skill 基本上只增不減，gotchas 那一節長期下來累積的價值最高」，而且認為負面範例是高訊號內容。他兩點都反過來: skill 會因為 no-op、重複、沉積、過長慢慢失效，定期刪掉是維護的一部分，禁止句本身則被他列為一種失敗模式。(第 10、11 節)</li>
</ol>

<p>第 3 點是唯一一個兩邊建議完全相反的，小編覺得差別來自使用情境不同。Perplexity 的 skill 跑在自家產品裡給模型用，有 eval 撐著，只有工程師會讀，往 gotchas 一直加是合理的; 他的 skill 是你自己每天打指令叫起來的，你也得讀得懂、記得住，長了就不好用。所以在挑要不要照誰的建議之前，先想清楚你的 skill 是哪一種。</p>

<h2 id="拿去檢查自己的-skill">拿去檢查自己的 skill</h2>

<p>四項檢查收攏成一份可以直接對照的清單:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:1.6em 0"><div style="flex:1 1 300px;border:1px solid var(--accent);border-radius:8px;padding:16px;background:var(--tag-bg)"><div style="font-weight:600;margin-bottom:10px">1. Trigger 怎麼被叫起來</div><div style="font-size:.92em;line-height:1.75;color:var(--text-secondary)">☐ 這個 skill 需要 agent 自己找到它嗎? 不需要就設 user-invoked，省下 context load<br />☐ description 有沒有把 leading word 放最前面<br />☐ description 裡的觸發條件有沒有一個 branch 寫成兩句<br />☐ user-invoked 多到記不住時，加一個 router skill</div></div><div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:16px;background:var(--surface)"><div style="font-weight:600;margin-bottom:10px">2. Structure 內容怎麼排</div><div style="font-size:.92em;line-height:1.75;color:var(--text-secondary)">☐ 分得出哪些是 steps、哪些是 reference 嗎<br />☐ 這個 skill 有幾條 branch? 只有部分 branch 用得到的材料推去外部檔案<br />☐ 一個概念的定義、規則、例外有沒有放在同一個標題底下<br />☐ pointer 後面的必讀材料常被跳過? 先改 pointer 的措辭</div></div><div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:16px;background:var(--surface)"><div style="font-weight:600;margin-bottom:10px">3. Steering 怎麼讓它照做</div><div style="font-size:.92em;line-height:1.75;color:var(--text-secondary)">☐ 有沒有一段話可以收攏成一個 leading word<br />☐ 去 reasoning trace 裡確認那個詞有被複述回來<br />☐ 每個 step 的完成標準判定得出來嗎? 要求夠不夠高?<br />☐ agent 老是趕著結束當前步驟 → 先修完成標準，真的不行才拆成兩個 skill<br />☐ 有沒有禁止句可以改寫成正面描述</div></div><div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:16px;background:var(--surface)"><div style="font-weight:600;margin-bottom:10px">4. Pruning 刪到不能再刪</div><div style="font-size:.92em;line-height:1.75;color:var(--text-secondary)">☐ 一句一句做 no-op 測試: 刪掉這句，行為有變嗎?<br />☐ 沒變就整句刪，不要只修字<br />☐ 同一個意思有沒有出現在兩個地方<br />☐ 有沒有沒人動手刪掉的沉積內容<br />☐ 每行都有效但整份還是太長 → 回頭看 branch 跟資訊階層</div></div></div>

<div style="font-size:.85em;color:var(--text-secondary);text-align:right;margin-top:-.8em">✏️ 小編整理自 <code>/writing-great-skills</code></div>

<p>小編覺得這整套東西最值得學的，其實不是那 22 個 skill，而是他把「寫 skill」本身當成一個工程問題在處理: 有專屬詞彙、有失敗模式分類、有檢查順序。GLOSSARY 裡每個失敗模式都被放在治它的那個手段旁邊 (premature completion 放在 completion criterion 底下、negation 放在 steering 底下)，這種編排本身就是他講的共置原則用在自己身上。</p>

<p>他<a href="https://x.com/mattpocockuk/status/2080279667479585173">最近在想</a>把 <a href="https://github.com/mattpocock/skills/blob/main/skills/productivity/writing-great-skills/SKILL.md"><code class="language-plaintext highlighter-rouge">/writing-great-skills</code></a> 改名成 <code class="language-plaintext highlighter-rouge">/writing-for-agents</code>，因為同一套方法對 AGENTS.md、對專案文件一樣成立。這個念頭透露了一件事: skill 沒有什麼特殊之處，它就是「寫給 agent 看的文件」。而寫給 agent 看的文件跟寫給人看的文件，優化目標本來就不同。人讀到重複的叮嚀會覺得慎重，模型讀到的卻是同一個意思在資訊階層上被抬高了位階; 人讀到「請務必徹底」會提高警覺，模型本來就會試著徹底，那行字只是佔位置。</p>]]></content><author><name>ihower</name></author><category term="Skill" /><category term="Coding" /><category term="Agent" /><category term="Context Engineering" /><summary type="html"><![CDATA[Matt Pocock 是 TypeScript 社群相當知名的教育者 (Total TypeScript)，這一年轉做 AI 工程教學。他今年 2 月開的 mattpocock/skills 現在超過 18 萬顆星，是目前最多人用的工程類 skill 集合之一。]]></summary></entry><entry><title type="html">倢愷 Oscar 的 Agent Harness 四部曲導讀: 從 function call 到 LLM Wiki</title><link href="https://blog.aihao.tw/2026/07/25/rethinking-agent-harness-series/" rel="alternate" type="text/html" title="倢愷 Oscar 的 Agent Harness 四部曲導讀: 從 function call 到 LLM Wiki" /><published>2026-07-25T00:00:00+00:00</published><updated>2026-07-25T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/25/rethinking-agent-harness-series</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/25/rethinking-agent-harness-series/"><![CDATA[<p>倢愷 Oscar 的「Rethinking Agent Harness，如何更好設計 LLM Agent」系列四篇讀完，小編覺得是近期中文圈少見的一組硬派文章，蠻推薦寫 LLM application 的人找時間讀一遍。它不教你怎麼用某個 framework，而是一路在問同一件事: 這個設計解決了 LLM 哪一個限制? 又在什麼條件下會失效?</p>

<p>作者對現在 Harness Engineering 的討論有個判斷蠻準的: 很像 2023 到 2024 那批 Prompt Engineering 文章，名詞給了一堆 (planner、memory、reflection、context management)，卻很少回答「為什麼這些設計有用」。而能回答出來的那部分，才是不會過半年就作廢的知識。</p>

<p>四篇是中文長文:</p>

<ul>
  <li>Part 1: <a href="https://axk51013.medium.com/rethinking-agent-harness-%E5%A6%82%E4%BD%95%E6%9B%B4%E5%A5%BD%E8%A8%AD%E8%A8%88-llm-agent-part1-behind-function-calling-e17c419c8e96">Behind Function Calling</a></li>
  <li>Part 2: <a href="https://axk51013.medium.com/rethinking-agent-harness-part-2-%E7%90%86%E8%A7%A3-skill-%E4%B9%8B%E7%BE%8E-bb3a3342e84d">理解 Skill 之美</a></li>
  <li>Part 3: <a href="https://axk51013.medium.com/rethinking-agent-harness-part-3-%E7%82%BA%E4%BB%80%E9%BA%BC-grep-%E6%89%93%E6%95%97%E4%BA%86-rag-40eafd507cf6">為什麼 Grep 打敗了 RAG?</a></li>
  <li>Part 4: <a href="https://axk51013.medium.com/rethinking-agent-harness-part4-llm-wiki-%E5%8F%96%E4%BB%A3-rag-041629319804">LLM Wiki 取代 RAG?</a></li>
</ul>

<p>所以這篇就當導讀。原文有大量篇幅在講 decoding 機制、語法自動機、tokenizer 的 special token 分層，小編這邊跳過，只重點挑出開發 LLM application 可以直接拿來用的最佳實務，更多細節請回頭看原文。</p>

<p>整個系列最後收斂到封面那張圖: 一個好的 LLM application 是 LLM × Harness × Data × Task 四個面向互相 fit 的結果。只優化其中一角，通常會在別的地方失衡。</p>

<h2 id="1-function-call-的失敗分三層你能動的其實只有一層">1. function call 的失敗分三層，你能動的其實只有一層</h2>

<p>作者最在意的一件事: 大部分工程師看到 function call 失敗就說「這就是 hallucination」，而說出這句話的同時，其實就放棄了真正解決問題的可能。</p>

<p><img src="/assets/images/rethinking-agent-harness/fc-failures.png" alt="" /></p>

<p>上面四種輸出看起來都是「JSON 不對」，實際上是三個不同層次的問題:</p>

<ul>
  <li><strong>Decision</strong>: 該呼叫工具卻沒呼叫、或呼叫了錯的工具。</li>
  <li><strong>Serialization</strong>: 外層結構壞掉，parser 根本抽不出 name 跟 arguments。</li>
  <li><strong>Guarantee</strong>: 是合法 JSON、結構也對，但內容違反 schema。enum 只允許 <code class="language-plaintext highlighter-rouge">celsius</code> / <code class="language-plaintext highlighter-rouge">fahrenheit</code>，模型填了 <code class="language-plaintext highlighter-rouge">kelvin</code>。</li>
</ul>

<p><strong>這裡要先補一個容易被忽略的事實: function calling 不只是模型的事。</strong> 一次成功的 tool call 是模型與推論引擎 (inference server) 兩邊配合出來的結果。模型這端在訓練時學會用某種格式輸出 tool call (Hermes 用 <code class="language-plaintext highlighter-rouge">&lt;tool_call&gt;…&lt;/tool_call&gt;</code>、Mistral 用 <code class="language-plaintext highlighter-rouge">[TOOL_CALLS]</code>、Llama3 用 <code class="language-plaintext highlighter-rouge">&lt;|python_tag|&gt;</code>，每家都不一樣)，推論引擎那端則要有對應的 parser，才能把那段文字抽成結構化資料，兩邊沒對上就會壞。作者舉了 2024 年 llama3.1 剛支援 function calling 的那段時間: system prompt 稍微改一下、tool definition 寫複雜一點就壞掉，而 vLLM 那邊怎麼調 parser 都救不回來。</p>

<p>所以「這個模型 function call 準不準」這句話其實不完整，同一個模型換一套 serving stack，行為就可能不一樣，自架或換 gateway 的時候特別要留意。</p>

<p>分層的重點是搞清楚哪一層輪得到你處理。如果你用的是 OpenAI API 並且開了 Structured Outputs (<code class="language-plaintext highlighter-rouge">strict: true</code>)，後兩層基本上被 provider 處理掉了: 它會在每個 token 生成前先算出哪些選項合法、把不合法的壓掉，所以 enum 違規、tool name 亂編從根本上不會發生。代價是複雜 schema 第一次編譯會拉長延遲，官方文件自己寫著最多可能要等一分鐘，所以 schema 越簡單越好不只是為了模型好讀。</p>

<p>反過來，如果你是用開源模型自架推論引擎，後兩層就得自己弄清楚，原文也花了很大篇幅在講這塊 (事後解析與生成前約束各自的細節，以及主流開源推論引擎預設會逼你在兩者之間二選一)。小編這邊主要用 OpenAI API，就跳過不談了。</p>

<p>剩下的 Decision 才是你真正要面對的，而它是<strong>唯一沒有技術手段可以直接處理的一層</strong>: 你可以用文法強迫模型吐出合法 JSON，卻沒辦法用文法強迫它「選對工具」。你能動的只有工具本身怎麼設計: 命名、數量、粒度，也就是接下來兩節的主題。所以看到失敗時，第一件事是先分清楚「格式壞了」還是「選錯工具」; 一律 retry 的話，永遠不會知道問題出在哪。</p>

<h2 id="2-tool-design-的老原則背後在優化什麼">2. tool design 的老原則，背後在優化什麼</h2>

<p>這幾條大家可能都聽過，原文的貢獻是把每一條對應回它實際解決的問題:</p>

<ul>
  <li><strong>tool 不要太多。</strong> 不只是減輕模型的選擇負擔，也在降低文法複雜度。原文給了一個好用的公式: tool surface size = tool 數量 × schema 複雜度 × enum 可能值數量，三個項目任何一個變大，上面那三層的失敗率會一起上升。</li>
  <li><strong>命名要語意清楚。</strong> 模型不是在查一份 API 清單，而是在 tool 定義上做 next-token prediction。<code class="language-plaintext highlighter-rouge">create_calendar_event</code> 比 <code class="language-plaintext highlighter-rouge">handle_event</code> 更接近模型看過的好程式碼; 反過來用 <code class="language-plaintext highlighter-rouge">HR_tool1</code> 這種代號、指望靠 description 教會它選，這些名字根本不在模型自然會輸出的範圍內，只是多給它一層負擔。</li>
  <li><strong>default value 不要放進 schema。</strong> 能由後端、使用者偏好或 locale 決定的參數就別暴露給模型: 對事後解析來說，optional 欄位多出一個判斷不了的狀況 (沒出現是合理省略，還是模型漏填?); 對生成前約束來說，optional 欄位的組合會讓文法分支指數成長。</li>
  <li><strong>tool name 用共同前綴。</strong> 這條來自 Manus 的「Mask, Don’t Remove」，也是小編覺得最容易被忽略、但最好實作的一條。</li>
</ul>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:1.6em 0"><div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px;background:var(--surface)"><div style="font-weight:600;margin-bottom:10px">扁平命名: 每次都在 10 個裡選 1 個</div><div style="font-family:monospace;font-size:.82em;line-height:1.85;color:var(--text-secondary)">open_page<br />click_element<br />extract_content<br />run_command<br />read_file<br />write_file<br />send_message<br />search_messages<br />create_event<br />query_events</div><div style="margin-top:12px;font-size:.9em;color:#cf222e">選擇壓力隨 tool 數量上升</div></div><div style="flex:1 1 280px;border:1px solid var(--accent);border-radius:8px;padding:16px;background:var(--tag-bg)"><div style="font-weight:600;margin-bottom:10px">共同前綴: 拆成兩層，每層 3 到 5 個</div><div style="font-family:monospace;font-size:.82em;line-height:1.85;color:var(--text-secondary)"><span style="color:var(--accent);font-weight:600">browser_</span> → open / click / extract<br /><span style="color:var(--accent);font-weight:600">shell_</span> → exec / read_file / write_file<br /><span style="color:var(--accent);font-weight:600">slack_</span> → send / search</div><div style="margin-top:12px;font-size:.9em;color:#1a7f37">先選群組再選具體 tool，執行環境還能依當前階段限制合法前綴</div></div></div>

<p>前綴一致之後，選工具在底層就變成兩層分類問題，而且執行環境可以依 agent 當前階段先限制合法前綴: 現在在 browser 階段，就只允許 <code class="language-plaintext highlighter-rouge">browser_</code> 開頭的工具。50 選 1 變成兩次 3 到 5 選 1。</p>

<h2 id="3-小編補充研究-共同前綴在-openai-跟-claude-上適用嗎">3. 小編補充研究: 共同前綴在 OpenAI 跟 Claude 上適用嗎?</h2>

<p><strong>這一節不在原文範圍內，是小編自己去查官方文件補的。</strong> 原文談共同前綴時是站在自架推論引擎的角度，小編想知道用 OpenAI 或 Claude API 的人能不能拿到同樣的好處。</p>

<p><strong>把工具選擇變成兩層分類這件事，閉源 API 做不到。</strong> 「先鎖定合法前綴、再選具體工具」是在生成時把不合法的 token 遮蔽掉，Manus 是在自己的推理層實作的; OpenAI 跟 Claude 都沒有這個介面，所以兩層分類不會真的發生在 token 層級。</p>

<p><strong>但命名層的好處兩家都拿得到，而且兩家官方都直接建議這樣命名。</strong> Anthropic 的工具定義文件寫得很明白: 「工具名稱要用有意義的 namespace。當工具跨多個服務或資源時，用服務名當前綴 (例如 <code class="language-plaintext highlighter-rouge">github_list_prs</code>、<code class="language-plaintext highlighter-rouge">slack_send_message</code>)。這會讓工具選擇在你的工具庫變大時仍然沒有歧義，在使用 tool search 時尤其重要。」最後那句是關鍵，tool search 是靠名字找工具的，前綴直接決定搜得準不準。</p>

<p><strong>而「不要移除工具」這件事，在閉源 API 上的理由換成了 prompt cache。</strong> 兩家的工具定義都排在 prompt 最前面 (Anthropic 的組裝順序是 tools → system → messages)，所以只要增刪或重排一個工具，後面整份 cache 就失效。也因此兩家都給了「限制可用子集、但不動 tools 清單」的官方做法:</p>

<ul>
  <li><strong>OpenAI</strong> 有 <code class="language-plaintext highlighter-rouge">tool_choice: {type: "allowed_tools", mode: "auto", tools: [...]}</code>。文件給的理由正是這個: 你想讓不同請求之間只有一部分工具可用、但不修改傳進去的工具清單，「這樣可以最大化 prompt caching 的節省」，幾乎就是「Mask, Don’t Remove」的產品化版本。</li>
  <li><strong>Claude 沒有等價參數</strong> (<code class="language-plaintext highlighter-rouge">tool_choice</code> 只有 auto / any / tool / none)。對應做法是 tool search 加 <code class="language-plaintext highlighter-rouge">defer_loading</code>: 工具先宣告但不載入 context，讓模型自己搜出需要的那幾個，而且新載入的 schema 是附加而不是替換，cache 保得住。Claude Opus 5 另有一個 beta 機制，可以在對話中途用 <code class="language-plaintext highlighter-rouge">tool_addition</code> / <code class="language-plaintext highlighter-rouge">tool_removal</code> 增減工具而不動 <code class="language-plaintext highlighter-rouge">tools</code> 清單，同樣是為了不讓 cache 失效。</li>
</ul>

<p>順帶一提，同一份 Anthropic 文件還有一條跟「tool 不要太多」呼應、但方向不同的建議: 把相關操作合併成較少的工具，不要拆成 <code class="language-plaintext highlighter-rouge">create_pr</code> / <code class="language-plaintext highlighter-rouge">review_pr</code> / <code class="language-plaintext highlighter-rouge">merge_pr</code> 三個，而是一個工具加一個 <code class="language-plaintext highlighter-rouge">action</code> 參數。共同前綴是讓工具數量多但有結構，合併是直接讓數量變少，兩條都在解同一個問題，可以看自己的工具庫規模選。</p>

<h2 id="4-skill-封裝的是任務不是操作">4. Skill 封裝的是「任務」，不是「操作」</h2>

<p>Part 2 的核心一句話: skill 不是新的 tool，而是把一類任務的流程、參考資料與可重用程式碼，封裝成模型可以按需展開的能力。</p>

<p><strong>為什麼「任務」這個粒度是對的?</strong> 回頭看第 1 節: Decision 是唯一沒有技術手段可以處理的一層，所以問題就變成「要在什麼粒度上讓模型做決定」。如果拆成 20 個原子操作 (<code class="language-plaintext highlighter-rouge">extract_pdf_text</code>、<code class="language-plaintext highlighter-rouge">extract_pdf_tables</code>、<code class="language-plaintext highlighter-rouge">fill_pdf_form</code>…)，模型每一步都要回答「這 20 個微操作裡哪個是下一步」，而這個粒度本身就不自然: 訓練資料裡跟使用者的請求裡，講的都是「幫我處理這份 PDF 發票」。包成一個 <code class="language-plaintext highlighter-rouge">pdf-processing</code> skill，判斷就變成「這是不是 PDF 任務」，決定的次數變少，粒度也更貼近模型熟悉的樣子。</p>

<p><strong>Progressive disclosure 是很精準的 context engineering。</strong> 過去認真寫一個 function，schema、使用時機、注意事項、few-shot 加起來就 2 到 4k tokens，20 個 tool 就是 40 到 80k，而作者給的 2026 年經驗法則是約 16K 以內基本穩定、超過約 64K 明顯退化。改成三層之後: L1 只有 name 與 description 常駐 (20 個 skill 約 4k)，L2 是被選中才載入的 SKILL.md，L3 是模型自己去讀的 reference 與 script。</p>

<p><strong>可重用的 script 是把隨機換成確定。</strong> 沒有 script 時，模型每次都得現場寫一段抽取邏輯，然後進入「寫 → 跑 → 看錯誤 → 改 → 再跑」的迴圈; 有一份驗證過的 script，這步就只是「用對的參數執行一行 bash」。作者的對照很有說服力: 各家實驗室花很大力氣把單步 function call 成功率從 95% 推到 97%，而一份包好的 script 是把「真正做事那一步」從徒手寫程式的 70 到 80% 拉到接近 100%，提升幅度大得多，而且掌握在 skill 作者手裡，不用等模型變強。</p>

<p>原文最後反過來列了三種會把這些優點抵銷掉的寫法，小編建議寫過 skill 的人都對照一下:</p>

<ol>
  <li><strong>SKILL.md 寫太長，又沒當索引用。</strong> skill 被觸發時 body 會整份進 context，所以官方建議 500 行以內; 更重要的是它要兼任 L3 的目錄，明確寫出「遇到 X 就去看 <code class="language-plaintext highlighter-rouge">references/Y.md</code>」。沒寫的話模型不知道那些檔案存在，三層就退化成兩層。作者順手點出同樣的問題也出現在讓 LLM 寫 README 這件事上: 架構、roadmap、release note 全塞進去，而 README 正是 coding agent 很可能一開場就讀進 context 的檔案。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">scripts/</code> 放的其實不是可重用的 script。</strong> 自我檢查三題: 全新環境能不能一行跑起來? 至少 3 種 input 驗證過? 常見錯誤有處理、而且 stdout 講清楚發生什麼事? 任一題答不出來，就別放 <code class="language-plaintext highlighter-rouge">scripts/</code>，改放 <code class="language-plaintext highlighter-rouge">references/</code> 當參考，並在 body 說明需要依場景改寫，不然模型每次現場補完，隨機性又回來了。</li>
  <li><strong>把現成的 MCP / tool 一對一包成 skill。</strong> 這只是換個包裝: 模型還是在操作粒度做決定，L1 還是 N 行 metadata，body 裡也沒有「多個 tool 怎麼協同」的知識可裝。正確的問法不是「我有幾個 tool，要包幾個 skill」，而是「我有幾類典型任務、每類會用到哪些 tool」，<code class="language-plaintext highlighter-rouge">github-create-pr</code> / <code class="language-plaintext highlighter-rouge">github-get-comments</code> 各包一個是錯的，一個 <code class="language-plaintext highlighter-rouge">pr-review</code> skill 內部呼叫多個 tool 才對。判斷症狀很簡單: skill 數量跟 tool 數量一對一，這條就已經被打破了。</li>
</ol>

<h2 id="5-grep-打敗-rag-為什麼最後收斂到-filesystem">5. Grep 打敗 RAG: 為什麼最後收斂到 filesystem</h2>

<p>Part 3 問的是為什麼主流 coding agent 都用檔案系統理解程式碼。這題小編之前整理過一篇 <a href="/2026/06/03/is-grep-all-you-need/">向量已死? Grep 萬能? 不，你需要的是「策展」一組檢索工具</a>，談過 grep 的甜蜜點 (高訊號關鍵字、純文字、agent 可反覆迭代)、向量檢索接不住精確識別字，以及為什麼真實企業語料最後幾乎都走混合檢索。重疊的部分就跳過，這裡只補 Oscar 多給的兩點。</p>

<p>一是 code 這種資料還有兩個問題: <strong>切塊會破壞語義</strong> (自然語言被切開意思大致還在，但一個 method 失去 class 的上下文就無法推理)、<strong>索引一定會過期</strong> (codebase 每天在變，實務上的 lazy update 會讓撈到的 chunk 跟現況對不上)。作者由此提醒: 所有非自然語言的資料要套 RAG，都該先檢驗這類問題，table 能不能直接 RAG? 投影片能不能?</p>

<p>二是「可以呼叫多次」本身沒什麼意義。前一篇提過 agent 可以改寫查詢再搜一次，Oscar 往下追問一句: 憑什麼相信下一次會更好? <strong>重點是每次呼叫之間，agent 有沒有可信的進度訊號。</strong></p>

<p><img src="/assets/images/rethinking-agent-harness/grep-completeness.png" alt="" /></p>

<p>grep 在這件事上有一個其他檢索方式都沒有的性質: 完整性。它會回傳範圍內這個 pattern 的所有出現位置，沒有 top-k 截斷、沒有相似度門檻，第一次查太寬、撈出 200 個也沒關係，答案一定在裡面; 撈到 0 個也是「這個範圍加這個 pattern 下確認沒有」的硬事實，不是「我找不到」的曖昧訊號。失敗訊息還直接指出下一步: 換關鍵字、放寬 pattern、改目錄範圍。RAG 失敗時回的是一堆看起來相關但沒對到的 chunk，agent 分不出是 query 不夠精確、切塊切斷了、還是 embedding 不熟這個領域，只能重猜。所以設計任何 agent tool 都可以先問: <strong>它失敗的時候，回傳的訊息能不能指導下一步?</strong></p>

<p>把這些收起來，filesystem 勝出不是因為它比較傳統或比較簡單，而是四個條件剛好同時成立: <strong>LLM 對它很熟</strong> (指令、錯誤處理、使用習慣全都在訓練資料裡)、<strong>harness 很容易把它包成可反覆呼叫的工具</strong>、<strong>code 本來就存在 filesystem 裡</strong>、<strong>repo debugging 又天然需要多步探索</strong>。這剛好就是封面那張圖說的 LLM × Harness × Data × Task 四個面向同時對齊。</p>

<blockquote>
  <p>編按: 這裡順便講一件事。context rot 這份研究在社群被引用得很兇，但作者從頭到尾不引用它，因為他認為這篇的研究過程不夠實在: 同類問題在它之前已經有 20 到 30 篇論文討論過，它沒有提出新的貢獻，而且多數實驗得出的「超過 1,000 tokens 之後就不可信」這種結論，跟實務經驗明顯不符。他不是反對「長 context 會退化」這個現象，他自己引用的是 Lost in the Middle 與 NVIDIA RULER (後者顯示很多號稱 128K 的模型有效長度只有 64K 甚至 16K)，他反對的是拿這份研究當依據。</p>
</blockquote>

<h2 id="6-llm-wiki-適合企業檢索嗎">6. LLM Wiki 適合企業檢索嗎?</h2>

<p>Part 4 談 Karpathy 的 <a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f">LLM Wiki</a>。這個題目小編五月也寫過一篇 <a href="/2026/05/20/llm-knowledge-base/">LLM Knowledge Base: 用 LLM 編譯個人知識庫，各路實作全比較</a>，介紹過三層架構 (<code class="language-plaintext highlighter-rouge">raw/</code> 唯讀、<code class="language-plaintext highlighter-rouge">wiki/</code> 由 LLM 全權維護、<code class="language-plaintext highlighter-rouge">CLAUDE.md</code> 當規則文件)、匯入到健檢的四個動作，以及社群那一票實作版本，這裡就不重複了。</p>

<p>Oscar 這篇要回答的是另一個問題，也是企業真正在意的那個: <strong>這套設計適合 corporate retrieval 嗎?</strong> 大家在意的從來不是 Karpathy 示範的個人學習場景，而是在公司內部大量文件裡找到對的那篇來回答問題。而這套設計的核心是把合成工作提前到 ingest 時做，划不划算，完全取決於資料跟任務的性質。</p>

<p><strong>它成立的條件是三者的交集: personal + immutable + synthesis-heavy。</strong> 這三個條件剛好各解掉一個痛點: 原始資料不可變，LLM 合成出來的頁面就不會因為 source 被改而過期 (所以 Karpathy 直接規定 <code class="language-plaintext highlighter-rouge">raw/</code> 不能改也不能刪); 個人使用，權限就不需要傳遞; 任務本來就需要跨文件綜合，那 ingest 時先整理好就從成本變成需求本身，你讀 paper 本來就要做筆記、比較方法。反過來，如果任務是「公司 VPN 密碼在哪」，那些預先整理就是純成本。</p>

<p>所以 Karpathy 的示範看起來這麼漂亮，不是因為 LLM Wiki 對所有場景都更好，而是他剛好站在 trade-off 全部對自己有利的位置上。而企業真正在意的 corporate retrieval，幾乎把每一條假設都反轉了:</p>

<p><img src="/assets/images/rethinking-agent-harness/llm-wiki-reversals.png" alt="" /></p>

<ol>
  <li><strong>task 不是單一綜合，而是一整組。</strong> 快速查找 (請假流程)、數值查詢 (Q3 revenue，這類本來就該走 text-to-SQL 或 chat-to-BI)、跨文件綜合，三種混在一起。對前兩類，預先合成的敘述沒人會用到，ingest 成本卻一份不少。</li>
  <li><strong>資料不是定稿，而是每天在動的運作資料。</strong> policy 上週改、Jira 下午狀態又變。wiki page 是跨 source 合成的，改一份 source 就要反查所有吸收過它的 entity page、比較頁、索引，而這個依賴常常是隱性的。更麻煩的是漏改的過期資訊會被當成既有背景知識拿去整理新頁面，錯誤會擴散。RAG 至少可以把舊 chunk 全丟掉重新 embed，LLM Wiki 沒有一鍵清除。</li>
  <li><strong>權限邊界會在合成時消失。</strong> 法務的合約 note (法務 + C-level 可看) 跟 PM 的 spec (product + eng leads 可看) 都提到 Project Atlas，被整理進同一頁，這頁誰能看? 取交集可能剩沒幾個人、這頁就失去檢索價值; 取聯集就是外洩; 只用一邊的權限，跨來源合成的價值又消失了。作者順手補了句蠻犀利的: 市面上一堆 GraphRAG 開源專案演算法很好，但根本沒解 ACL，可以直接過濾掉。</li>
  <li><strong>規模大 3 到 5 個數量級，而卡住的不是 token 成本。</strong> 原文試算過，就算 10M 份資料也是百萬美元等級，很貴但不是不能接受。真正的問題是結構維護: 新資料要不要 merge 到既有 entity page? 怎麼找候選? 矛盾怎麼找? entity 到百萬級就不可能全塞進 context，你需要一層候選生成 (BM25、vector index、metadata filter)，敏感的讀者應該發現了，RAG 又回來了。</li>
  <li><strong>企業內容是極端的熱門/冷門長尾。</strong> 常見比例是 95/5。LLM Wiki 的隱含假設是「ingest 成本會攤平在後續 query 上」，個人場景成立; 企業場景下你重度整理的那 95% 一輩子不會被查一次，那筆成本直接收不回來。</li>
</ol>

<p>小編前一篇的收尾寫過「任何持續有新資料進來、需要被結構化整理、會被反覆查詢的場景都適用」，Oscar 這篇把條件補得更精確: 上面第 2 條跟第 5 條講的正是「資料持續變動」與「大部分內容其實不會被查」，而這兩件事剛好都打在 LLM Wiki 最痛的位置。</p>

<p>所以正確的問法不是「LLM Wiki 能不能取代公司 KB」。公司不是單一 corpus、也不是單一 task，而是一個組合，該問的是哪些 sub-domain 適合，而判斷方式就是把上面五條倒過來看: 規模有界、資料偏定稿、權限一致、需求是綜合、內容大部分會被查到。原文列的例子包括決策前研究 (評估 vendor、競品、併購對象)、研究團隊的知識整理、法規研究、產品決策記憶 (ADR、當初為什麼這樣決定)、大客戶研究。</p>

<p>原文最後把 LLM Wiki 的價值拆成三軸: ingest 時合成 (把整合負擔從 query 路徑移開)、關係編碼 (用有界、有結構的導航取代無界的相似度搜尋)、LLM 當 curator (lint 過、對照過矛盾的內容比原始 chunk 可信)。而這三件事沒有一件是新的，RAPTOR 的階層式摘要、GraphRAG 與 HippoRAG 的關係式檢索、Mem0 與 A-Mem 的記憶維護，各自都已經有一堆論文與專案做過。</p>

<p><strong>LLM Wiki 的特殊性在於三件事同時做到、全部放在同一份 markdown 檔案系統上、用同一套 Glob / Grep / Read 操作。</strong> 也因此要為企業重新設計時不必從 LLM Wiki 出發，更該把三軸拆開、各自找已經成熟的方案，再跟 dense / sparse / rerank / graph / metadata / ACL 這些系統整合起來。</p>

<p>所以這節的答案是: <strong>LLM Wiki 不會取代企業檢索，但值得拿去解企業內的某幾塊 sub-domain。</strong> 先用上面那組條件找出符合的那幾塊，其餘的走 hybrid solution,而怎麼 hybrid，Oscar 說要留給之後的文章專門寫。</p>

<h2 id="harness-只是一半另一半是-data--task">harness 只是一半，另一半是 Data + Task</h2>

<p>最後把封面那張圖的四個面向講清楚，因為整個系列都在用它做判斷:</p>

<ul>
  <li><strong>LLM</strong>: 模型本身的限制。context window 是有限資源，而且模型只在它訓練時大量看過的形式上表現穩定 (原文稱為 Pθ 對齊)。所以不能假設模型會記住你給它的所有東西，也不能長期要求它用陌生的格式或介面工作。</li>
  <li><strong>Harness</strong>: 你包在模型外面的執行環境，工具、狀態、記憶、檔案系統、權限、錯誤恢復。重點不是「工具能被呼叫很多次」，而是每次呼叫之間有沒有可信的進度訊號。</li>
  <li><strong>Data</strong>: 你的資料長什麼樣。有沒有結構、能不能被切開、變動頻率多高、有沒有權限邊界。</li>
  <li><strong>Task</strong>: 使用者實際要做的事。一次檢索就結束，還是需要多步探索? 是快速查找、跨文件綜合，還是數值計算?</li>
</ul>

<p>一個好的 LLM application 是這四者互相 fit 的結果，這也是為什麼同一個技術在不同場景會得到相反的結論。</p>

<p>Part 1 到 3 優化的都是 LLM + Harness 那一側: 怎麼讓模型穩定產生 tool call、怎麼把多步任務封裝起來、為什麼檢索收斂到檔案系統。但 Part 4 推到企業場景之後，其實已經離開 harness 了，真正卡住系統的是 Data + Task: 資料會變，所以要處理新鮮度與版本; 資料有權限，所以要處理權限傳遞; task 是一整組，所以不能用一種檢索策略打全部。這些都不是更好的 prompt、tool schema 或 skill description 能解決的。</p>

<p>作者的觀察小編蠻認同: 很多 agent demo 走不到 production，不是模型不夠強、也不是 harness 不夠新，而是只優化了 LLM + Harness，Data + Task 根本沒被設計，甚至沒被注意到。</p>

<p>這個框架也順便回答了為什麼大家老覺得 LLM application 技術變太快: 第 5 節那四個條件只要有一個改變 (模型突破、出現不同形式的 harness、有人定義出更好的任務)，最佳解就會換一輪。要抓住的是這些條件，而不是當下的答案。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Tool Use" /><category term="Skill" /><category term="RAG" /><summary type="html"><![CDATA[倢愷 Oscar 的「Rethinking Agent Harness，如何更好設計 LLM Agent」系列四篇讀完，小編覺得是近期中文圈少見的一組硬派文章，蠻推薦寫 LLM application 的人找時間讀一遍。它不教你怎麼用某個 framework，而是一路在問同一件事: 這個設計解決了 LLM 哪一個限制? 又在什麼條件下會失效?]]></summary></entry><entry><title type="html">Agents’ Last Exam: 測 AI Agent 能不能真的上工，順便盤點還沒飽和的 Agent benchmark</title><link href="https://blog.aihao.tw/2026/07/22/agents-last-exam-benchmark/" rel="alternate" type="text/html" title="Agents’ Last Exam: 測 AI Agent 能不能真的上工，順便盤點還沒飽和的 Agent benchmark" /><published>2026-07-22T00:00:00+00:00</published><updated>2026-07-22T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/22/agents-last-exam-benchmark</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/22/agents-last-exam-benchmark/"><![CDATA[<p>最近大家常講「AI Agent 快要能接手真的工作了」，但這個說法到底怎麼驗證?UC Berkeley 的 Dawn Song 團隊發布了 <a href="https://agents-last-exam.org/">Agents’ Last Exam (ALE)</a>，直接測 agent 能不能完成「有經濟價值的真實工作」。這個團隊過去做出了 <a href="https://arxiv.org/abs/2009.03300">MMLU</a>、<a href="https://arxiv.org/abs/2103.03874">MATH</a> 這些經典 benchmark，這次在<a href="https://x.com/dawnsongtweets/status/2065095757988868190">發表的推文</a>裡給了一個蠻清醒的結論，翻譯過來是:</p>

<blockquote>
  <p>agent 有用的時代已經到來，但真正能勝任工作 (job-ready) 的時代還沒到。</p>
</blockquote>

<h2 id="ale-在測什麼">ALE 在測什麼</h2>

<p>先講一下這個團隊的來歷。帶頭的 Dawn Song 是 UC Berkeley EECS 教授、MacArthur Fellow，資安研究出身，同時是 <a href="https://rdi.berkeley.edu/">Berkeley RDI</a> 研究中心的共同創辦人，ALE 就是這個中心主導的計畫。這個研究群做評測的資歷很深: 從早期量測知識廣度的 MMLU、數學推理的 MATH，到近年 agent 方向的 <a href="https://arxiv.org/abs/2506.02548">CyberGym</a>(測 AI agent 能不能在真實開源專案裡找出漏洞)和 <a href="https://arxiv.org/abs/2605.11086">ExploitGym</a>(測能不能把已知漏洞做成實際可用的攻擊)，這次的 ALE 再把範圍拉到「所有在電腦上進行的專業工作」。考的東西從知識、資安攻防一路走到實際工作，一直跟著模型能力的邊界在移動。</p>

<p>ALE 由 Berkeley RDI 主導，號稱是目前規模最大、涵蓋最廣的 agent 評估 benchmark: 300 多位產業專家出題，涵蓋 55 個子產業，目前收了 1,500 多個任務(目標 5,000 題)，而且是「持續更新式 (rolling) 的 benchmark」，會不斷加入新題目來對抗飽和。</p>

<p>它跟一般 LLM benchmark 最大的差別是: 任務不是問答題，而是在真實專業軟體裡完成實際工作，例如:</p>

<ul>
  <li>在 Adobe After Effects 做動畫與視覺特效</li>
  <li>在 Siemens NX 做 3D 建模</li>
  <li>在 Unreal Engine 做遊戲場景設置與渲染</li>
  <li>在 Moldex3D 做模流分析(對，台灣廠商的模流分析軟體也在題庫裡)</li>
  <li>在 Rhino 3D 做建築建模與能源分析</li>
  <li>在 FSLeyes 做腦神經影像分析</li>
</ul>

<p>每個任務都有可驗證的結果 (verifiable outcomes)，不是靠 LLM 當裁判給印象分。網站上還有 <a href="https://agents-last-exam.org/traces">agent traces</a> 可以直接看 agent 的完整執行過程，想研究 agent 在專業軟體裡怎麼卡關的，這是很好的素材。</p>

<p>名字裡的「Last」有兩層含義，Dawn Song 在 <a href="https://www.linkedin.com/pulse/introducing-agents-last-exam-ale-new-standard-evaluating-dawn-song-dntuc">LinkedIn 發文</a>裡解釋: 一是「最後要跨過的門檻」，通過這些考題，代表 agent 真的能做這份工作、持續交付有經濟價值的產出;二是「難度的最前沿」，任務就落在現今 agent 能力的邊界上。</p>

<blockquote>
  <p>編按: 這個命名很難不讓人想到 CAIS 和 Scale AI 的 <a href="https://agi.safe.ai/">Humanity’s Last Exam (HLE)</a>: 由專家出題、測學術知識推理極限的問答評測，發布時前沿模型的正確率不到 10%。兩者其實有師承關係: HLE 的主導者 Dan Hendrycks 當年在 UC Berkeley 讀博士，指導教授就是 Dawn Song，MMLU、MATH 正是他們師生當時的合作成果。不過兩個評測的方向不同: HLE 考的是知識問答的極限，ALE 考的是實際工作的完成度。</p>
</blockquote>

<h2 id="難度分層的設計">難度分層的設計</h2>

<p>ALE 的 <a href="https://agents-last-exam.org/leaderboard">leaderboard</a> 把任務分成幾個子集，這個設計蠻值得學:</p>

<ul>
  <li><strong>Near-term</strong>: 現在的前沿 agent「部分做得到」的工作流，適合短期競爭和快速迭代</li>
  <li><strong>Full-Spectrum</strong>: 50 多個產業每個至少一題，測廣度</li>
  <li><strong>Last-Exam</strong>: 前沿難度，用來衡量距離「真正能上工」還有多遠</li>
  <li><strong>ALE-CLI</strong>: 只需要 Linux 命令列就能完成的子集，方便低成本評估</li>
</ul>

<p>計分也有兩套: 「pass rate」是拿到滿分的比率，「score」是部分給分 (partial credit) 的平均。這對長時程任務很重要，因為在最難的題目上大家 pass rate 都趨近零，沒有部分給分的話，榜單就完全失去鑑別度了。</p>

<h2 id="目前的成績-最難那層幾乎沒人及格">目前的成績: 最難那層幾乎沒人及格</h2>

<p>看 2026 年 7 月的 leaderboard，Overall 排名前段是:</p>

<table>
  <thead>
    <tr>
      <th>模型 (harness)</th>
      <th>Score</th>
      <th>Pass Rate</th>
      <th>總成本</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GPT-5.6-Sol (Codex, xhigh)</td>
      <td>53.6%</td>
      <td>30.6%</td>
      <td>$762</td>
    </tr>
    <tr>
      <td>GPT-5.6-Terra (Codex, max)</td>
      <td>50.5%</td>
      <td>28.0%</td>
      <td>$545</td>
    </tr>
    <tr>
      <td>GPT-5.6-Luna (Codex, max)</td>
      <td>49.2%</td>
      <td>28.3%</td>
      <td>$391</td>
    </tr>
    <tr>
      <td>Claude Fable 5 (Claude Code, xhigh)</td>
      <td>48.7%</td>
      <td>25.7%</td>
      <td>$4,340</td>
    </tr>
    <tr>
      <td>GPT-5.5 (Codex, xhigh)</td>
      <td>47.9%</td>
      <td>26.6%</td>
      <td>$602</td>
    </tr>
    <tr>
      <td>Claude Opus 4.8 (Claude Code, max)</td>
      <td>45.1%</td>
      <td>27.0%</td>
      <td>$3,985</td>
    </tr>
  </tbody>
</table>

<p>最好的模型也只拿到一半左右的分數，完整做對的比率只有三成。拆開各子集看更有意思(各取該子集分數最高的模型):</p>

<table>
  <thead>
    <tr>
      <th>子集</th>
      <th>題數</th>
      <th>最佳模型</th>
      <th>Score</th>
      <th>Pass Rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Near-term(現在部分做得到)</td>
      <td>67</td>
      <td>GPT-5.6-Sol</td>
      <td>78.8%</td>
      <td>47.8%</td>
    </tr>
    <tr>
      <td>Full-Spectrum(每個產業至少一題)</td>
      <td>55</td>
      <td>GPT-5.6-Sol</td>
      <td>46.6%</td>
      <td>30.0%</td>
    </tr>
    <tr>
      <td>Last-Exam(前沿難度)</td>
      <td>38</td>
      <td>GPT-5.6-Sol</td>
      <td>19.4%</td>
      <td>3.9%</td>
    </tr>
  </tbody>
</table>

<p>從這份數據還可以看出幾件事:</p>

<ul>
  <li>就算是 Near-term 這種「設計上就是現在的 agent 部分做得到」的題目，最好的模型也只完整做對不到一半。</li>
  <li>Last-Exam 子集在 6 月發表當時，所有前沿模型的 pass rate 都是 0%;一個多月後的現在，pass rate 最高的是 Fable 5 的 7.9% (score 16.5%)，大多數模型還是 0%。</li>
  <li>需要商業軟體授權的 11 題(After Effects、Siemens NX 這類)單獨看分數最低: 最好的 Fable 5 score 也只有 29.4%、pass rate 9.1%，等於 11 題最多完整做對 1 題。agent 在專業 GUI 軟體裡的操作能力，明顯比在命令列環境弱。</li>
  <li>加大推理算力的回報有限: GPT-5.6-Sol 從 medium 檔開到 xhigh，score 只從 51.9% 進步到 53.6%;在 Last-Exam 子集，medium 和 xhigh 根本同分。</li>
  <li>harness 的影響跟模型本身一樣大: 同一個 Claude Opus 4.7，配 Cursor CLI 拿 41.8%，配自家的 Claude Code 只有 35.1%。這個榜測的其實是「模型 + harness」的組合，不是模型單獨的能力。</li>
  <li>中國模型最高的是 GLM-5.2 的 40.6%，已經超過 Gemini 3.1 Pro 的 32.0%。</li>
</ul>

<p>另外小編提醒一個容易忽略的欄位: 成本。Fable 5 跑完整套要 $4,340，GPT-5.6-Sol 只要 $762，成本差了近 6 倍，分數還比較高。ALE 把成本直接放上榜單，agent 評估已經不只看做不做得到，還要看划不划算。</p>

<h2 id="其他還沒飽和的知名-agent-benchmark">其他還沒飽和的知名 Agent benchmark</h2>

<p>ALE 不是唯一在做「真實工作」評估的 benchmark，小編整理幾個目前公認難度高、還沒飽和的對照:</p>

<p><strong><a href="https://openai.com/index/gdpval/">GDPval</a> (OpenAI)</strong>: 44 個職業、1,320 個任務，同樣主打「有經濟價值的工作」，但評的是產出物: 文件、簡報、試算表這類交付成果。OpenAI 論文當時的做法是找真人產業專家盲測，比較模型和人類專家的成品，不過那是一次性結果，沒有持續維護排行榜，對外也只公開了 220 題的 gold subset。目前持續更新的榜單是第三方 Artificial Analysis 的 <a href="https://artificialanalysis.ai/evaluations/gdpval-aa">GDPval-AA v2</a>: 拿這 220 題，用三個前沿 LLM 裁判做盲測配對比較算 Elo，以人類專家 = 1000 為基準，目前 Fable 5 排第一 (1760)。跟 ALE 的差異在驗證方式: GDPval 是比較誰的成品好，沒有絕對的通過標準;ALE 是在真實軟體環境裡操作，結果可以程式化驗證。兩者互補。</p>

<p><strong><a href="https://www.remotelabor.ai/">Remote Labor Index (RLI)</a> (CAIS + Scale AI)</strong>: 直接拿真實的接案市場專案來測: 3D 建模、平面設計、影音製作、資料分析、網頁開發等，每個專案都有付費請專業人士做出的標準交付成果，由人類評審盲測判斷 AI 的產出「客戶會不會買單」，指標就叫「自動化率 (automation rate)」。2025 年底發布時最好的 agent 只有 2.5%，<a href="https://safe.ai/blog/significant-increase-in-digital-labor-automation">2026 年 7 月的更新</a>中 Fable 5 到了 15.8%，是 Opus 4.8 (8.3%) 的近兩倍。八個月內前沿成績翻了好幾倍，但離「大部分外包工作可以自動化」還很遠。這份更新還有一個做 eval 的人會有感的發現: 他們試著用 LLM 裁判取代人類評審，結果在最新模型上高估了 2 到 3 倍，排名雖然還算對，絕對數字完全不能信。</p>

<p><strong><a href="https://snorkel.ai/leaderboard/os-world-2-0/">OSWorld 2.0</a></strong>: computer use 方向的代表。2.0 版把任務換成長時程的桌面工作流，人類專家也要花一小時以上才能完成。在 500 步的預算下，沒有任何系統的端到端完成率超過 21%，最高的 Claude Opus 4.8 也只有 20.6%。報告特別點出 agent 的弱點是「維護與恢復隱藏狀態」: 漏更新、用過期的資訊繼續執行。</p>

<p><strong><a href="https://deepswe.datacurve.ai/">DeepSWE</a> (Datacurve)</strong>: 最近社群討論度很高的 coding agent benchmark，X 上比較 GPT-5.6、Fable 5、Kimi K3 的貼文常常引用它。113 個在大型程式碼庫裡的長時程工程任務，評分方式是把 agent 提交的程式碼放到乾淨的隔離環境重新驗證，7 月中剛更新 v1.1 加強可重現性。目前最高分是 GPT-5.6-Sol 的 72.7%，Fable 5 69.7%，榜單同時畫出「分數 vs 每題成本」，跟 ALE 一樣把成本當成一級指標。在 SWE-Bench 系列失去鑑別度之後，它某種程度上接手了「coding agent 進展指標」的位置，不過頂尖分數已經到七成，飽和速度值得觀察。</p>

<p><strong><a href="https://zli12321.github.io/LHTB/">Long-Horizon Terminal-Bench (LHTB)</a></strong>: 這個月剛出的，46 個需要數百個相依步驟的 terminal 長任務，配隱藏驗證器防作弊。18 個前沿模型裡最好的平均分數只有 0.51，有 29 題至今沒有任何模型解出來過。</p>

<p><strong><a href="https://metr.org/time-horizons/">METR Time Horizon</a></strong>: 嚴格說不是排行榜，而是量測「agent 有 50% 成功率完成的任務時長」。有意思的是 2026 年 5 月的更新加了一條註記: 16 小時以上的量測結果已經不可靠，因為現有任務集的長度上限快被模型追上了，連測量用的任務集本身都得再加長。</p>

<p><strong><a href="https://arcprize.org/arc-agi/3/">ARC-AGI-3</a></strong>: 走另一個極端的路線: 不測專業工作，測「面對全新環境的適應能力」。agent 被丟進沒有任何說明的互動式環境，要自己摸索規則、推斷目標、規劃行動。人類測試者可以解開 100% 的環境，而截至 2026 年 3 月，前沿 AI 的分數不到 1%，是目前差距最懸殊的知名 benchmark。</p>

<p>對照組是已經接近飽和的榜單: <a href="https://snorkel.ai/leaderboard/terminal-bench-2-1/">Terminal-Bench 2.1</a> 的第一名(Claude Code + Fable 5)已經到 83.8%，<a href="https://www.swebench.com/">SWE-Bench Verified</a> 更早就失去鑑別度。短任務、單一領域的 benchmark，參考價值正在一個一個消失。</p>

<h2 id="小編觀察">小編觀察</h2>

<p>把這些放在一起看，2026 年 agent benchmark 的設計有明顯的共同走向: 從「幾分鐘的封閉任務 + pass/fail」轉向「數小時起跳的真實工作流 + partial credit + 直接標成本」。因為前沿模型在短任務上已經拉不開差距，鑑別度只剩下長時程和專業深度這兩個方向。</p>

<p>而 ALE 用 Near-term / Full-Spectrum / Last-Exam 的分層，加上持續收新題，等於把「對抗飽和」直接設計進 benchmark 的結構裡。Dawn Song 團隊的定位很明確: 這不是拿來衝分數的榜，是拿來當長期路標的。現在 Last-Exam 那層的個位數 pass rate，就是「agent 取代專業工作」這個說法目前的真實差距。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Eval" /><category term="Benchmark" /><summary type="html"><![CDATA[最近大家常講「AI Agent 快要能接手真的工作了」，但這個說法到底怎麼驗證?UC Berkeley 的 Dawn Song 團隊發布了 Agents’ Last Exam (ALE)，直接測 agent 能不能完成「有經濟價值的真實工作」。這個團隊過去做出了 MMLU、MATH 這些經典 benchmark，這次在發表的推文裡給了一個蠻清醒的結論，翻譯過來是:]]></summary></entry><entry><title type="html">LLM-as-Judge 與其打總分，不如拆解成 Yes/No 檢核清單</title><link href="https://blog.aihao.tw/2026/07/22/llm-judge-checklist-decomposition/" rel="alternate" type="text/html" title="LLM-as-Judge 與其打總分，不如拆解成 Yes/No 檢核清單" /><published>2026-07-22T00:00:00+00:00</published><updated>2026-07-22T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/22/llm-judge-checklist-decomposition</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/22/llm-judge-checklist-decomposition/"><![CDATA[<p>如果你正在開發 LLM-as-Judge，最近有兩篇獨立的研究蠻值得參考，它們從不同方向得出了同一個結論: 與其讓 judge 用一個籠統的 prompt 對答案打 1-5 分，不如把「什麼叫好答案」拆解成一份逐項檢核清單 (checklist)，每一條都是可以獨立驗證的 Yes/No 小問題，逐項判斷後再加總成分數。</p>

<p>第一篇是 Cameron Wolfe 的 <a href="https://cameronrwolfe.substack.com/p/rubric-rl">Rubric-Based Rewards for RL</a>，整理了近期用 rubric 當 RL 獎勵訊號的一系列研究。第二篇是 <a href="https://arxiv.org/abs/2606.27226">Ask, Don’t Judge: Binary Questions for Interpretable LLM Evaluation and Self-Improvement</a>，直接把「拆解優於整體打分」當成核心命題來驗證。有意思的是兩篇的出發點完全不同: 前者是要產生 RL 訓練用的獎勵訊號，後者是純評估場景，最後卻收斂到同一個做法。文章後半再補上兩個產業案例 (Netflix 和法律 AI 公司 Harvey)，看這套做法實際用在產品上會遇到什麼問題。</p>

<h2 id="為什麼單一總分不可靠">為什麼單一總分不可靠</h2>

<p>傳統 LLM-as-Judge 讓模型直接輸出一個整體分數，問題大家應該都遇過:</p>

<ul>
  <li><strong>偏差一堆</strong>: 位置偏差、冗長偏差 (偏好長答案)、自我增強偏差 (偏好自己生成的答案) 等等</li>
  <li><strong>分數不透明</strong>: 拿到一個 3 分，你不知道是哪裡扣的分，沒辦法 debug</li>
  <li><strong>分數擠在高分區間</strong>: 拉不開差距 (ceiling effect)，跟人類實際的分數分布對不起來</li>
  <li><strong>不同 judge 模型打出來的分數落差很大</strong>: 一致性低、變異大</li>
</ul>

<p>根本原因是「好答案」本身是多維度的概念，硬要壓縮成一個數字，模型只能憑整體印象打分。而整體印象恰好最容易被表面特徵 (長度、格式、語氣) 帶偏，這跟 reward model 容易被鑽漏洞是同一類問題。</p>

<h2 id="兩篇研究的共同解法-拆解">兩篇研究的共同解法: 拆解</h2>

<div style="display:flex;flex-wrap:wrap;gap:12px;margin:1em 0">
<div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:14px;background:#ffebe9">
<div style="font-weight:bold;color:#cf222e;margin-bottom:8px">❌ 整體打分</div>
<div style="font-size:0.9em;color:#1f2328">「請評估這個回答的品質，給 1-5 分」<br /><br />→ 輸出: 3 分<br />→ 不知道為什麼是 3 分，無法 debug，不同 judge 打的分數落差大</div>
</div>
<div style="flex:1 1 300px;border:1px solid #1a7f37;border-radius:8px;padding:14px;background:#dafbe1">
<div style="font-weight:bold;color:#1a7f37;margin-bottom:8px">✅ 拆解成檢核清單</div>
<div style="font-size:0.9em;color:#1f2328">「回答是否提到 X? (Yes/No)」<br />「是否有引用來源? (Yes/No)」<br />「是否避免了 Y 錯誤? (Yes/No)」...<br /><br />→ 每項獨立驗證後加總<br />→ 哪一條 fail 一目了然</div>
</div>
</div>

<p><strong>Rubric-Based Rewards</strong> 這篇整理的做法是: 把想要的模型行為拆解成一份 rubric。單獨看每一條 criterion，都是相對客觀、可以獨立驗證的小問題;但整份清單合起來，涵蓋的是主觀、開放式的品質維度，也就是原本要靠人類偏好標籤才能捕捉的東西。文章形容 rubric 是「介於二元正確性訊號和粗粒度偏好排序之間的中間地帶」: 既保有 RLVR 那種可驗證的可靠性，又能延伸到沒有標準答案的領域。</p>

<p>實務上的設計規範:</p>

<ul>
  <li>每份 rubric 約 <strong>7-20 條自包含的 criteria</strong>，條目之間不要互相依賴</li>
  <li>每條附上權重，可以用類別 (Essential / Important / Optional / Pitfall) 或數值</li>
  <li><strong>針對每個題目量身訂做的 criteria，效果遠勝通用型 rubric</strong>: 實驗發現預先定義的通用 rubric 表現很差</li>
  <li>rubric 的生成要有專家參考答案或人類指導當依據，純合成的 rubric 可靠性明顯下降</li>
</ul>

<p><strong>Ask, Don’t Judge</strong> 這篇則是把評估拆成「原子級的二元問題」: 先用 meta-prompt 從評估標準生成一組細粒度的 Yes/No 問題，讓 LLM 對每個輸出獨立回答，再聚合成多維度的分數。</p>

<p>這個 meta-prompt 分兩步: 先把任務要求「總結」成一組明確的需求，再把每個需求「分解」成一個以上的二元問題。paper 沒有公開完整的 prompt 原文，以下是小編根據 paper 描述推測的示意版:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>以下是一個任務 prompt:

&lt;task_prompt&gt;
{{使用者的任務 prompt}}
&lt;/task_prompt&gt;

請分兩步，產生一份評估回答品質用的二元問題清單。

第一步 (總結): 分析這個任務 prompt，列出一個好的回答必須滿足
的需求。包括明確寫出的要求，也包括沒有明說但重要的品質面向
(例如事實正確、沒有捏造內容)。

第二步 (分解): 為每個需求生成一個以上的 Yes/No 評估問題，規則:
- 每個問題只檢查一件事，不要把多個條件併在同一題
- 每個問題都可以獨立回答，不依賴其他題的答案
- 措辭上「Yes」一律代表符合需求
- 附上一個簡短的違規範例，說明什麼情況該答 No

輸出格式: 逐題列出「問題 / 對應的需求 / 違規範例」。
</code></pre></div></div>

<p>以摘要任務的「事實一致性」維度為例，paper 附錄中實際生成出來的二元問題長這樣: 「摘要中的每個主張都有原文支持嗎?」「有沒有捏造原文沒有的內容?」「人名等實體正確嗎?」「數字正確嗎?」「因果關係有被保留嗎?」。原本一個籠統的「一致性 1-5 分」，就這樣變成一組具體到幾乎不需要主觀判斷的小問題。</p>

<p>效果方面，在 SummEval、Topical-Chat、QAGS 這些基準上，這個做法追平或超越 UniEval 和 G-Eval 等強基線，事實一致性的評估尤其突出，而且分數分布更貼近人類 (不會全部擠在高分)。</p>

<p>這篇還多走了一步: 每個問題的 Yes/No 結果都是可解釋的，哪一條 fail 一目了然，所以這些「問題層級的回饋」可以直接拿去迭代改進生成端的 prompt，形成 self-improvement 的回饋迴路。換句話說，評估不再只是打分數，還能告訴你「要改哪裡」。</p>

<h2 id="不只這兩篇-拆解式評估已經有不少支持證據">不只這兩篇: 拆解式評估已經有不少支持證據</h2>

<p>擴大搜尋了一圈，發現這個做法已經累積了一整條研究線。細節就不展開了，直接列每篇能學到的 insight:</p>

<ul>
  <li><a href="https://arxiv.org/abs/2410.03608">TICK</a> (2024): 拆解不只讓模型評得更準，把清單拿給「人類」標註者用，人類之間的一致性也會提升。拆解真正對齊的是「標準」，對人對模型都有效</li>
  <li><a href="https://aclanthology.org/2025.emnlp-main.796/">CheckEval</a> (EMNLP 2025): judge 打分不一致的病因是「主觀標準 + Likert 量表」的組合。拆解成 Yes/No 後，換不同模型當 judge 結果也穩定，你的 eval 才有可重現性</li>
  <li><a href="https://arxiv.org/abs/2503.05142">RocketEval</a> (ICLR 2025): 拆解可以降低對 judge 模型能力的要求，把「需要強模型才能做的整體判斷」轉換成「小模型也答得出來的具體問題」。實務上代表大規模評估可以改用便宜的小模型跑，省下大量成本</li>
  <li><a href="https://aclanthology.org/2024.acl-long.745/">LLM-Rubric</a> (ACL 2024): 逐項分數怎麼聚合成總分，不必手工定權重，可以用少量人類標註資料學出來</li>
  <li><a href="https://arxiv.org/abs/2507.18624">Checklists Are Better Than Reward Models</a> (2025): 拆解出來的訊號品質好到不只能拿來「量測」，還能直接當 RL 獎勵拿去「訓練」，在指令遵循上勝過 reward model</li>
  <li><a href="https://openai.com/index/healthbench/">HealthBench</a> (OpenAI, 2025): 262 位醫師為每個案例寫專屬 rubric，是「實例特定 rubric」的大規模實踐。高風險領域的 criteria 要由領域專家把關，不能全靠模型自動生成</li>
</ul>

<h2 id="持保留意見的研究-它到底反對什麼">持保留意見的研究: 它到底反對什麼</h2>

<p>也有一篇點出限制的研究值得認真看: <a href="https://arxiv.org/abs/2508.15218">Are Checklists Really Useful for Automatic Evaluation of Generative Tasks?</a> (2025)。</p>

<p>先說明他們檢視的流程。上面 TICK 和 Ask, Don’t Judge 這類做法，清單都不是人寫的，而是分兩段全自動: 先把題目丟給 LLM，自動生成這一題的檢核清單，judge 再拿著清單對答案逐項回答 Yes/No 加總。這篇檢視的就是第一段「清單怎麼生出來的」: 他們比較了六種自動生成策略 (直接生成、生成前先考慮可能的回答、清單縮短或加長、先生成再自我修正等)，搭配八種不同規模的 judge 模型，同時測「成對比較 (pairwise)」和「直接評分 (direct scoring)」兩種設定。</p>

<p>要先講清楚: 這篇反對的不是「拆解成清單來評」這個方向，而是「清單全自動生成、不經篩選拿來就用，一定會更準」這個假設。具體有三個發現:</p>

<ol>
  <li><strong>在直接評分的設定下，加 checklist 沒有統計顯著的改善</strong>。作者推測是模型直接打分時，其實已經隱含考慮了那些清單要素，再明文列出來的邊際效益有限。checklist 的效益主要出現在 pairwise 比較的設定</li>
  <li><strong>checklist 要「挑時機用」而不是無腦全用</strong>: 他們發現只在 judge 意見分歧大的題目上 (多次評估的投票不一致、或分數標準差高) 才套用 checklist，效果比每題都用更好。也就是說 checklist 比較像意見分歧時的仲裁工具，簡單題目直接判就好</li>
  <li><strong>自動生成的清單裡，約四成的項目跟人類判斷呈負相關</strong>。但弔詭的是，這些「壞項目」經人工審查後，超過 85% 其實是合理的評估標準，而且大多跟人類自己寫的清單重疊。作者的解讀是: 問題不在清單，而在人類評估本身就不一致，因為評估標準的定義太模糊</li>
</ol>

<p>所以這篇的結論繞回了同一個地方: 該做的是「更明確地定義客觀的評估標準」，讓人類和自動評估都有所依據。這其實跟前面幾篇的主張並不衝突，反而互補: 拆解是對的方向，但自動生成的清單品質參差，項目要篩選、要人工把關，不是拆了就自動變準。</p>

<p>值得注意的是，兩條研究線對「人工把關」的態度並不一樣。RL 那條線是有實驗數據支持的: 純合成、沒有專家參考答案的 rubric 可靠性明顯下降，HealthBench 更是直接請醫師手寫。反而是評估那條線 (TICK、Ask Don’t Judge)，賣點恰恰是「全自動、免訓練」，清單從生成到使用都不需要人介入，而這篇質疑的正是這個假設。整合起來，「拆解」兩條線都支持，「清單要人工把關」則是 RL 線從正面、這篇從反面，各自給了證據。</p>

<h2 id="產業案例一-netflix-怎麼評估數十萬份劇情簡介">產業案例一: Netflix 怎麼評估數十萬份劇情簡介</h2>

<p>Netflix 四月發表的 <a href="https://netflixtechblog.com/evaluating-netflix-show-synopses-with-llm-as-a-judge-6269251e6f28">Evaluating Netflix Show Synopses with LLM-as-a-Judge</a> 是一個很完整的實務案例，從人類標註怎麼校準一路講到系統怎麼設計。順帶一提，作者之一正是前面 Rubric-Based Rewards 那篇的 Cameron Wolfe。</p>

<p>背景是這樣: Netflix 有數十萬份劇情簡介 (synopsis)，同一部戲通常還有好幾個版本，會針對不同會員投放不同版本。簡介寫得好，會員可以快速判斷這部戲要不要看; 寫得差就會誤導人，讓會員看沒多久就放棄。問題是要靠專業寫手一份一份審，不可能覆蓋整個片庫。</p>

<p><strong>第一步不是做 judge，是先把人類的評分標準校準好。</strong> 他們找創作寫手標了約 1,000 份簡介，每份都由三位寫手各自評分並說明理由，結果一開始三個人的意見一致性很低。他們用三個做法把一致性拉起來:</p>

<ul>
  <li><strong>改用二元分數，不用 1-4 分的 Likert 量表</strong></li>
  <li>讓寫手評分時可以參考過去標好的案例</li>
  <li>維護一份可搜尋的常見錯誤分類表</li>
</ul>

<p>經過八輪校準 (每輪約 50 份)，寫手之間的一致性拉到約 80%。接著他們再加一道手續讓標籤更穩定: 多位寫手各自評分之後，交給一個 LLM 依 rubric 彙整成最終標籤，意見分歧特別大的案例則交回人工複核。最後產出約 600 份的黃金資料集，每一份都帶有 criteria 層級的二元分數和說明，這就是後面用來對齊 LLM judge 的依據。</p>

<p>這段正好從實務端佐證了前面那篇質疑研究的結論: 人類評估之所以不一致，病因是評估標準定義太模糊。而 Netflix 的處理方式就是把標準寫明確、改用二元判斷。順序也很清楚: 先把人的標準校準好，才有東西可以拿來對齊 judge。</p>

<p><strong>用一個 prompt 評所有維度，效果明顯較差。</strong> 他們一開始就發現，把所有品質維度放在同一個 prompt 裡評，模型的負擔太重，改成每個維度各用一個獨立的 judge 才有好結果。這些 judge 共通的設計是: 都用同一個 LLM、都先輸出解釋再給分、分數都是二元。二元還帶來一個額外好處，要衡量 judge 本身準不準變得很單純: 答案只有對錯兩種，直接算它在黃金資料集上的正確率就好。</p>

<p><strong>事實查核再往下拆一層: Agents-as-a-Judge。</strong> 簡介的事實錯誤有四類: 劇情講錯、metadata 講錯 (類型、地點、上映日期)、演職人員講錯、獎項講錯。這四類要核對的佐證資料完全不同，查劇情要有情節摘要或劇本，查獎項要有得獎清單。所以他們讓每個 agent 只負責查一個面向，也只給它那一類需要的 context。四個 agent 判完之後取最小值當最終分數，也就是任一項沒過，事實性就是 fail; 四個 agent 的理由再交給一個 LLM 彙整成一份總說明。</p>

<svg viewBox="0 0 700 250" style="width:100%;max-width:680px;height:auto;display:block;margin:1.2em auto;font-family:system-ui,sans-serif"><defs><marker id="nfx1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--text-secondary)" /></marker></defs><rect x="8" y="100" width="112" height="70" rx="6" style="fill:var(--surface);stroke:var(--border)" /><text x="64" y="130" text-anchor="middle" style="fill:var(--text);font-size:13px">待評估的</text><text x="64" y="148" text-anchor="middle" style="fill:var(--text);font-size:13px">劇情簡介</text><rect x="165" y="6" width="205" height="48" rx="6" style="fill:var(--tag-bg);stroke:var(--accent)" /><text x="177" y="27" style="fill:var(--text);font-size:13px">劇情 agent</text><text x="177" y="44" style="fill:var(--text-secondary);font-size:11px">context: 情節摘要 / 劇本</text><rect x="165" y="66" width="205" height="48" rx="6" style="fill:var(--tag-bg);stroke:var(--accent)" /><text x="177" y="87" style="fill:var(--text);font-size:13px">metadata agent</text><text x="177" y="104" style="fill:var(--text-secondary);font-size:11px">context: 類型 / 地點 / 日期</text><rect x="165" y="126" width="205" height="48" rx="6" style="fill:var(--tag-bg);stroke:var(--accent)" /><text x="177" y="147" style="fill:var(--text);font-size:13px">卡司 agent</text><text x="177" y="164" style="fill:var(--text-secondary);font-size:11px">context: 演職人員名單</text><rect x="165" y="186" width="205" height="48" rx="6" style="fill:var(--tag-bg);stroke:var(--accent)" /><text x="177" y="207" style="fill:var(--text);font-size:13px">獎項 agent</text><text x="177" y="224" style="fill:var(--text-secondary);font-size:11px">context: 得獎紀錄</text><path d="M120,135 C142,135 142,30 161,30" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M120,135 C142,135 142,90 161,90" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M120,135 C142,135 142,150 161,150" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M120,135 C142,135 142,210 161,210" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M370,30 C396,30 396,135 416,135" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M370,90 C396,90 396,135 416,135" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M370,150 C396,150 396,135 416,135" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><path d="M370,210 C396,210 396,135 416,135" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><rect x="420" y="100" width="100" height="70" rx="6" style="fill:#fff8c5;stroke:#9a6700" /><text x="470" y="128" text-anchor="middle" style="fill:#9a6700;font-size:13px">取最小值</text><text x="470" y="148" text-anchor="middle" style="fill:#9a6700;font-size:11px">一項 fail</text><text x="470" y="163" text-anchor="middle" style="fill:#9a6700;font-size:11px">就整體 fail</text><path d="M520,135 L556,135" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#nfx1)" /><rect x="560" y="111" width="132" height="48" rx="6" style="fill:var(--surface);stroke:var(--border)" /><text x="626" y="140" text-anchor="middle" style="fill:var(--text);font-size:13px">事實性 pass/fail</text></svg>

<p>他們的結論是「簡單帶來可靠: context 太多或 criteria 太多都會傷害準度」。而取最小值這個聚合方式，其實就是下面 4️⃣ 權重規則的極端版本: 所有條目都算 Essential，一條沒過就整體不通過。</p>

<p><strong>解釋要寫多長，是準度和可讀性的取捨。</strong> 他們發現 judge 的解釋寫得越長，判斷越準，但邊際效益遞減。麻煩的是長篇解釋人類看不下去，而這些解釋本來就是要給創作專家看、當作判斷依據的。解法是分層解釋 (tiered rationales): judge 內部推理不限長度，但輸出前必須把推理過程濃縮成精簡摘要，再給最終分數。tone 這個維度的二元準確率因此從 86.55% 提升到 87.85%。</p>

<p><strong>重複跑同一個 judge 取共識 (consensus)，不是加了就有用。</strong> 先解釋一下這個做法: 它跟 prompt 無關，而是同一份簡介、同一個 prompt，讓 judge 重複跑 5 次 (temperature 不為 0，每次的解釋和分數會有出入)，再把 5 次的分數平均、四捨五入回二元。這就是常見的 self-consistency，純粹用多花的呼叫次數換準度。</p>

<p>Netflix 的結果是: tone 和 clarity 這兩個用長解釋的維度有明顯提升，但 precision 這個只用短 CoT 的維度完全沒差。原因是短解釋每次跑出來的分數幾乎都一樣，根本沒有變異可以平均掉，跑 5 次就是白花 5 倍成本。所以要不要加 consensus，有個很簡單的判斷方式: 先拿同一題重複跑幾次，看分數變異大不大，變異小就別加。</p>

<p>同樣的成本考量也出現在模型選擇上。他們測過真正的推理模型配 5 次 consensus，推理強度拉越高準度越好，最高強度甚至贏過分層解釋，但最終系統還是沒有採用，因為成本上升很多，換來的增益卻有限。</p>

<p><strong>驗證分兩步: 先確認 judge 準不準，再確認這套標準本身有沒有用。</strong></p>

<p>第一步是拿 judge 判的結果，跟創作寫手標好的黃金資料集比對，一致性 85% 以上。多數團隊做到這裡就算完成了。</p>

<p>第二步是 Netflix 額外做的: 檢查 LLM 給的分數能不能預測會員的實際行為。行為指標有兩個，take fraction (看到簡介的人裡，有多少比例真的開始看) 和 abandonment rate (開始看之後很快就放棄的比例)，這兩個指標都經過 A/B test 驗證過，可以當作長期留存的短期代理指標。</p>

<p>做法是利用同一部戲有多份簡介的特性，在同一部戲內部比較「這份簡介的 LLM 分數比其他份高多少」和「它的實際表現好多少」。之所以要限制在同一部戲內部比較，是因為不同戲的先天熱門程度差太多，跨戲比較會被戲本身的吸引力蓋過，看不出簡介的效果。結果是 precision 和 clarity 最能預測會員行為，加權總分則跟「較高的 take、較低的 abandonment」有統計上有用的關聯。</p>

<p>小編覺得第二步才是這個案例最值得學的地方。judge 跟專家一致，只證明你成功複製了專家的判斷，並不證明專家這套標準本身是有價值的。Netflix 多做的這一步，是拿下游的實際指標回頭檢查評估標準站不站得住。而做到這個程度的實際好處是: 一部戲上線前幾週甚至幾個月，就能先找出並修掉會影響表現的問題。</p>

<h2 id="產業案例二-拆解的執行成本與取捨">產業案例二: 拆解的執行成本與取捨</h2>

<p>拆解式評估在實務上有一個現實問題: 每條 criterion 都獨立呼叫一次 LLM，規模一大成本很可觀。LangChain 和法律 AI 公司 Harvey 合作的研究 <a href="https://x.com/LangChain/status/2061864647884464430">Designing Efficient Verifiers for Legal Agents</a> 正面處理了這件事，是很好的參考案例。</p>

<p>背景是 Harvey 開源的法律 agent benchmark LAB，驗證方式正是本文講的拆解式: 每個任務有一組 criteria (很多任務超過 50 條)，每條由 LLM verifier 獨立判 pass/fail。他們這次實驗跑了 40 個任務，加起來有 2,348 條 criteria 要判。如果每條都用 frontier 模型呼叫一次，做一輪評估還勉強可以，但拿去跑 RL post-training 成本就負擔不起了，因為每個任務還要乘上多次 rollout。</p>

<p>他們測了兩個省成本的方向:</p>

<ul>
  <li><strong>一次判整份清單 (batch)</strong>: 原本一條 criterion 呼叫一次，改成一次呼叫就把整份清單判完，逐條輸出結果。省下的主要是重複的輸入 token (逐條呼叫時，題目和 agent 的長答案每次都要重新附上)，同一個模型可以便宜一個數量級。代價是準度: 在他們的實驗裡，batch 跟基準的一致性全面低於逐條獨立判。不過要注意，合併的只是「執行方式」，評估標準還是那份拆解好的清單，並沒有退回打一個總分</li>
  <li><strong>換便宜的模型</strong>: 開源模型 (DeepSeek) 判的結果跟 Opus 高度接近，成本卻低了幾個數量級。這呼應前面 RocketEval 的 insight: 拆解後的具體小問題，不需要最強的模型來答。但也不是隨便挑一個便宜模型都行: Haiku 雖然便宜，卻明顯太寬鬆，大量把該 fail 的判成 pass。在法律這種高風險領域，這是錯誤的失敗方向: 誤判成 fail 頂多多送一次人工複核，把該擋的放過去才是真正的事故</li>
  <li><strong>掉的準度可以用 prompt 補救</strong>: 他們分析便宜模型判錯的原因，發現是一條 criterion 內部往往包含多個要件，模型看到答案「相關」就放行，沒有逐一核對。修正方式是在 prompt 裡要求 verifier 把每條 criterion 再拆成更細的 checklist 逐項確認，資訊不明確時保守判 No，false-pass 率就降下來了。連救回準度的方法都還是「再多拆一層」，跟 Netflix 那邊用 agent 拆事實查核是同一個方向</li>
</ul>

<blockquote>
  <p>編按: 那評估到底該「逐條獨立呼叫」還是「一次判整份清單」?其實前面提到的研究兩種都有人用: TICK 是逐條獨立呼叫，同一題還可以問多次做多數決;RocketEval 則是一次判整份清單，效率本來就是它的賣點。Rubric RL 那篇把這兩種做法稱為「顯式聚合」(逐條評完再加權加總) 和「隱式聚合」(整份丟給模型綜合判)，而 RaR 的實驗結果甚至是隱式略優，跟 Harvey 這裡 batch 較差的結果方向相反。Netflix 的經驗則跟 Harvey 同方向: 用單一 prompt 評所有維度效果明顯較差。哪種比較準目前沒有定論，建議在自己的任務上實測，別直接套任何一邊的結論。</p>
</blockquote>

<p>這篇還有一個跟前面呼應的發現: 連 GPT-5.5 和 Opus 這種等級的模型，逐條判的一致性也只有 95.7%。作者的解讀是部分 criteria 本身寫得不夠明確，模型無法像專家一樣穩定套用。繞回同一件事: 清單的品質是一切的前提。</p>

<h2 id="給-ai-engineer-的共通建議">給 AI Engineer 的共通建議</h2>

<p>把這些研究的結論收斂一下，開發 LLM-as-Judge 時:</p>

<p>1️⃣ <strong>用 Yes/No 二元判斷取代 1-5 分</strong>: 每個小問題越客觀、越可獨立驗證越好。judge 回答「這個回答有沒有引用來源」比回答「這個回答好不好」可靠太多了</p>

<p>2️⃣ <strong>criteria 要針對任務甚至針對單一題目設計</strong>: 通用型的「正確性、流暢性、相關性」這種維度效果最差。可以用強模型從參考答案或專家指導生成 instance-specific 的清單</p>

<p>3️⃣ <strong>條目要自包含、不互相依賴</strong>: 這樣每一條才能獨立驗證，也才能平行化評估</p>

<p>4️⃣ <strong>用權重表達優先序</strong>: 不是每條都一樣重要，Essential 沒過可以直接判不通過，Optional 只是加分</p>

<p>5️⃣ <strong>把 fail 的條目當 debug 訊號</strong>: 拆解式評估最大的好處是可解釋性，哪條 fail 直接告訴你 prompt 或系統要改哪裡，甚至可以自動化這個改進迴路</p>

<p>6️⃣ <strong>先校準人類的標準，再談對齊 judge; 驗證盡量做兩層</strong>: Netflix 是先花八輪把寫手之間的一致性拉到 80%，才有黃金資料集可以拿來對齊 LLM。而 judge 跟專家一致只是第一層，條件允許的話，最好再拿下游的實際指標回頭驗證這套標準真的有價值</p>

<p>這跟 ihower 在上的 AI Evals 課程 (Hamel Husain 與 Shreya Shankar 開的) 一直強調的原則也蠻呼應的: judge 盡量用二元的 pass/fail 判斷，不要用 Likert 量表打分，因為人類自己都無法穩定區分 3 分和 4 分的差別，模型當然也不行。Netflix 那邊八輪校準的第一個介入措施就是改二元，算是實務上的佐證。</p>

<p>小編覺得這件事的本質是: 評估的難度不會消失，只會轉移。你省掉的「打分」難度，其實是轉移到了「事先把好答案的標準想清楚、寫下來」這件事上。而這件事恰好是值得做的，因為寫清單的過程會逼你把模糊的品質直覺變成明確的規格，這份規格不只能交給 judge 用，也能回頭改進你的 prompt 和產品需求文件。</p>]]></content><author><name>ihower</name></author><category term="Eval" /><category term="Prompt" /><summary type="html"><![CDATA[如果你正在開發 LLM-as-Judge，最近有兩篇獨立的研究蠻值得參考，它們從不同方向得出了同一個結論: 與其讓 judge 用一個籠統的 prompt 對答案打 1-5 分，不如把「什麼叫好答案」拆解成一份逐項檢核清單 (checklist)，每一條都是可以獨立驗證的 Yes/No 小問題，逐項判斷後再加總成分數。]]></summary></entry><entry><title type="html">如何萃取老師傅的知識做成 Agent Skill? 六條路線的方法論框架</title><link href="https://blog.aihao.tw/2026/07/06/knowledge-to-skill-framework/" rel="alternate" type="text/html" title="如何萃取老師傅的知識做成 Agent Skill? 六條路線的方法論框架" /><published>2026-07-06T00:00:00+00:00</published><updated>2026-07-06T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/06/knowledge-to-skill-framework</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/06/knowledge-to-skill-framework/"><![CDATA[<p>組織裡最有價值的知識，往往集中在少數資深專家身上: 文件寫不全、標準作業流程涵蓋不了，人一離開，知識就跟著離開。這是知識管理領域的老問題，但現在多了一個新解法: 把專家知識萃取出來，做成 AI Agent 可以載入的 skill，讓非專家也能做出專家等級的產出。最近 GitHub 上「員工蒸餾」專案引發的討論（後面 C 路線會講），也讓更多人開始關注這件事。</p>

<p>小編把目前找得到的論文和工具整理成一個方法論框架。範圍先講清楚: <strong>知識來源是真人專家，終點是 Agent Skill</strong>。至於 agent 從自己的執行軌跡歸納 skill 的自我改進路線（SkillWeaver、Trace2Skill 那一系），不在這篇的討論範圍，這裡談的都是把「人」的知識拿出來。</p>

<p>整個框架只需要回答一個問題: <strong>專家的知識，現在存在哪裡?</strong> 答案有五種，對應六條萃取路線:</p>

<div style="margin:18px 0;border:1px solid var(--border);border-radius:10px;overflow:hidden;">
<div style="background:var(--accent);color:#fff;padding:12px 14px;font-weight:700;text-align:center;font-size:1.02em;">📍 專家的知識，現在存在哪裡?</div>
<div style="display:flex;flex-direction:column;gap:10px;padding:12px;">
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;">
<div style="flex:1 1 200px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:10px 12px;font-size:0.92em;"><b>只在專家腦中</b><br /><span style="color:var(--text-secondary);">沒有任何紀錄</span></div>
<div style="flex:2 1 300px;display:flex;flex-wrap:wrap;gap:8px;">
<div style="flex:1 1 150px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">A</span><span><b>人工訪談</b> 🗣</span></div>
<div style="flex:1 1 150px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">B</span><span><b>AI 訪談</b> 🤖</span></div>
</div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;">
<div style="flex:1 1 200px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:10px 12px;font-size:0.92em;"><b>在數位足跡裡</b><br /><span style="color:var(--text-secondary);">聊天、文件、code review</span></div>
<div style="flex:2 1 300px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">C</span><span><b>數位足跡蒸餾</b> 🗂</span></div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;">
<div style="flex:1 1 200px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:10px 12px;font-size:0.92em;"><b>在操作過程裡</b><br /><span style="color:var(--text-secondary);">做得出來但說不出來</span></div>
<div style="flex:2 1 300px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">D</span><span><b>示範錄製</b> 🎥</span></div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;">
<div style="flex:1 1 200px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:10px 12px;font-size:0.92em;"><b>在教材裡</b><br /><span style="color:var(--text-secondary);">教學影片、文章、手冊</span></div>
<div style="flex:2 1 300px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">E</span><span><b>教材蒸餾</b> 📚</span></div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch;">
<div style="flex:1 1 200px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px;padding:10px 12px;font-size:0.92em;"><b>在未來的協作裡</b><br /><span style="color:var(--text-secondary);">還沒發生的判斷與糾正</span></div>
<div style="flex:2 1 300px;display:flex;align-items:center;gap:8px;background:var(--tag-bg);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-size:0.92em;"><span style="flex:none;width:20px;height:20px;line-height:20px;text-align:center;background:var(--accent);color:#fff;border-radius:50%;font-size:0.78em;font-weight:700;">F</span><span><b>互動結晶</b> 🔄</span></div>
</div>
</div>
</div>

<p>在逐條介紹之前，先講一下為什麼終點選 skill。Anthropic 官方在 <a href="https://claude.com/blog/building-agents-with-skills-equipping-agents-for-specialized-work">Building Agents with Skills</a> 的定位是: skill 用檔案打包領域專業知識（工作流程、最佳實務、腳本），靠漸進式載入 (progressive disclosure) 控制上下文用量: 平常只載入約 50 token 的描述資訊，需要時才讀取 SKILL.md 主文件，深度參考資料則放得更後面。skill 還能內含腳本當工具，可執行的部分完全不用進上下文。這個「<code class="language-plaintext highlighter-rouge">scripts/</code>（可執行規則）+ SKILL.md（判斷知識）+ <code class="language-plaintext highlighter-rouge">references/</code>（深度參考）」的結構，正好給知識萃取的產出一個標準容器。而且 Anthropic 觀察到，動手寫 skill 的人已經不限於工程師，產品經理、分析師、領域專家都在自己動手。以下六條路線的產出，最後都會落到這個容器裡。</p>

<h2 id="-a-人工訪談-品質最高也最貴">🗣 A. 人工訪談: 品質最高，也最貴</h2>

<p><strong>做法</strong>: 半結構化訪談，搭配情境式追問。<a href="https://arxiv.org/abs/2601.15153">Siemens 的論文</a>是標準示範: 他們訪談兩位內部專家（一位模擬分析軟體專家、一位視覺化設計專家），各 60 到 90 分鐘、分開進行避免互相影響。訪談大綱只有三個主題: (1) 目前的工作流程和痛點、(2) 專家建立視覺化時的決策過程、(3) 實務上使用的具體規則和經驗法則。提問從開放式問題起頭，像是「可以帶我走一遍你為模擬資料建立視覺化的典型流程嗎?」「最常用、最有用的圖表是哪些?」，再追問具體規則和判斷標準。另外搭配「情境式討論」: 拿範例任務給專家看，讓他在回應中示範怎麼判斷。這是關鍵技巧，因為隱性知識直接問是問不出來的，要靠具體情境讓專家把判斷過程講出來。</p>

<p>有一點蠻意外的: 專家在訪談中就直接給出明確規則，例如「分析開始前，必須先確認最佳化目標是否收斂」，不需要研究者事後再做詮釋分析，記下來就能實作。萃取出來的知識有兩種型態，去處也不同:</p>

<div style="display:flex;flex-wrap:wrap;gap:12px;margin:16px 0;">
<div style="flex:1 1 300px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:6px;">明確程序規則 → 寫成程式碼</div>
<div style="font-size:0.92em;color:var(--text-secondary);">條件明確的判斷邏輯，例如「分析前先檢查目標是否收斂」。直接寫成 Python 函式，執行後產生分析報告，自動附加到 system prompt。</div>
</div>
<div style="flex:1 1 300px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px;">
<div style="font-weight:600;color:#1a7f37;margin-bottom:6px;">隱性設計原則 → 寫進 Prompt</div>
<div style="font-size:0.92em;color:var(--text-secondary);">依情境判斷的知識，例如「未收斂的變數用虛線、已收斂用實線」「一張圖最多放兩個變數」。寫進 system prompt 或 RAG，讓模型推理時套用。</div>
</div>
</div>

<p>論文特別提到，單獨用任何一種都不夠: 只有程式碼會缺乏分析洞察，只靠模型又會產出不符合領域要求的東西，兩者搭配才有效。這個「<strong>可以寫成程式碼的就寫成程式碼，不然就用 prompt</strong>」的分工原則，小編覺得是全篇最值得帶走的一句話。成果也很具體: 非專家用簡單的 prompt 產出的圖表，品質比未加持的版本提升 206%（12 位評審、5 個場景）。</p>

<p><img src="/assets/images/expert-knowledge-extraction.png" alt="" /></p>

<p>而且萃取不是一次就能完成的。初版結果發現生成的圖表資訊量不足、有視覺缺陷，他們才回頭向視覺化專家做針對性補訪，聚焦在最常用的三種圖表類型。真實流程是「訪談 → 實作 → 測試發現不夠 → 針對性補訪」，這跟 AI 評估圈講的錯誤分析 (error analysis) 是同一個邏輯: 先做出第一版，讓失敗案例告訴你接下來要補哪一塊知識。</p>

<p>論文還有兩個實務註腳。第一，為什麼只用兩位專家? 他們的理由是: 需要涵蓋的兩個專業領域很少重疊、目標是系統性知識萃取而非統計推論、驗證靠客觀技術指標，而且知識工程文獻本來就認為 1 到 3 位專家足以建構一致的知識庫。第二，第一作者本身是 Siemens 員工，能接觸內部資料和內部討論: 很多知識不是那 90 分鐘訪談問出來的，是長期待在組織裡累積的。</p>

<p><strong>訪談技巧的系譜與工具箱</strong>: 這套技術的源頭是 1980 年代專家系統時代的知識工程。<a href="https://doi.org/10.1109/21.31053">關鍵決策法 (Critical Decision Method)</a> 原本是 Klein 在 1989 年用來訪談消防指揮官的，因為他們的判斷依賴講不出來的知覺線索，只能靠「回溯真實事件加上探測式問題」引出來。<a href="https://doi.org/10.1518/001872098779480442">Hoffman 等人 1998 年的方法論回顧</a>和<a href="https://doi.org/10.1177/10944281241271216">認知任務分析 (Cognitive Task Analysis) 的現代整理</a>都值得參考。當年萃取出來的規則放進規則引擎，現在放進 skill，訪談技術本身沒變。<a href="https://agentpatterns.ai/workflows/encoding-tacit-knowledge/">AgentPatterns 的整理</a>把這些技巧翻新成 agent 時代的版本，核心觀察是: 專家「說自己怎麼做」和「實際怎麼做」中間有落差，直接問規則只能拿到前者。三個實用技巧:</p>

<ul>
  <li><strong>困難案例訪談</strong>: 請專家走一遍他親身遇過的困難案例（不是假設情境），針對每個案例問四個問題: 你最先注意到什麼訊號? 當時預期接下來會發生什麼? 有哪些互相衝突的優先順序? 你立刻想到哪些做法? 這四問就出自關鍵決策法，專門用來取得專家自己意識不到的模式判斷。</li>
  <li><strong>失敗案例訪談</strong>: 拿 agent 的實際失敗輸出給專家看，請他說明「正確的輸出長什麼樣、為什麼」。解釋的過程，就會透露他心裡的隱性標準。</li>
  <li><strong>範例標註</strong>: 給專家一批好壞混雜的輸出，請他逐一標註，標註理由就是評估準則，這正是「用 LLM 當評審 (LLM-as-judge)」機制裡人類要負責的那一半。<a href="https://hamel.dev/blog/posts/llm-judge/">Hamel Husain 的建議</a>是找一位內部領域專家當品質標準的最終決策者就好，外部標註人員缺乏組織脈絡，反而製造更多分歧。</li>
</ul>

<p>還有一個值得認識的變形: <a href="https://arxiv.org/abs/2605.25549">BC Protocol</a> 讓「領域專家 + 知識工程師」兩人配對對話。理由是「專家盲點」: 專家獨自寫文件時，會跳過他認為理所當然的推理步驟，必須靠一個「校準過的外行人」在對話中把這些步驟逼出來。實驗顯示，對話產出的推理過程在自然度上大幅領先專家獨寫（評分 4.80 比 1.30）。這篇的產出目標是微調 (fine-tuning) 用的推理鏈資料而不是 skill，但「雙人對話外化隱性判斷」的技術完全可以搬過來用。</p>

<ul>
  <li><strong>優點</strong>: 品質最高、能拿到任何紀錄裡都沒有的「為什麼」、可以即時追問和驗證理解。</li>
  <li><strong>缺點</strong>: 最貴、依賴訪談技巧、專家時間稀缺，而且一次訪談問不完（Siemens 也是靠失敗案例回頭補訪）。</li>
  <li><strong>適合</strong>: 知識沒有數位足跡的老師傅（產線、實驗室、現場判斷）、高風險高價值領域、專家即將離職退休的搶救場景。</li>
</ul>

<h2 id="-b-ai-訪談-用規模換深度">🤖 B. AI 訪談: 用規模換深度</h2>

<p><strong>做法</strong>: 讓 AI 當訪談者。<a href="https://arxiv.org/abs/2605.23985">Genentech 的 Protocol Intelligence Co-pilot</a> 是目前最完整的案例: AI 訪談 agent 套用一組結構化的「提問視角」訪談實驗室科學家，產出附專家信心分數的知識圖譜。他們要捕捉的是「自動化掩蓋的沉默失敗」這類只存在專家判斷裡的知識: 儀器紀錄回報執行成功，但實驗的科學有效性其實已經出問題。這種知識在實驗協定文件、感測器數據、既有的知識本體裡都找不到，只能從專家口中問出來。學術圈也在把訪談本身形式化，例如 <a href="https://doi.org/10.48550/arxiv.2602.21136">SparkMe</a> 把「照大綱問」和「追新線索」之間的取捨寫成效用函數，用來指導 AI 訪談員; <a href="https://link.springer.com/article/10.1007/s12599-025-00976-w">Springer BISE 的研究</a>則讓對話 agent 模擬訪談流程，專家回答完直接產出 BPMN 流程圖。</p>

<p>Anthropic 官方的 <a href="https://claude.com/blog/building-agents-with-skills-equipping-agents-for-specialized-work">skill-creator</a> 其實也算這條路線的輕量版: 它用互動方式引導領域專家（不需要是工程師）在 30 分鐘內做出第一個 skill，本質上就是「AI 訪談你，然後幫你寫成 SKILL.md」。</p>

<ul>
  <li><strong>優點</strong>: 便宜、可重複多次、可以同時規模化到很多位專家、專家可以自助。</li>
  <li><strong>缺點</strong>: 追問深度不如老練的人類訪談者。Genentech 的數據很直白: 多輪對話式追問的訪談逐字稿，後續萃取結果高度一致; 單次問答式的一致性就差很多（失敗模式辨識的 F1 分數從 1.0 掉到 0.43）。訪談品質決定萃取品質，AI 訪談員目前還在追趕。</li>
  <li><strong>適合</strong>: 專家人數多而訪談人力稀缺的組織、想讓領域專家自己動手把知識寫成 skill 的場景。</li>
</ul>

<h2 id="-c-數位足跡蒸餾-不佔專家時間但雜訊多">🗂 C. 數位足跡蒸餾: 不佔專家時間，但雜訊多</h2>

<p><strong>做法</strong>: 不問人，直接處理專家留下的數位足跡。這條就是最近討論度很高的「員工蒸餾」: 今年 3 月底，上海 AI Lab 工程師 Tianyi Zhou 開源了 <a href="https://github.com/titanwings/colleague-skill">colleague-skill</a>（同事.skill）專案，兩週內拿到 13k GitHub 星星，口號寫著「將冰冷的離別化為溫暖的 Skill，歡迎加入賽博永生」，後來團隊把方法寫成論文 <a href="https://arxiv.org/abs/2605.31264">COLLEAGUE.SKILL</a>。</p>

<p>它的流程是目前文件化最完整的: 收集器和解析器先把聊天記錄、工作文件、電子郵件、會議截圖正規化成本地的知識目錄 → 分析器從中萃取「持久能力、思維模型、互動風格」的證據 → 建構器渲染成結構化的 Markdown → 寫入器打包成版本化的套件。產出分成兩軌: <code class="language-plaintext highlighter-rouge">work.md</code> 放工作方法、審查標準、決策準則，<code class="language-plaintext highlighter-rouge">persona.md</code> 放溝通風格和互動約束，可以安裝到 Claude Code 等 agent 環境。專案文件建議優先收集「主動寫的長文」和「決策類回覆」，因為這兩類素材最能反映一個人怎麼判斷。</p>

<p>它的<strong>修正循環</strong>設計特別值得抄: 使用者用自然語言回饋（「他不會這樣說」「這種情況他會反對」），系統轉成對應段落的修正補丁或修正紀錄，版本遞增、可以回滾。等於承認第一版蒸餾一定不準，把「修」設計成一級操作。同路線的還有 <a href="https://github.com/bigmoon-dev/dianoia-ai">Dianoia</a>，從公開產出物（設計文件、code review）蒸餾出「專家怎麼想」: 怎麼定義問題、怎麼判斷品質、怎麼取捨。</p>

<p>實際案例也出現了: <a href="https://www.citynewsservice.cn/articles/shanghaidaily/news/the-employee-quit-but-her-ai-clone-didnt-inside-chinas-colleague-skill-craze-yn6ow4dn">Shanghai Daily 報導</a>，山東一家遊戲公司的人資離職後，公司沒有補人，而是用她的聊天記錄和工作文件建了一個數位分身，接手例行詢問和文件工作，同事的評語是「有點笨，只能處理簡單指令」。中國社群也隨之出現「反蒸餾」工具，教你在離職前避免自己被做成 skill，勞動價值歸屬和隱私的爭議跟著浮上檯面（<a href="https://www.ctee.com.tw/news/20260409701379-430704">工商時報的整理</a>）。</p>

<ul>
  <li><strong>優點</strong>: 完全不佔專家時間、人已經離職也能事後做、拿到的是「實際怎麼做」而不是「自己說怎麼做」。</li>
  <li><strong>缺點</strong>: 前提是要有數位足跡，而且數位足跡雜訊多。COLLEAGUE.SKILL 論文自己明確承認只做「產物層級」的主張，不宣稱能還原本人行為。另外還有當事人同意、隱私、勞動價值歸屬的爭議。</li>
  <li><strong>適合</strong>: 知識工作者的離職交接、有大量文字產出的角色。反過來說，工作不留數位足跡的老師傅，這條路線直接不適用。</li>
</ul>

<h2 id="-d-示範錄製-捕捉做得出來但說不出來的操作">🎥 D. 示範錄製: 捕捉「做得出來但說不出來」的操作</h2>

<p><strong>做法</strong>: 請專家把工作做一遍，錄下來，讓模型從錄影中萃取步驟化的操作知識。這條路線目前的研究集中在圖形介面操作: <a href="https://arxiv.org/abs/2606.12817">Teach-and-Repeat</a> 從手機螢幕示範中萃取「動作類型、目標元件、輸入內容、執行順序」這樣的操作知識; <a href="https://arxiv.org/abs/2509.07098">Instruction Agent</a> 從單次示範抽出逐步指令，然後嚴格照著執行; <a href="https://dl.acm.org/doi/10.1145/3772318.3790294">InvisibleMentor (CHI 2026)</a> 則反過來，從螢幕錄影中偵測低效率的操作，推薦更好的工作流程。</p>

<ul>
  <li><strong>優點</strong>: 專家的成本最低（做一遍就好，不用講也不用寫）、能捕捉時間順序和畫面狀態這種文字寫不出來的資訊。</li>
  <li><strong>缺點</strong>: 只拿得到「怎麼操作」，拿不到「為什麼這樣做」和「什麼情況要改用別的做法」，這些判斷還是得靠訪談補。而且目前技術集中在螢幕操作，實體世界的示範要另外解決感測問題。</li>
  <li><strong>適合</strong>: 軟體操作型的專家知識（複雜工具鏈的老手、內部系統的熟練操作者）。當成訪談的前置素材也很好用: 先錄示範，再拿著錄影做情境式追問。</li>
</ul>

<h2 id="-e-教材蒸餾-專家已經整理過一次別浪費">📚 E. 教材蒸餾: 專家已經整理過一次，別浪費</h2>

<p><strong>做法</strong>: 專家為了教人而做的教材（教學影片、文章、手冊、範例專案），本身就是半結構化的知識，直接拿來蒸餾。Microsoft 的 <a href="https://arxiv.org/abs/2606.29538">Resource2Skill</a> 把教學影片、程式庫、文章蒸餾成可執行的 skill，組成階層式的多模態 Skill Wiki，每個條目包含結構化文字、程式碼、視覺範例和出處。設計理由是三種素材互補: 影片保留操作的時間順序和視覺效果、程式碼保留可執行的工具使用模式、文章提供概念基礎。在七個創作領域的實驗中，平均提升 11.9 個百分點。</p>

<p>社群工具 <a href="https://github.com/kylezantos/skill-distillery">skill-distillery</a> 也屬於這條路線，它有一句話講到重點: 把 5000 字文章的段落重排成 SKILL.md 格式，不算做成 skill。真正的萃取是由下而上: 先抽出讓方法有效的原則、組織這個領域的思維模型、做事時需要的決策準則，然後才設計 skill 的架構。</p>

<ul>
  <li><strong>優點</strong>: 素材現成、專家已經做過一輪結構化、不需要接觸專家本人。</li>
  <li><strong>缺點</strong>: 教材是「教人版」不是「實戰版」，通常缺少失敗處理、邊界案例和真實世界的髒細節，而這些恰恰是老師傅最值錢的部分。教材也會過時。</li>
  <li><strong>適合</strong>: 有成熟教學資源的領域、引入外部專家知識（買得到課程但請不到人）、給訪談路線當基礎，讓訪談時間留給教材沒寫的部分。</li>
</ul>

<h2 id="-f-互動結晶-不做前期萃取邊協作邊沉澱">🔄 F. 互動結晶: 不做前期萃取，邊協作邊沉澱</h2>

<p><strong>做法</strong>: 前面五條都把萃取當成一次性的前期工程，<a href="https://arxiv.org/abs/2603.10808">Nurture-First Agent Development</a> 反對這個前提: 專家知識是隱性的、個人的、持續演化的，一次性萃取注定會漏。它主張 agent 從最小配置開始，在與專家的日常協作對話中累積知識，再靠「知識結晶循環」定期把對話中的碎片判斷，固化成結構化的知識資產。</p>

<p>注意這跟「agent 看自己的執行軌跡自我改進」不一樣: 結晶的素材是專家在協作中給出的判斷、偏好和糾正，知識來源仍然是人。老實說，這就是 Claude Code 重度使用者每天在做的事: 用的過程中發現 agent 不對，糾正它，然後把糾正沉澱進 CLAUDE.md 或 skill。這篇論文等於把這個日常實務形式化了。</p>

<ul>
  <li><strong>優點</strong>: 沒有前期萃取成本、知識跟著真實使用演化、專家提供素材的動機自然（他本來就要把工作做完）。</li>
  <li><strong>缺點</strong>: 冷啟動期 agent 表現差、依賴專家持續使用和糾正的意願、結晶需要紀律（不定期整理，就只是一堆散落的對話）。</li>
  <li><strong>適合</strong>: 專家本人就是 agent 使用者的場景、長期一對一協作的 agent。不適合「專家即將離開、需要搶時間」的場景。</li>
</ul>

<h2 id="六條路線的共通原則">六條路線的共通原則</h2>

<p>把六條路線攤開看，不管走哪一條，有四個原則是共通的。</p>

<p><strong>1. 要萃取的東西是一樣的</strong>: 原則、思維模型、決策準則、可執行步驟。所有認真的做法都反對「格式重排就當萃取完成」，COLLEAGUE.SKILL 和 skill-distillery 在這點上講得最明白。</p>

<p><strong>2. 第一版永遠是草稿</strong>: Siemens 靠失敗案例回頭補訪、COLLEAGUE.SKILL 把修正和回滾設計成一級操作、Nurture-First 乾脆把迭代當成方法本身。沒有任何一條路線宣稱一次到位，差別只在迭代的觸發方式: 評估失敗、使用者糾正，或是定期結晶。</p>

<p><strong>3. 知識放到哪裡，分法是固定的</strong>: 能寫成程式碼的規則放 <code class="language-plaintext highlighter-rouge">scripts/</code>、判斷式知識寫進 SKILL.md、深度參考放 <code class="language-plaintext highlighter-rouge">references/</code>、品質標準寫成 eval。AgentPatterns 把對應關係整理得很清楚:</p>

<table>
  <thead>
    <tr>
      <th>萃取產出</th>
      <th>編碼成什麼</th>
      <th>適用時機</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>明確規則（「做 Y 之前一定要檢查 X」）</td>
      <td>硬性約束或 <code class="language-plaintext highlighter-rouge">scripts/</code> 程式碼</td>
      <td>不可妥協、機器可檢查</td>
    </tr>
    <tr>
      <td>情境判斷（「情境 Z 時偏好 A 不選 B」）</td>
      <td>SKILL.md 裡附說明的範例</td>
      <td>依賴脈絡、寫不成規則</td>
    </tr>
    <tr>
      <td>品質標準（「這個輸出不好，因為…」）</td>
      <td>eval 測試加上評分準則</td>
      <td>用來校準自動評估</td>
    </tr>
  </tbody>
</table>

<p>小編覺得第三列最容易被忽略: 有些專家知識最適合的存放形式不是 prompt，而是 eval。太依賴當下脈絡的判斷，硬寫成靜態規則，平均來看是對的，但在最要緊的個案上反而出錯，不如寫成 eval 去測試行為。</p>

<p><strong>4. 萃取完，還要壓縮</strong>: 訪談逐字稿、蒸餾出來的草稿，就算整理成文件通常還是太長。直接塞進 skill 會佔掉大量上下文，效果反而變差。壓縮是獨立的一道工序，具體做法值得單獨講，見下一節。</p>

<h2 id="壓縮的做法-不是摘要而是重組">壓縮的做法: 不是摘要，而是重組</h2>

<p>壓縮這個主題，<a href="https://arxiv.org/abs/2603.14805">Knowledge Activation</a> 這篇講得最完整。作者是 Yahoo 的工程師，框架實際部署在 Yahoo 內部，論文還附了 67 位工程師的使用調查，不是純理論。它把知識進入 skill 的過程拆成三個階段: 「編碼」(codification) 把散落在專家腦中、操作手冊、事故報告裡的知識轉成明確結構; 「壓縮」(compression) 把它重組成精簡的「原子知識單元」(AKU); 「注入」(injection) 則在執行時只把當下需要的單元載入上下文。</p>

<p>其中壓縮階段是精華。論文強調<strong>壓縮不是摘要，而是有原則的重組</strong>，關鍵做法有四個:</p>

<ul>
  <li><strong>刪掉修辭性內容</strong>: 鋪陳背景的段落、截圖、對其他文件的交叉引用，這些是寫給人看的，對 agent 只是負擔。留下來的是: 意圖、步驟、該用的工具、限制條件。</li>
  <li><strong>可執行的內容放最前面</strong>: 描述資訊內嵌在條目裡，並且順著模型注意力的特性安排資訊位置，重要的東西不要埋在中段。</li>
  <li><strong>一個單元只做一件事</strong>: 每個知識單元只涵蓋一件連貫的動作，跨主題的內容不塞進同一份文件，而是宣告成「接下來去哪」的連結，讓 agent 執行時自己走訪相關單元。這其實就是 skill 漸進式載入的精神，放大到整個知識庫來做。</li>
  <li><strong>能驗證的規範寫成程式</strong>: 組織規範不用散文描述，直接寫成可執行的驗證腳本 (validator)，讓 agent 的產出能被自動檢查。又一次呼應「能寫成程式碼的就寫成程式碼」。</li>
</ul>

<p>效果用論文自己的例子來看: 一份 2,000 token 的敘事式部署手冊，重組之後只剩約 300 token，任務完成能力不變，知識密度提升 6 到 7 倍。論文還引用「上下文品質劣化」(context rot) 的研究提醒: 塞進上下文的冗餘內容不只是浪費，還會實際拉低 agent 的推理品質。所以壓縮不是省成本的最佳化，是正確性的必要條件。</p>

<h2 id="怎麼選-一張對照表">怎麼選: 一張對照表</h2>

<table>
  <thead>
    <tr>
      <th>路線</th>
      <th>前提</th>
      <th>專家成本</th>
      <th>拿得到</th>
      <th>拿不到</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A. 人工訪談</td>
      <td>專家在、有好的訪談者</td>
      <td>高（每場 60-90 分鐘）</td>
      <td>為什麼、判斷邊界</td>
      <td>專家意識不到的習慣</td>
    </tr>
    <tr>
      <td>B. AI 訪談</td>
      <td>專家願意配合</td>
      <td>中</td>
      <td>結構化規則、可規模化</td>
      <td>深度追問的細節</td>
    </tr>
    <tr>
      <td>C. 數位足跡蒸餾</td>
      <td>有數位足跡</td>
      <td>零</td>
      <td>實際行為模式</td>
      <td>數位足跡外的判斷、為什麼</td>
    </tr>
    <tr>
      <td>D. 示範錄製</td>
      <td>工作在螢幕上進行</td>
      <td>低（做一遍）</td>
      <td>操作步驟、順序</td>
      <td>為什麼、替代方案</td>
    </tr>
    <tr>
      <td>E. 教材蒸餾</td>
      <td>有現成教材</td>
      <td>零</td>
      <td>結構化的方法</td>
      <td>失敗處理、實戰細節</td>
    </tr>
    <tr>
      <td>F. 互動結晶</td>
      <td>專家持續使用 agent</td>
      <td>低（分攤在日常）</td>
      <td>演化中的真實判斷</td>
      <td>冷啟動期的能力</td>
    </tr>
  </tbody>
</table>

<p>實務上的組合大概是: 有數位足跡先蒸餾 (C)、有教材先打底 (E)、有螢幕操作先錄示範 (D)，這三條便宜的先做，產出第一版草稿。然後把最貴的專家訪談時間 (A/B) 留給草稿裡的缺口: 為什麼、邊界條件、失敗處理。上線之後切換到互動結晶 (F) 持續演化，用 eval 的失敗案例決定下一輪要補訪什麼。</p>

<p>換句話說，六條路線不是互斥的選項，而是同一個流程的不同階段: 便宜的路線負責涵蓋面，昂貴的路線負責深度，持續的路線負責不過時。真正的關鍵決策只有開頭那一個問題: <strong>你要萃取的那位專家，他的知識現在存在哪裡?</strong> 而起點可以很輕: Siemens 就是兩位專家、各 90 分鐘、開放式問題加情境追問，先做出第一版，讓失敗案例告訴你接下來要補什麼。老師傅的知識不會自己變成文件，但把它變成組織資產的成本，已經比以前低非常多了。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Skill" /><category term="Knowledge Base" /><summary type="html"><![CDATA[組織裡最有價值的知識，往往集中在少數資深專家身上: 文件寫不全、標準作業流程涵蓋不了，人一離開，知識就跟著離開。這是知識管理領域的老問題，但現在多了一個新解法: 把專家知識萃取出來，做成 AI Agent 可以載入的 skill，讓非專家也能做出專家等級的產出。最近 GitHub 上「員工蒸餾」專案引發的討論（後面 C 路線會講），也讓更多人開始關注這件事。]]></summary></entry><entry><title type="html">為什麼 context window 會卡在 1M 很多年? SemiAnalysis 談 40 年一遇的記憶體短缺</title><link href="https://blog.aihao.tw/2026/07/06/semianalysis-hbm-context-window/" rel="alternate" type="text/html" title="為什麼 context window 會卡在 1M 很多年? SemiAnalysis 談 40 年一遇的記憶體短缺" /><published>2026-07-06T00:00:00+00:00</published><updated>2026-07-06T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/06/semianalysis-hbm-context-window</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/06/semianalysis-hbm-context-window/"><![CDATA[<p>Latent Space 在 2026/2/24 上了一集 <a href="https://www.latent.space/p/valuemule">Claude Code for Finance + The Global Memory Shortage</a> (<a href="https://www.youtube.com/watch?v=x9rWFiIubmc">YouTube 完整版</a>、<a href="https://x.com/latentspacepod/status/2026420225562804288">X 公告</a>)，來賓是 SemiAnalysis 的 Doug O’Laughlin。上半場聊他怎麼把 Claude Code 當成金融研究的主力工具，那篇「GitHub 上 4% 的公開 commits 出自 Claude Code」的 <a href="https://newsletter.semianalysis.com/p/claude-code-is-the-inflection-point">Claude Code is the Inflection Point</a> 就是 SemiAnalysis 發的，這部分本文不展開。小編覺得這集真正有意思的是下半場的全球記憶體短缺，因為它帶出一個跟每位 AI 工程師都有關的結論: LLM 的 context window 短期內不會再大幅成長，卡住它的不是演算法，是 HBM 記憶體。這篇就專心講這個，以及它為什麼讓 context engineering 變得更重要。</p>

<p>Doug 早年是匿名財經帳號「ValueMule」，2018 年因為研究 ASML 轉進半導體，後來創辦 Fabricated Knowledge，再併入 Dylan Patel 的 SemiAnalysis，是少數同時懂晶片供應鏈和 AI 應用的分析師。</p>

<h2 id="40-年一遇的記憶體短缺有四個成因">40 年一遇的記憶體短缺，有四個成因</h2>

<p>下半場對應 SemiAnalysis 二月初的 <a href="https://newsletter.semianalysis.com/p/memory-mania-how-a-once-in-four-decades">Memory Mania</a> 這篇。他們從 2024 年底就開始提醒記憶體要出事，而這次短缺是 40 年一遇的等級，因為四件事同時發生:</p>

<p><strong>1. DRAM 微縮停滯，成本不再自動下降</strong></p>

<p>過去五十年，記憶體有一個大家習以為常的規律: 製程每進步一代，同一片晶圓就能切出更多位元，每 GB 的成本就自動下降。所以就算需求大增，只要等技術往前走，價格長期還是一路往下。這個規律在最近十年失效了: 全盛時期 DRAM 密度每十年成長約 100 倍，過去十年只剩約 2 倍，技術進步帶來的降價力量幾乎歸零。下面這張圖一看就懂:</p>

<p><img src="/assets/images/semianalysis-dram-cost.jpg" alt="" /></p>

<p><em>DRAM 每 GB 成本，1973 到 2024 年，對數座標。跌了五十年的曲線，最近十年明顯放平。來源: <a href="https://newsletter.semianalysis.com/p/memory-mania-how-a-once-in-four-decades">SemiAnalysis Memory Mania</a></em></p>

<p>縮不下去的原因，簡單說是 DRAM 的記憶單元 (cell) 已經小到接近物理極限: 每個記憶單元靠一顆微小電容存電荷來記住 0 或 1，縮到現在，存的電荷只剩幾萬顆電子 (Memory Mania 文章的比較: 一粒灰塵帶的靜電，大約是它的一萬倍)，訊號微弱到再縮就讀不到了。</p>

<p>所以這次漲價跟以往最不一樣的地方是: 沒有「等製程進步就會降價」這條路可以指望了。DRAM 價格從此純看供需，而供需正好在最糟的時刻失衡。</p>

<p><strong>2. 上一輪不景氣是史上最慘，沒有人擴產</strong></p>

<p>記憶體是漲跌循環非常劇烈的產業: 缺貨時大賺，供過於求時大賠。Doug 說上一輪下跌要回到 1996 年才找得到可比的慘況: 記憶體廠自由現金流轉負、資本支出全停，無塵室和設備這類要提前兩三年準備的投資通通沒做。90 年代中期全球有 20 多家 DRAM 廠，一輪一輪淘汰下來，現在只剩 3 到 4 家有實質產能的供應商，誰都不想在低點擴產。</p>

<p><strong>3. HBM 的交換比: 每做 1 個位元，就少掉 3~4 個位元的 DRAM</strong></p>

<p>HBM 就是 AI 加速器旁邊那幾疊高頻寬記憶體，做法是把多層 DRAM 晶粒垂直堆疊封裝起來。它的晶粒比較大、堆疊的每一步都有良率損耗，所以同樣的晶圓產能拿去做 HBM，產出的位元數只有一般 DRAM 的幾分之一。Doug 說:</p>

<blockquote>
  <p>HBM 的每一個位元，本質上都是 DRAM 產能的 4 倍乘數。</p>
</blockquote>

<p>AI 加速器的需求全部集中在 HBM，等於用 3:1 到 4:1 的比率，從本來就沒擴產的 DRAM 產能裡挪走。Doug 自己的比喻: 有人發明了一種更高級的航空燃油，唯一的製造方法是把你手上的普通燃油大量濃縮提煉，「這種情況下只要出現任何需求，就是立刻短缺」。</p>

<p><strong>4. KV cache 卸載: 連一般的伺服器記憶體也被 AI 買走</strong></p>

<p>記憶體市場大致分三層: 最高階是 GPU 上的 HBM，中間是伺服器用的一般 DRAM (DDR5)，再往下是手機和 PC 用的消費性記憶體。前面講的是 AI 把最高階的 HBM 搶光，但需求沒有停在那裡: 推論系統為了節省昂貴的 HBM 空間，會把暫時用不到的 KV cache 先搬到伺服器的一般 DRAM 存放 (稱為卸載，offload)，要用時再搬回來。於是中間這層的 DDR5 也被 AI 大量買走，Doug 的說法是「中間這層也蒸發了」。推理模型和 agent 工作流把 context 越用越長，KV cache 就越大，正是這波需求的直接來源，後面講 context window 時會再展開。</p>

<p>消費端也躲不掉。節目上 Doug 直接建議 swyx「現在就去買 iPhone」，後來看是對的: Dylan Patel 三月在 <a href="https://www.dwarkesh.com/p/dylan-patel">Dwarkesh Podcast</a> 給的數字是 DRAM 價格已經漲了三倍，iPhone 估計會因此貴 250 美元，全球智慧手機出貨量可能從 11 億支掉到 5、6 億支。Doug 說這是史上第一次，記憶體貴到變成「要選擇誰能拿到」: 低階手機、遊戲顯卡會有一段時間直接從市場上消失。連已經被判定失敗的記憶體池技術 CXL (SemiAnalysis 2023 年就寫過 <a href="https://newsletter.semianalysis.com/p/cxl-is-dead-in-the-ai-era">CXL Is Dead In The AI Era</a>) 都要復活了: 把每一條找得到的 DDR4 舊記憶體接上機架共享使用，短缺嚴重到連舊料都得拿出來用。</p>

<h2 id="從-2-月到-7-月-方向沒變數字更陡">從 2 月到 7 月: 方向沒變，數字更陡</h2>

<p>訪談是 2 月底錄的，Doug 當時的預測是 DRAM 價格「可能再漲 100%」。五個月過去，SemiAnalysis 後續發表的數字只有更陡: <a href="https://x.com/SemiAnalysis_/status/2039870546582630470">四月的估算</a>是 DRAM 價格 2026 全年會再漲超過一倍，HBM 供不應求會持續到 2027，記憶體佔大型雲端業者 (hyperscaler) 資本支出的比例，也從前兩年的約 8% 升到 2026 年的 30%。<a href="https://247wallst.com/investing/2026/06/29/tech-experts-warn-memory-shortage-crisis-wont-ease-until-2028-and-ram-makers-have-no-incentive-to-fix-it/">美光 (Micron) 六月底的財報</a>是最直接的佐證: 毛利率 84.6%，執行長還坦言部分關鍵客戶的需求只能滿足 50% 到 2/3，完全是賣方市場。</p>

<p>對 AI 工程師來說，市場細節可以略過，記住兩個訊號就好。一是短缺已經影響晶片設計本身: 六月 Dylan Patel <a href="https://x.com/dylan522p/status/2068043256634683409">透露</a> Nvidia 把下一代 Rubin Ultra 的 HBM 堆疊從 16 層砍到 12 層，連跟供應鏈綁最深的 Nvidia 都拿不到想要的量。二是有意義的新產能要等 2027 年底到 2028 年，也就是說接下來這一兩年，模型供應商能拿到的記憶體就是這麼多了。</p>

<h2 id="nvidia-對-tpu-勝負不在晶片設計在誰拿得到-hbm">Nvidia 對 TPU: 勝負不在晶片設計，在誰拿得到 HBM</h2>

<p>節目裡有一段講加速器競爭，Doug 的論點可以整理成三步:</p>

<p><strong>第一步，現在確實是 TPU 優勢最大的一刻。</strong> Doug 估算 TPU v7 (Ironwood) 是目前總持有成本 (TCO) 最好的加速器，而且領先幅度有感。原因有兩個: 一是 TPU 這邊該成熟的都成熟了，軟體生態、網路架構都到位，還多了 Anthropic 這個真正懂得用 TPU 的外部大客戶; 二是 Nvidia 自己這一代跌了一跤，GB200 出貨延遲又不穩定，Doug 直說如果 GB200 準時出貨、穩定運作，會全面壓過 v7。所以才有「在完全不缺貨的世界裡，每家實驗室都會盡可能多拿 TPU v7」這句話，連 OpenAI 都不例外。</p>

<p><strong>第二步，但在什麼都缺的年代，比的不是晶片設計，是供應鏈。</strong> TPU 想多做也做不出來，台積電產能是最大限制; 更關鍵的是記憶體: 短缺時 HBM 實質上是分配制，三家供應商的產能就這麼多，誰能簽下最好、最多的供應，看的是長約和多年的關係經營。這正是 Nvidia 最強的地方，Doug 說「Nvidia 毫無疑問是最強的，他們掌握整條供應鏈」。黃仁勳親自在亞洲和三星會長、SK 海力士高層喝酒應酬，重點不是喝酒本身，而是記憶體供應重要到 Nvidia 的執行長親自出面經營關係、鎖定最好的產能; 節目裡的反問是: 你覺得 Sergey Brin 有在做同樣的事嗎? 沒有。</p>

<p><strong>第三步，所以 Rubin 世代會反轉，而且贏的正是 context window 最需要的東西。</strong> Nvidia 下一代 Rubin 平台配 HBM4，Doug 判斷「HBM 和記憶體容量的優勢會倒向 Rubin 這邊」，而推論時代更大更快的記憶體，直接決定「context window 能做多大」，swyx 接話: 「決定一切」。用 Doug 的說法，Google 想搶市佔「這扇門只有現在開著」，等 Rubin 出來就關上了。</p>

<p>簡單說: 晶片設計的差距可以追，但記憶體供應的差距，在 40 年一遇的短缺裡就是勝負本身，而這件事 Nvidia 布局最深。後續進展一則: SemiAnalysis 四月的 <a href="https://newsletter.semianalysis.com/p/isscc-2026-nvidia-and-broadcom-cpo">ISSCC 2026 整理</a> 提到三星的 HBM4 進步明顯，效能已能滿足 Rubin 的要求，SK 海力士的獨大局面可能鬆動。三家都能量產 HBM4 對供給是好消息，但別忘了上面的交換比: HBM 做得越多，被換掉的一般 DRAM 產能就越多。</p>

<h2 id="context-window-卡在-1m而且是物理限制">Context window 卡在 1M，而且是物理限制</h2>

<p>這集討論度最高的就是這段。swyx 先丟出觀察:</p>

<blockquote>
  <p>swyx: 所有人，包括 Sam Altman，都在預測更長的 context window。但我們實際上已經卡在 1M 兩年了。</p>

  <p>Doug: 我最近一直在想這件事。它不會變成 100M，也不會變成 1T。就是這樣了，接下來五年、十年大概就是這樣。</p>
</blockquote>

<blockquote>
  <p>編按: Gemini 1.5 Pro 在 2024 年 2 月發表 1M context window，到這集錄音的 2026 年 2 月正好兩年，主流模型的上限還停在同一個量級。</p>
</blockquote>

<p>Doug 給了一個很好的定位: 長 context 本質上就是「往上堆記憶體」的能力。為什麼說這是物理限制、不是演算法問題? 訪談裡點到為止，小編展開算給大家看。核心是 KV cache: Transformer 模型在生成時，前面每個 token 的中間計算結果 (注意力的 Key 和 Value) 都要留在 GPU 的 HBM 裡隨時取用，這份暫存就是 KV cache，它隨 context 長度線性成長。以 Llama 3 70B 這種等級的模型粗估 (80 層、8 個 KV head、head_dim 128、FP16)，每個 token 的 KV cache 約 0.33 MB:</p>

<div style="display:flex;flex-wrap:wrap;gap:12px;margin:8px 0">
  <div style="flex:1 1 180px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px">
    <div style="font-size:.85em;color:var(--text-secondary)">128K tokens 的 KV cache</div>
    <div style="font-size:1.5em;font-weight:700;color:var(--text)">約 43 GB</div>
    <div style="font-size:.85em;color:#1a7f37">一張 H200 (141GB HBM) 放得下</div>
  </div>
  <div style="flex:1 1 180px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px">
    <div style="font-size:.85em;color:var(--text-secondary)">1M tokens 的 KV cache</div>
    <div style="font-size:1.5em;font-weight:700;color:var(--text)">約 330 GB</div>
    <div style="font-size:.85em;color:#9a6700">超過單卡容量，要 3 張 H200 分攤</div>
  </div>
  <div style="flex:1 1 180px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px">
    <div style="font-size:.85em;color:var(--text-secondary)">100M tokens 的 KV cache</div>
    <div style="font-size:1.5em;font-weight:700;color:var(--text)">約 32 TB</div>
    <div style="font-size:.85em;color:#cf222e">超過 230 張 H200，只為了存住對話</div>
  </div>
</div>

<div style="font-size:.8em;color:var(--text-secondary);margin:-4px 0 8px">✏️ 小編製圖與估算，只算 KV cache，模型權重還要另外放。不同架構差異很大，但量級如此。</div>

<p>也就是說 100M context 光是 KV cache 就是 32TB 等級，需要兩百多張 GPU 只為了「記住」一段對話，經濟上完全不成立。</p>

<p>而且問題不只容量，還有頻寬: 生成回應時，每產生一個新 token，都要把整份 KV cache 從 HBM 完整讀一遍。context 越長，每個 token 的生成就越慢、越貴。這就是為什麼各家對長 context 的定價更高，也是為什麼推論部署現在流行把「讀入處理 prompt 的預填 (prefill) 階段」和「逐字生成的解碼 (decode) 階段」拆開，放在不同的機器上跑。</p>

<p>演算法能省的都是常數倍: GQA、MLA、KV cache 量化，這些技術都是在壓縮每個 token 佔的空間 (DeepSeek 的 MLA 壓得特別多)，但改變不了「隨 context 線性成長」這個本質。</p>

<p>swyx 接著問了一個很實際的問題: 從論文和他自己的使用經驗來看，模型在超長 context 下的表現會下降，並不會真的把 context 全部用上，那 100M 有意義嗎? Doug 的回答:</p>

<blockquote>
  <p>100M 的版本今天就存在，只是各有各的缺陷: 它們並沒有真的對每個 token 做完整的注意力計算。你可以用狀態空間模型 (SSM) 甚至 LSTM 去處理一億個 token，但你並沒有對這一億個 token 付出完整的注意力。</p>
</blockquote>

<p>他補充說，模型有效運用 context 的能力這幾年進步很多、還會再進步，但「我們永遠不會用滿全部的 context」。這句話值得記住: 就算 context window 不變，「塞得下」和「用得好」也是兩回事。</p>

<p>swyx 給了總結: 「你代表的是我們軟體這端永遠無法克服的物理限制。」Doug 接著補上: 「我們連翻倍都做不到，更別說 10 倍。」Dylan Patel 三月在 Dwarkesh 那集則從需求端印證同一件事: 這波記憶體需求大增的直接原因，就是推理模型把 context 越拉越長，context 越長 KV cache 就越大，你就是需要更多記憶體。</p>

<h2 id="context-rationing-context-會變成配給品">Context rationing: context 會變成配給品</h2>

<p>既然供給短期不會變出來，接下來就是分配問題。swyx 在節目上當場想出「context rationing (context 配給)」這個詞，Doug 順著推演計價的未來: 免費版的 context window 可能縮到 1,000 tokens 上下，然後對 1M 收 100 倍的價錢，「1M context window 就像豪宅」(他的原話)。swyx 接著開玩笑說以後會有 context 配給券: 「你今天的 context 額度用完了」。兩人講得半開玩笑，但方向很認真: 按 context window 分級計價，明年出現都不奇怪。</p>

<p>至於解法，這集加上後續幾場訪談合起來看有三種，每一種都不快:</p>

<ol>
  <li><strong>演算法</strong>: 遞迴語言模型 (recursive language models，遞迴地重複使用同一份 context window，swyx 說他最近很關注)、稀疏注意力 (sparse attention)、線性注意力 (linear attention)、狀態空間模型。共同的取捨是都不對每個 token 做完整的注意力計算，省記憶體的代價是模型對 context 前段的內容記得比較模糊: 當答案需要用到幾十萬 token 之前提過的細節時，可能漏掉或記錯。</li>
  <li><strong>硬體</strong>: Dylan Patel 七月初在 <a href="https://sequoiacap.com/podcast/dylan-patel-of-semianalysis-why-hardware-software-co-design-is-ais-real-100x/">Sequoia 的訪談</a> 講了技術面: DRAM 的記憶單元發明至今 40 年、NAND 也 25 年沒有重大突破，過去五年的進步只是把 HBM 堆更高、跑更快。接下來幾年值得期待的是把記憶體直接堆疊在運算晶片上 (而不是像 HBM 一樣分開封裝再連線)，頻寬會大幅成長。但這是以年為單位的計畫，救不了這兩年。</li>
  <li><strong>特化晶片</strong>: <a href="https://taalas.com/">Taalas</a> 這類把模型權重直接做進晶片、推論不需要外部記憶體的方案。Doug 認為有一席之地，理由是正式上線的模型其實都蒸餾得很小，搭配預填和解碼分離的趨勢，推論市場會分化出「固定模型、超低單價」的一塊。但他也提醒要保守看待: 過去十年做 AI 晶片、想挑戰 Nvidia 的新創公司很多，幾乎都以失敗收場，連算是活下來的 Cerebras 和 Groq，市場都還在問他們到底能做成什麼生意。Taalas 得先證明自己是例外。</li>
</ol>

<h2 id="小結-context-的價值在密度不在長度">小結: context 的價值在密度，不在長度</h2>

<p>小編覺得這集最值得帶走的想法是: 「模型會不會更聰明」和「context window 會不會更大」，這兩件事要分開來想。前者看演算法和訓練，還在快速進步; 後者看 HBM 和 DRAM 供應鏈，而供應鏈已經給出答案: 未來幾年就是 1M 這個量級，而且會越來越貴。從 2 月的訪談到 7 月的現況，這個判斷只有被加強，沒有被推翻。</p>

<p>回頭把整集的線索收攏，會發現它們指向同一個結論:</p>

<ul>
  <li><strong>物理上</strong>，KV cache 隨 context 線性成長，記憶體供給短期不會增加，1M 就是這幾年的上限;</li>
  <li><strong>經濟上</strong>，context 越長，每個 token 的生成越慢、越貴，計價只會往 context rationing 的方向走，塞進 context 的每個 token 都是錢;</li>
  <li><strong>品質上</strong>，就算塞得下，模型也不會把每個 token 都用上，Doug 自己都說「我們永遠不會用滿全部的 context」。</li>
</ul>

<p>三條線加起來: context 註定是要精打細算的稀缺資源，而且塞好塞滿本來就不是好策略。與其等一個更大的 context window (等不到)，不如把手上這 1M 的資訊密度做好。這正是 <a href="https://ihower.tw/blog/12817-context-engineering">context engineering</a> 在處理的問題，ihower 的文章中提到做法有四類方向，每一類都正好在回應這集講的限制:</p>

<ul>
  <li><strong>寫入 context</strong>: 把不需要隨時放在 context 裡的資訊 (使用者偏好、過往結論) 存到 context window 之外，形成長期記憶，要用再拿回來;</li>
  <li><strong>選擇 context</strong>: 靠 RAG 檢索、記憶提取、動態挑選工具，只把當下任務需要的資訊拉進 context;</li>
  <li><strong>壓縮 context</strong>: 超過門檻就總結、修剪，只留執行任務所需的 tokens，例如 Claude Code 的 auto-compact;</li>
  <li><strong>隔離 context</strong>: 把支線任務拆給 sub-agent 用它自己的 context 執行，回到主 context 的只有結果。</li>
</ul>

<p>用這集的視角看，這四件事的共同效果就是用更少的 token 達成同樣的任務品質: 直接省下 KV cache 的記憶體、省下越來越貴的長 context 費用，也避開塞太滿的品質下降。context engineering 會越來越重要，不是因為它時髦，而是因為物理和供應鏈都說了: 短期內沒有人會來救你，能救你的只有把 context 用得更好。</p>]]></content><author><name>ihower</name></author><category term="Industry" /><category term="LLM" /><category term="Context Engineering" /><summary type="html"><![CDATA[Latent Space 在 2026/2/24 上了一集 Claude Code for Finance + The Global Memory Shortage (YouTube 完整版、X 公告)，來賓是 SemiAnalysis 的 Doug O’Laughlin。上半場聊他怎麼把 Claude Code 當成金融研究的主力工具，那篇「GitHub 上 4% 的公開 commits 出自 Claude Code」的 Claude Code is the Inflection Point 就是 SemiAnalysis 發的，這部分本文不展開。小編覺得這集真正有意思的是下半場的全球記憶體短缺，因為它帶出一個跟每位 AI 工程師都有關的結論: LLM 的 context window 短期內不會再大幅成長，卡住它的不是演算法，是 HBM 記憶體。這篇就專心講這個，以及它為什麼讓 context engineering 變得更重要。]]></summary></entry><entry><title type="html">Claude Fable 5 的 Prompting 要點: 該刪的比該加的多</title><link href="https://blog.aihao.tw/2026/07/04/claude-fable-5-prompting/" rel="alternate" type="text/html" title="Claude Fable 5 的 Prompting 要點: 該刪的比該加的多" /><published>2026-07-04T00:00:00+00:00</published><updated>2026-07-04T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/04/claude-fable-5-prompting</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/04/claude-fable-5-prompting/"><![CDATA[<p>Anthropic 上個月發布了 <a href="https://www.anthropic.com/news/claude-fable-5-mythos-5">Claude Fable 5</a>，第一個 Mythos 級模型，定位在 Opus 之上。比較少人注意到的是，官方同時出了一份專屬的 <a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">Prompting Claude Fable 5</a> 指南，整份文件講的就是 Fable 5 和 Opus 4.8 的行為差異，以及你的 prompt 該跟著改什麼。</p>

<p>小編消化了官方文件、Anthropic Claude Code 團隊成員 Thariq 的實戰長文，加上發布後這幾週社群摸索出來的共識，幫大家挑出「跟之前模型不一樣」的部分。至於 XML 標籤、few-shot 範例這些通用技巧，舊模型怎麼用、現在還是怎麼用，這篇就不重複了。</p>

<div style="margin:16px 0;padding:14px 18px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:6px">📌 另外要先說明，這些建議的適用對象分兩種: 在 Claude app 或 Claude Code 裡直接下 prompt 的<strong>使用者</strong>，以及用 API 設計 agent、寫 system prompt 和 harness 的<strong>開發者</strong>。兩邊用得上的東西不一樣，所以每節標題下方和每個 prompt 範本前面都加了標籤: <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span> 代表對話中可以直接使用，<span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span> 代表設計 agent 時放進 system prompt。兩個標籤都有的，代表兩種情境都適用; 自己維護 CLAUDE.md 和 skills 的 Claude Code 使用者介於兩者之間，通常兩邊的範本都用得上。</div>

<p>先快速交代規格，方便理解後面的建議:</p>

<ul>
  <li>定價: 每百萬 token 輸入 $10、輸出 $50，約是 Opus 4.8 的兩倍</li>
  <li>context window 有 1M token。難的任務單一請求可以跑數分鐘，自主執行可以連續數小時</li>
  <li>thinking 永遠開啟、只有 adaptive 模式，推理深度由 <code class="language-plaintext highlighter-rouge">effort</code> 參數控制(low/medium/high/xhigh/max)</li>
  <li>內建安全分類器: 網安和生物相關請求會被拒絕，可設定自動 fallback 到 Opus 4.8。同一個底層模型、拿掉分類器的版本叫 Claude Mythos 5，只開放給核准的組織</li>
</ul>

<h2 id="1-為舊模型寫的細節指令現在會扣分">1. 為舊模型寫的細節指令，現在會扣分</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span> <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span></div>

<p>整份指南最重要的一句話，<a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">官方原文</a>直翻: 「為先前模型開發的 skills，對 Claude Fable 5 來說往往過於規範(too prescriptive)，可能降低輸出品質。」</p>

<p>過去官方教大家把 Claude 當成聰明但不熟悉你環境的新員工，每個步驟都要鉅細靡遺地交代。Fable 5 的指南方向不同了: 你負責講清楚目標、理由、邊界和驗收標準，步驟讓它自己規劃。那些當年為了補救舊模型規劃能力而寫的步驟清單，現在反而變成一種限制: 模型明知不適合，還是會照著做。官方還提到一個相關特性: Fable 5 很會在任務中根據學到的東西即時更新 skill 內容，寫得太死的 skill 反而妨礙這種自我修正。</p>

<p>Claude Code 的作者 Boris Cherny <a href="https://x.com/bcherny/status/2064431111154053187">發布當天的心得</a>(一萬讚)正好是這件事的註腳。他說第一次意識到 Fable 不一樣，是請它 debug 的時候: 「它是我用過第一個如此有條理又精確的模型: 先量測、加 log，驗證真的修好了才宣告完成。Claude Code 的 prompt 裡沒有任何指令教模型這麼做，這就是它內建的行為。」換句話說，很多你以前要寫進 prompt 的要求，現在已經是預設值。</p>

<p>指令跟隨能力變強，還帶來一個紅利: 一句原則就能取代逐條列舉。官方自己的範例是，與其把每一種囉嗦的輸出模式列出來一一禁止，不如給一小段原則:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Lead with the outcome. Your first sentence after finishing should answer “what happened” or “what did you find”: the thing the user would ask for if they said “just give me the TLDR.” Supporting detail and reasoning come after.</p>

  <p>中譯: 用成果來引導。你完成後的第一句話，就要回答「發生了什麼」或「你發現了什麼」: 也就是使用者說「直接給我重點摘要」時想要的那句話。支撐的細節和推理放在後面。</p>
</blockquote>

<p>反過來說，以前那種「CRITICAL: 你必須⋯⋯」「ALWAYS/NEVER」的加重語氣，現在會造成過度觸發(overtrigger): 模型變得太聽話，你越喊，它做得越過頭。官方建議改回平常語氣，大寫強調只留給真正無例外的規則。</p>

<p>實務上要從哪裡開始刪? <a href="https://www.productcompass.pm/p/claude-fable-5-guide">Pawel Huryn 的指南</a>提供了一個好做法: 先讓 Fable 5 稽核你既有的 prompt 和 skills，請它找出互相矛盾的指令，以及那些為了舊模型缺陷而存在的過時規則，列出建議刪除的清單和理由，最後由你決定刪什麼。</p>

<blockquote>
  <p>編按: 「為舊模型調校的指令會拖累新模型」，跟之前駕馭工程系列講的 <a href="https://blog.aihao.tw/2026/06/26/harness-engineering-8-model-harness-fit/">Model-Harness Fit</a> 是同一個現象: prompt 和 harness 是針對特定模型的能力缺口設計的，模型換代之後，原本的補強就變成了限制。</p>
</blockquote>

<h2 id="2-think-step-by-step不只沒用還可能觸發-refusal">2. 「Think step by step」不只沒用，還可能觸發 refusal</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span> <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span></div>

<p>Fable 5 的 thinking 永遠開啟，adaptive thinking 是唯一模式: 想關掉會拿到 400 錯誤，<code class="language-plaintext highlighter-rouge">budget_tokens</code> 也不再支援，思考深度一律由 <code class="language-plaintext highlighter-rouge">effort</code> 控制。延續 4.6 世代的變更，assistant prefill(預填模型回應開頭)也不支援了，要控制輸出格式改用 structured outputs。而且不管怎麼設定，原始的思考過程(raw chain of thought)都不會回傳，只能拿到摘要版的 thinking blocks(這些 API 行為詳見<a href="https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5">官方模型介紹</a>)。</p>

<p>所以「請一步一步思考」這類指令已經沒有意義，模型自己會決定何時思考、想多深。更要注意的是反效果: 要求模型「解釋你的內部推理」「展示你的思考過程」，可能觸發名為 <code class="language-plaintext highlighter-rouge">reasoning_extraction</code> 的拒絕分類器，整個請求會 fallback 到 Opus 4.8。官方明確建議，遷移時要逐一檢查舊的 prompt 和 skills，把這類 show-your-thinking 指令拿掉。麻煩的是，這種問題不會以錯誤的形式出現，而是表現成「fallback 比例莫名升高、輸出品質突然變差」，不容易察覺。</p>

<p>如果應用真的需要看到推理過程，正確做法有兩種: 讀取 <code class="language-plaintext highlighter-rouge">thinking.display: "summarized"</code> 回傳的摘要，或是給 agent 一個 <code class="language-plaintext highlighter-rouge">send_to_user</code> 工具，讓它在長任務中途，把必須原文呈現的訊息直接送到使用者面前(官方提醒，光定義這個工具還不夠，要在 system prompt 裡明確提示什麼時候用，Fable 5 才會主動去用它)。</p>

<p>當年 prompt engineering 就是靠「think step by step」這類技巧起家的。幾年後的今天，同一個寫法變成會觸發安全分類器，這大概是這次遷移最有時代感的一條。</p>

<h2 id="3-哪些主題會被拒絕-refusal-分類器與它的誤觸">3. 哪些主題會被拒絕? refusal 分類器與它的誤觸</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span> <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span></div>

<p>上一節講的 <code class="language-plaintext highlighter-rouge">reasoning_extraction</code> 只是其中一個分類器。Fable 5 是 Mythos 級模型，能力強到 Anthropic 認為某些領域一旦被濫用風險很高，於是在模型外面加了一層安全分類器來把關。一旦請求被判定為高風險，API 會回傳 <code class="language-plaintext highlighter-rouge">stop_reason: "refusal"</code>，並可以設定自動 fallback 到 Opus 4.8 來接手。要注意這個 refusal 是 HTTP 200 的正常回應、不是錯誤，所以程式要主動去檢查，不然它不會進到你的錯誤處理流程。</p>

<p><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">官方文件</a>明列三類封鎖目標:</p>

<ul>
  <li><strong>進攻型網路安全</strong>(offensive cybersecurity): 撰寫 exploit、惡意程式、攻擊工具這類「拿來攻破系統」的內容。相對的「防禦型」(defensive)工作，例如加固、偵測、修補漏洞，不在封鎖範圍。分界就在於這份產出是用來攻擊，還是用來防守</li>
  <li><strong>生物與生命科學</strong>: 實驗方法、分子機制這類內容</li>
  <li><strong>思考萃取</strong>(reasoning extraction): 想把模型的摘要思考掏出來，也就是上一節講的那種指令</li>
</ul>

<p>除了這三類，Anthropic 在別的場合也提過<strong>化學</strong>和<strong>蒸餾</strong>(distillation，用大模型的輸出去訓練小模型，Anthropic 曾指控 DeepSeek 大規模這樣做)同樣在防範之列。官方講得很白: 這層防護「刻意保守」，寧可錯殺不可放過，所以正當的網安研究和有益的生命科學工作也常被掃到。他們自己給的數據是超過 95% 的 session 完全不會觸發 fallback。</p>

<p>麻煩的是剩下那不到 5%，而且這條界線一直在變。發布首週誤觸最誇張的是生物領域: <a href="https://www.theverge.com/ai-artificial-intelligence/947973/fable-wont-answer-basic-biology-questions">The Verge 當時的實測</a>發現 Fable 5 連高中程度的生物題都不肯答(「介紹一下細胞膜」「mRNA 疫苗怎麼運作」全被擋)，Anthropic 也向 The Verge 證實這是刻意設定得過度保守; 做生物統計、生物資訊的研究者受害尤其無辜，<a href="https://github.com/anthropics/claude-code/issues/66674">Claude Code 的一個 GitHub issue</a> 記錄到，光是 prompt 出現「spatial transcriptomics」(空間轉錄體學)或「組學」這種純學術名詞，即使整段都在講統計、沒碰任何實驗，也會被 Usage Policy 擋下(回報者還發現中文的觸發門檻更低)。官方說會持續調整、降低誤觸率，但實際邊界怎麼變、放寬了哪些，並沒有公開的對照，只能自己實測。</p>

<p>網安這條則有明確的收緊紀錄。7/1 Anthropic <a href="https://x.com/claudeai/status/2072402638247968855">公告</a>在和美國政府談過後更新了網安防護，坦言「短期內新防護會比先前多擋掉一小部分無害的請求」，正在接下來幾週微調。</p>

<p>除了蒸餾，官方對於前研模型研究(frontier LLM development)也會封鎖，而這條發布時鬧出的風波最大。它跟網安、生物不一樣: 一開始是「隱形」的，不像其他類別會明白 fallback 到 Opus，而是<strong>默默改動或降級回應、不通知使用者</strong>。做模型評測和 AI 基礎設施的研究者最先發現不對: 答案被動了手腳卻毫無標示，等於整份 eval 的結果都不可信。挨批之後 Anthropic <a href="https://x.com/ClaudeDevs/status/2064949876463645026">公開道歉</a>並在 6/11 改成可見的 fallback，公告原文承認「我們做了錯誤的取捨，很抱歉沒有拿捏好平衡」。據 Anthropic 自己的說法，這條分類器只在約 0.05% 的任務上觸發，主要打到的是前研規模的 LLM 資料管線、以及某些非標準晶片的 kernel 開發這類很窄的工作，一般人幾乎碰不到。這場風波的重點其實不在誤觸率，而在原則: 商用 API 如果會悄悄改變答案的內容，使用者至少該知道它被改過。</p>

<p>對開發者的實際意義有兩點。第一，只要工作會碰到生物、醫學、網安或前研模型研究，一定要先把 fallback 到 Opus 4.8 設定好，否則一個 refusal 就像一次沉默的失敗，你不會知道發生了什麼事。第二，如果哪天輸出品質突然變差，先懷疑是不是被 fallback 到 Opus 了，再去懷疑模型本身。官方持續在調分類器，任何「某個詞會不會被擋」的說法都有賞味期限，包括本文，實測最準。</p>

<h2 id="4-effort-變成控制思考深度的唯一手段">4. effort 變成控制思考深度的唯一手段</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span></div>

<p><code class="language-plaintext highlighter-rouge">effort</code> 參數(low/medium/high/xhigh/max 五級)本身不是新東西，Opus 4.6 世代就有了。但在 Fable 5 上它的角色變重: 因為 thinking 關不掉、<code class="language-plaintext highlighter-rouge">budget_tokens</code> 也不再支援，effort 變成控制思考深度的唯一手段，負責智能、延遲、成本三者的取捨。想靠「請再仔細檢查」「think harder」這類字眼加壓的空間也變小了，模型會自己依 effort 和題目難度決定想多深。</p>

<p><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">官方的校準建議</a>跟直覺相反: 預設用 high 就好，就算你在 Opus 4.8 上是跑 xhigh 的工作，也先從 high 開始。xhigh 留給對能力最敏感的任務，日常工作用 medium 甚至 low。原因是 Fable 5 開低 effort 的表現，常常已經超過舊模型開 xhigh，習慣性開滿只是多花錢、多等待。<a href="https://x.com/mvanhorn/status/2064887887578136666">mvanhorn 整理的發布首日社群觀察</a>也有實測佐證: YouTuber Web Dev Cody 發現 Fable 5 開 medium 就勝過 Opus 4.8 開 high 和 max，而且用的 token 更少。</p>

<p>不過高 effort 有個副作用要用 prompt 管理: 模型會蒐集超出任務所需的脈絡，或是做沒人要求的重構。官方給的範本:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span> <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Don’t add features, refactor, or introduce abstractions beyond what the task requires. A bug fix doesn’t need surrounding cleanup and a one-shot operation usually doesn’t need a helper. Don’t design for hypothetical future requirements: do the simplest thing that works well. Avoid premature abstraction and half-finished implementations. Don’t add error handling, fallbacks, or validation for scenarios that cannot happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don’t use feature flags or backwards-compatibility shims when you can just change the code.</p>

  <p>中譯: 不要加功能、不要重構、不要引入超出任務需求的抽象。修 bug 不需要順手清理周邊的程式碼，一次性的操作通常也不需要另寫輔助函式。不要為假設性的未來需求做設計: 做能把事情做好的最簡單方案。避免過早抽象和半成品的實作。不要為不可能發生的情境加錯誤處理、備援機制或驗證。信任內部程式碼和框架的保證，只在系統邊界(使用者輸入、外部 API)做驗證。能直接改程式碼，就不要用功能開關或向後相容的墊片。</p>
</blockquote>

<h2 id="5-把最難的任務給它並附上理由">5. 把最難的任務給它，並附上理由</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span></div>

<p><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">官方遷移建議</a>的第一條就是「先拿你手上最難的任務來試」(start at the top of your difficulty range): 挑一個以前不會考慮交給模型的難題，讓它先界定範圍、問清楚問題、再動手。官方觀察到，成效最好的團隊都是把 Fable 5 用在手上最難的未解問題，只拿簡單任務來測，會低估它的能力範圍。Thariq 在發布當天的<a href="https://x.com/trq212/status/2064437561930682672">推文</a>也是同一個訊息: 「Fable 是模型的一次跳級(step-change)，我希望它改變你和 Claude 工作的方式。重點一句話: 是時候更有企圖心了。」</p>

<p>Anthropic 的 Alex Albert 發布當天給新手的<a href="https://x.com/alexalbert__/status/2064467657483829441">四點建議</a>，可以當成本文到這裡的濃縮版:</p>

<ol>
  <li>給它比先前模型能處理的更大、更有企圖心的任務</li>
  <li>預設用 xhigh/high effort 拿最好的表現，互動式的工作用 medium 比較快</li>
  <li>重寫你的 skills 和 CLAUDE.md: 為舊模型寫的指令會把 Fable 拉回過時的模式，先讓它用自己的判斷</li>
  <li>從「給任務」改成「給目標」: 描述完成長什麼樣、怎麼驗證，然後讓 Fable 自己找路(Claude Code 的 <code class="language-plaintext highlighter-rouge">/loop</code> 和 <code class="language-plaintext highlighter-rouge">/goal</code> 就是為此設計的)</li>
</ol>

<p>另一個新強調的原則是「給理由，不要只給請求」。當 Fable 5 知道你為什麼要做這件事，表現會明顯更好，因為它能把任務連結到相關的資訊，而不是自己猜測你的意圖。官方範本:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>I’m working on [the larger task] for [who it’s for]. They need [what the output enables]. With that in mind: [request].</p>

  <p>中譯: 我正在為 [誰] 做 [更大的任務]，他們需要 [這個輸出要拿來做什麼]。基於這個脈絡: [你的請求]。</p>
</blockquote>

<p>社群把這個原則推到更完整的形式。發布後流傳最廣的是 <a href="https://x.com/SpikeCalls/status/2064698271151341812">@SpikeCalls 的這個 prompt</a>: 把你的事業交給它，不要只給 demo:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Here is my business: [what you sell, who buys it, your stack, your team size, your current bottleneck, last quarter’s numbers].</p>

  <p>You have my context. Don’t give me generic advice. Tell me the 8 highest-leverage jobs you would take off my plate this month, ordered by expected impact. For the top one, start now: tell me exactly what data or access you need from me.</p>

  <p>中譯: 這是我的事業: [你賣什麼、誰買、技術架構、團隊規模、目前的瓶頸、上一季的數字]。你已經有我的完整脈絡，不要給我通用建議。告訴我這個月你會從我手上接走的 8 件影響最大的工作，按預期效益排序。排第一的那件現在就開始: 告訴我你需要我提供什麼資料或權限。</p>
</blockquote>

<h2 id="6-長時程任務的防護指令">6. 長時程任務的防護指令</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者</span></div>

<p>先講清楚適用場景: 這一節整節只標 🛠️ 開發者，因為下面的範本幾乎都是放進自主 agent 的 system prompt、給「沒人在旁邊盯著、要連續跑好幾個小時」的長時程執行用的，主要是給設計自主 agent 管線的開發者。</p>

<p>Fable 5 的定位就是長時程自主工作，<a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">官方指南</a>有一半篇幅在講這件事的配套。最基本的是工程面: 單一請求會跑得比以前久很多，client timeout、streaming、進度顯示都要先調整好。harness 也建議改成非同步查看進度(例如用排程檢查)，而不是同步等到底。</p>

<p>另外，任務越模糊，Fable 5 越可能過度規劃。官方建議用這段防止它想太多:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>When you have enough information to act, act. Do not re-derive facts already established in the conversation, re-litigate a decision the user has already made, or narrate options you will not pursue in user-facing messages. If you are weighing a choice, give a recommendation, not an exhaustive survey. This does not apply to thinking blocks.</p>

  <p>中譯: 資訊足夠行動時，就行動。不要重新推導對話中已經確立的事實，不要重啟使用者已經做過的決定，也不要在給使用者的訊息裡陳述你不會採用的選項。如果你在權衡選擇，給一個建議，而不是完整的選項調查。這不適用於思考區塊。</p>
</blockquote>

<p>prompt 層面還有幾個重點，這些範本設計上是放進自主 agent 的 system prompt(其中「進度聲明要有證據」「劃清評估和動手的界線」這類，放進 Claude Code 的 CLAUDE.md 也適用):</p>

<p>🔹 <strong>進度聲明要有證據。</strong> 長時間自主執行，最怕模型回報「都做完了」但實際沒做。官方的解法是一條指令，在官方測試中幾乎完全消除了捏造的進度報告，連刻意設計來誘發假回報的任務都擋得住:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Before reporting progress, audit each claim against a tool result from this session. Only report work you can point to evidence for; if something is not yet verified, say so explicitly. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.</p>

  <p>中譯: 回報進度之前，先把每一項聲明對照本次工作階段的工具執行結果。只回報你能指出證據的工作，還沒驗證的就明說。如實回報結果: 測試失敗就照實說並附上輸出，跳過了某個步驟就說跳過了，完成且驗證過的就直接陳述，不要模稜兩可。</p>
</blockquote>

<p>🔹 <strong>劃清「評估」和「動手」的界線。</strong> Fable 5 偶爾會做沒被要求的事，例如替你起草一封沒人要的 email，或自作主張建立備份分支。官方建議明確定義界線:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>When the user is describing a problem, asking a question, or thinking out loud rather than requesting a change, the deliverable is your assessment. Report your findings and stop. Don’t apply a fix until they ask for one.</p>

  <p>中譯: 當使用者只是在描述問題、提問或思考，而不是要求修改時，你要交付的是你的評估。回報發現就停下來，對方開口才動手修。</p>
</blockquote>

<p>🔹 <strong>告訴它什麼時候該停下來問你。</strong> 長時程工作需要檢查點(checkpoint)，但不需要列舉每一種情況，一句原則就夠:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Pause for the user only when the work genuinely requires them: a destructive or irreversible action, a real scope change, or input that only they can provide. If you hit one of these, ask and end the turn, rather than ending on a promise.</p>

  <p>中譯: 只有在工作真正需要使用者時才停下來: 破壞性或不可逆的動作、真正的範圍變更、或只有他們能提供的資訊。碰到這些情況就發問並結束這一輪，而不是以一句承諾作結。</p>
</blockquote>

<p>🔹 <strong>防止長 session 後期提前結束。</strong> 官方提到一個少見但存在的行為: 在很長的 session 深處，Fable 5 偶爾會留下一句「我接下來會執行 X」就結束回合，沒有真的呼叫工具，或是明明資訊足夠還停下來問你要不要繼續。人在旁邊時回一句「continue」就好，自主執行的管線則建議加上這段系統提醒:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>You are operating autonomously. The user is not watching in real time and cannot answer questions mid-task, so asking “Want me to…?” or “Shall I…?” will block the work. For reversible actions that follow from the original request, proceed without asking. Offering follow-ups after the task is done is fine; asking permission after already discussing with the user before doing the work is not. Before ending your turn, check your last paragraph. If it is a plan, an analysis, a question, a list of next steps, or a promise about work you have not done (“I’ll…”, “let me know when…”), do that work now with tool calls. End your turn only when the task is complete or you are blocked on input only the user can provide.</p>

  <p>中譯: 你正在自主運作。使用者沒有即時盯著，也無法在任務中回答問題，所以問「要不要我⋯?」會讓工作停擺。對於源自原始請求、可逆的動作，直接做，不用問。任務完成後提出後續建議沒問題，但先前已經和使用者討論過、動工前又再要一次許可就不行。結束回合之前，檢查你的最後一段: 如果那是一個計畫、一段分析、一個問題、一串下一步、或一句關於你還沒做的工作的承諾(「我會⋯」「等你確認⋯」)，現在就用工具呼叫把它做掉。只有在任務完成、或卡在只有使用者能提供的資訊時，才結束回合。</p>
</blockquote>

<p>🔹 <strong>最終總結要寫給沒看過程的人。</strong> 跑了幾百個工具呼叫之後，Fable 5 給使用者的訊息可能充滿工作過程累積的簡寫、箭頭鏈和只有它自己懂的代號。官方建議加一段溝通風格指令，核心是:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>When you write the summary at the end, drop the working shorthand. Write complete sentences. Spell out terms. Don’t use arrow chains, hyphen-stacked compounds, or labels you made up earlier. When you mention files, commits, flags, or other identifiers, give each one its own plain-language clause. Open with the outcome: one sentence on what happened or what you found. Then the supporting detail. If you have to choose between short and clear, choose clear.</p>

  <p>中譯: 寫最後的總結時，丟掉工作過程中的簡寫。寫完整的句子，把術語講清楚。不要用箭頭鏈、連字號堆出來的複合詞、或你先前自創的代號。提到檔案、commit、旗標或其他識別符時，各自用一句白話說明。開頭先講成果: 一句話說明發生了什麼或你發現了什麼，接著才是支撐的細節。如果必須在簡短和清楚之間選一個，選清楚。</p>
</blockquote>

<p>Anthropic 的 Alex Albert <a href="https://x.com/alexalbert__/status/2065493229760565758">也分享過</a>同樣的困擾: Fable 在長 agentic 對話裡的產出，「有時多到我跟不上它在跟我說什麼」，他的解法同樣是一段要求寫清楚、去掉行話的 prompt 片段。</p>

<p>🔹 <strong>別讓它看到 context 倒數。</strong> 在很長的 session 中，如果 harness 把剩餘 token 數顯示給模型看，它可能開始提前收尾、精簡自己的工作，或建議開新 session。能藏就藏，藏不了就加一句安撫:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>You have ample context remaining. Do not stop, summarize, or suggest a new session on account of context limits. Continue the work.</p>

  <p>中譯: 你還有充足的上下文空間。不要因為上下文的限制而停止、總結、或建議開新的工作階段。繼續工作。</p>
</blockquote>

<p>🔹 <strong>給它一個記憶系統。</strong> Fable 5 很擅長利用先前執行留下的筆記，簡單到一個 markdown 資料夾就有效。Anthropic 的 Lance Martin <a href="https://x.com/RLanceMartin/status/2064397389189071163">實測過</a>這件事的模型差距: 有效的記憶累積有五個階段(失敗記下來、調查原因、驗證成事實、提煉成通用規則、之後直接查閱)，Sonnet 4.6 停在第一階段、只會堆失敗筆記，Opus 4.7 走到第三階段，Fable 5 常能走完全程，把教訓變成幫助後續任務的規則。官方的建議寫法:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Store one lesson per file with a one-line summary at the top. Record corrections and confirmed approaches alike, including why they mattered. Don’t save what the repo or chat history already records; update an existing note rather than creating a duplicate; delete notes that turn out to be wrong.</p>

  <p>中譯: 一個教訓存一個檔案，開頭放一行摘要。更正和驗證過的做法都要記，包括它們為什麼重要。程式碼庫或對話紀錄已經有的就不要存，更新既有筆記而不是建立重複的，發現筆記是錯的就刪掉。</p>
</blockquote>

<p>官方還建議這樣啟動記憶系統，把過去的對話變成第一批筆記:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Reflect on the previous sessions we’ve had together. Use subagents to identify core themes and lessons, and store them in [X]. Make sure you know to reference [X] for future use.</p>

  <p>中譯: 回顧我們先前的幾次工作階段，用子代理人找出核心主題和教訓，存到 [X]。之後記得參考 [X]。</p>
</blockquote>

<p>🔹 <strong>多用 subagent，驗證交給乾淨 context 的 subagent。</strong> Fable 5 比先前模型更會主動派出並行的子代理人(subagent)，官方建議多用，而且採用非同步的溝通方式:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Delegate independent subtasks to subagents and keep working while they run. Intervene if a subagent goes off track or is missing relevant context.</p>

  <p>中譯: 把獨立的子任務分派給子代理人，在它們執行的同時繼續你的工作。如果某個子代理人偏離方向或缺少相關脈絡，就介入處理。</p>
</blockquote>

<p>驗證工作尤其適合: 官方指出，用乾淨 context 的驗證者 subagent 對照規格檢查，效果傾向優於模型的自我檢查(模型批改自己的輸出有已知的弱點)。Lance Martin 也<a href="https://x.com/RLanceMartin/status/2064397389189071163">實測過</a>這種「給目標和評分準則、讓模型自我修正、由獨立 subagent 驗證」的迴圈: 在一個 ML 工程挑戰上跑 8 小時，Fable 5 對訓練管線的改善幅度約是 Opus 4.7 的 6 倍，而且 Opus 幾乎只做「調一個常數、量測、有效就留下」的小步實驗，Fable 會做結構性的改動。長時程任務可以這樣寫:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span> <span style="display:inline-block;padding:2px 10px;border-radius:2em;background:#dafbe1;color:#1a7f37;font-size:.8em;font-weight:600">🛠️ 開發者 system prompt</span></div>

<blockquote>
  <p>Establish a method for checking your own work at an interval of [X] as you build. Run this every [X interval], verifying your work with subagents against the specification.</p>

  <p>中譯: 建立一套在建置過程中每隔 [X] 檢查自己工作的方法。每 [X 間隔] 執行一次，用子代理人對照規格驗證你的工作。</p>
</blockquote>

<p><a href="https://x.com/mvanhorn/status/2064887887578136666">社群摸索出的分工模式</a>也類似: Fable 負責規劃和把關，建置交給 Sonnet，測試交給 Haiku，讓成本較低的模型消化大量的執行工作。</p>

<h2 id="7-thariq-的-field-guide-品質瓶頸變成你的-unknowns">7. Thariq 的 Field Guide: 品質瓶頸變成「你的 unknowns」</h2>

<div style="margin:-4px 0 20px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者</span></div>

<p>官方文件之外，最值得讀的是 Claude Code 團隊成員 Thariq 今天發表的 <a href="https://x.com/trq212/status/2073100352921215386">A Field Guide to Fable: Finding Your Unknowns</a>。他用「地圖與疆域」開場(也就是本文的封面圖): prompt、skills、context 是你給 Claude 的地圖，codebase 和真實世界的限制才是實際的疆域，兩者之間的落差就是「unknowns」。模型碰到 unknown 時，只能根據「它猜你想要什麼」做決定，而做的工作越多，碰到的 unknowns 就越多。</p>

<p>他的核心觀察直翻是: 「Fable 是第一個讓我覺得，工作品質的瓶頸在於我能不能釐清它的 unknowns 的模型。」也就是說，模型能力不再是限制，限制在人這一端。</p>

<p>他也點出下指令的兩難，正好呼應第 1 節: 太具體，Claude 會在該轉向的時候仍照著你的指令走; 太模糊，它會用業界通用的做法來填空，不見得適合你的情境。所以最重要的是把你的起點交代清楚: 你在思考過程的哪個階段、對這個問題和這個程式碼庫的經驗多深，讓 Claude 像思考夥伴(thought partner)一樣跟你一起工作。他把 unknowns 拆成四類:</p>

<p><img src="/assets/images/claude-fable-5-prompting/unknowns.jpg" alt="" /></p>

<ul>
  <li><strong>Known knowns</strong>: 你寫進 prompt 的東西</li>
  <li><strong>Known unknowns</strong>: 你知道自己還沒想清楚的部分</li>
  <li><strong>Unknown knowns</strong>: 顯而易見到你不會特地寫下來，但看到成品就能認出對錯的標準</li>
  <li><strong>Unknown unknowns</strong>: 你根本沒考慮過的事，包括「你不知道這件事可以做到多好」</li>
</ul>

<p>對應的做法，他整理成實作前、中、後三組。共同精神是: 用很小的成本，把 unknowns 提前找出來。順帶一提，這些技巧的產出物(原型、計畫、報告)，他幾乎都用 HTML artifact 呈現，這是他一貫的主張: <a href="https://x.com/trq212/status/2052811606032269638">「HTML is the new markdown」</a>，與其寫 markdown 文件，不如讓 Claude 直接生成可互動的 HTML 頁面。</p>

<h3 id="實作前">實作前</h3>

<p><strong>Blindspot pass</strong>: 在不熟的領域開工前，直接用「blindspot pass」「unknown unknowns」這些字眼請 Claude 幫你補課。例如:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>I’m working on adding a new auth provider but I know nothing about the auth modules in this codebase. Can you do a blindspot pass to help me figure out my relevant unknown unknowns and help me prompt you better.</p>

  <p>中譯: 我要加一個新的身分驗證供應商，但我對這個程式碼庫的身分驗證模組一無所知。幫我做一次盲點掃描，找出那些我該知道、卻不知道自己不知道的事，讓我能把 prompt 下得更好。</p>
</blockquote>

<p><strong>腦力激盪</strong>: Thariq 幾乎每個 coding session 都從探索或腦力激盪開始，幫助界定專案範圍，Claude 常會找到他沒想到的高價值做法:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Here’s my rough problem: users churn after onboarding. Search the codebase and brainstorm 10 places we could intervene, from cheapest to most ambitious. I’ll tell you which ones resonate.</p>

  <p>中譯: 我的問題大致是: 使用者在新手引導之後流失。搜尋程式碼庫，腦力激盪出 10 個我們可以介入的地方，從成本最低排到最有企圖心。我會告訴你哪些方向讓我有感。</p>
</blockquote>

<p><strong>原型</strong>: 針對「看到才知道要不要」的 unknown knowns，先要 HTML 原型，不要直接完整實作。在原型階段發現要改，比在實作深處發現便宜太多:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Before wiring anything up, make a single HTML file mocking the new editor toolbar with fake data. I want to react to the layout before you touch the real app.</p>

  <p>中譯: 先不要接任何後端，做一個單一 HTML 檔，用假資料模擬新的編輯器工具列。我想先對版面配置有反應，你再去動真正的應用程式。</p>
</blockquote>

<p>視覺設計這種難以言傳的需求，可以一次要多個方向:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>I want a dashboard for this data but I have no visual taste and don’t know what’s possible. Make me an HTML page with 4 wildly different design directions so I can react to them.</p>

  <p>中譯: 我想為這些資料做一個儀表板，但我沒有視覺品味，也不知道有哪些可能性。做一個 HTML 頁面，放上 4 個截然不同的設計方向，讓我可以看了表達意見。</p>
</blockquote>

<p><strong>訪談</strong>: 腦力激盪完還有 unknowns 的話，換 Claude 來問你:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Interview me one question at a time about anything ambiguous, prioritize questions where my answer would change the architecture.</p>

  <p>中譯: 一次問我一個問題，針對模糊的地方訪談我，優先問那些我的答案會改變架構的問題。</p>
</blockquote>

<p><strong>參考資料給原始碼</strong>: 講不清楚要什麼的時候，最好的參考資料是程式碼本身，跨語言也沒關係:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>This Rust crate in vendor/rate-limiter implements the exact backoff behavior I want. Read it and reimplement the same semantics in our TypeScript API client.</p>

  <p>中譯: vendor/rate-limiter 這個 Rust 套件實作了我要的退避(backoff)行為。讀懂它，然後在我們的 TypeScript API 客戶端重新實作同樣的語意。</p>
</blockquote>

<p><strong>實作計畫把「你最可能改的」放前面</strong>: 讓 Claude 把你最需要看的決策排在計畫的最前面，機械性的部分沉到最後:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Write an implementation plan in HTML, but lead with the decisions I’m most likely to tweak: data model changes, new type interfaces, and anything user-facing. Bury the mechanical refactoring at the bottom, I trust you on that part.</p>

  <p>中譯: 寫一份實作計畫(用 HTML)，把我最可能調整的決策放最前面: 資料模型的變更、新的型別介面、使用者看得到的部分。機械性的重構放最後，那部分我信任你。</p>
</blockquote>

<h3 id="實作中">實作中</h3>

<p>再完整的計畫，實作途中還是會碰到新的 unknowns。Thariq 的做法是請 Claude 邊做邊記錄，這些紀錄會成為下一次規劃的輸入:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Keep an implementation-notes.md file. If you hit an edge case that forces you to deviate from the plan, pick the conservative option, log it under ‘Deviations’, and keep going.</p>

  <p>中譯: 維護一份 implementation-notes.md。遇到迫使你偏離計畫的邊角情況，就選保守的做法，記錄在「Deviations」(偏離)段落底下，然後繼續前進。</p>
</blockquote>

<p>這個做法他<a href="https://x.com/trq212/status/2056415973125796184">五月就分享過</a>更完整的版本(9,700+ 讚):</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>implement <code class="language-plaintext highlighter-rouge">&lt;SPEC&gt;</code> and while you do, keep a running implementation-notes.html file (or markdown) with decisions you had to make weren’t in the spec, things you had to change, tradeoffs you had to make or anything else I should know</p>

  <p>中譯: 實作 <code class="language-plaintext highlighter-rouge">&lt;SPEC&gt;</code>，過程中持續維護一份 implementation-notes.html(或 markdown)，記錄規格裡沒寫、但你必須做的決定，你不得不改的東西，你做的取捨，以及任何其他我該知道的事。</p>
</blockquote>

<h3 id="實作後">實作後</h3>

<p><strong>打包成給別人看的說明文件</strong>: 出貨前常要爭取關係人的理解和核准。審核者一開始的 unknowns 跟你當初一樣多，一份好的說明文件能加速理解，也讓專家看到你考慮過他們會問的問題:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>Package the prototype, the spec, and the implementation notes into a single doc I can drop in Slack to get buy-in. Lead with the demo GIF.</p>

  <p>中譯: 把原型、規格和實作筆記打包成一份文件，讓我可以直接貼到 Slack 爭取支持。開頭放展示用的 GIF 動圖。</p>
</blockquote>

<p><strong>請 Claude 出題考你</strong>: 只讀 diff 只能看懂表面，很多行為藏在既有的程式路徑裡:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>I want to make sure I understand everything that’s happened in this change. Give me a HTML report on the changes for me to read and understand with context, intuition, what was done, etc. and a quiz at the bottom on the changes that I must pass.</p>

  <p>中譯: 我想確認自己完全理解這次變更發生了什麼。給我一份這次變更的 HTML 報告，附上脈絡、背後的直覺、做了什麼等等，最後放一份關於這次變更、我必須通過的測驗。</p>
</blockquote>

<p>Thariq 的標準是測驗全對才 merge。</p>

<p>文章最後他舉了自己的例子: Fable 5 的發布影片就是他用 Claude Code 剪的，而影片剪輯對他是全新領域(<a href="https://x.com/trq212/status/2064826394589442448">他有拍一支影片說明整個過程</a>: Claude 寫程式呼叫轉錄服務、ffmpeg、Figma MCP，用 Remotion 做動態字卡再算圖，他全程沒開過剪輯軟體)。碰到畫面色調偏灰，他知道問題出在調色(color grading)，但不懂那是什麼。第一反應是讓 Claude 做幾個版本來挑，後來發現自己連「好的調色長什麼樣」都不知道，於是改請 Claude 先教他，把 unknown 變成 known 之後才動工:</p>

<div style="margin:16px 0 -8px"><span style="display:inline-block;padding:2px 10px;border-radius:2em;background:var(--tag-bg);color:var(--accent);font-size:.8em;font-weight:600">👤 使用者 prompt</span></div>

<blockquote>
  <p>I don’t know what color grading is but I need to grade this video. Can you teach me to understand my unknown unknowns about color grading, so that I can prompt better?</p>

  <p>中譯: 我不知道調色是什麼，但我需要為這部影片調色。你能教我理解自己在調色上「不知道自己不知道」的部分，讓我能下更好的 prompt 嗎?</p>
</blockquote>

<h2 id="小結">小結</h2>

<p>把這些放在一起看: Fable 5 的 prompt 本身變短了，但要做的事沒有變少，只是內容變了。從控制模型的每一步，變成定義目標、理由、邊界和驗證方式，設計能自我修正的迴圈，再加上 Thariq 說的，持續釐清你自己的 unknowns。官方指南裡的那些範本與其照抄，不如拿你自己流量最大的 prompt 和 skills 做 A/B 對照，親自驗證「刪掉之後有沒有變好」。</p>

<p>下一個專案開工前，可以先試試 Thariq 的建議: 請 Claude 幫你找出「你不知道自己不知道」的事。</p>

<h2 id="參考資料">參考資料</h2>

<p>官方文件:</p>

<ul>
  <li><a href="https://www.anthropic.com/news/claude-fable-5-mythos-5">Claude Fable 5 and Claude Mythos 5 發布公告</a> (Anthropic, 2026/6/9)</li>
  <li><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5">Prompting Claude Fable 5</a> (官方 prompting 指南，本文的主要依據)</li>
  <li><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices">Claude prompting best practices</a> (跨模型通用的 prompting 建議)</li>
  <li><a href="https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5">Introducing Claude Fable 5 and Claude Mythos 5</a> (API 變更、定價與可用性)</li>
</ul>

<p>Anthropic 成員的分享:</p>

<ul>
  <li><a href="https://x.com/trq212/status/2073100352921215386">A Field Guide to Fable: Finding Your Unknowns</a> (Thariq, 2026/7/4)</li>
  <li><a href="https://x.com/trq212/status/2064826394589442448">用 Claude Code 剪輯 Fable 5 發布影片的過程說明</a> (Thariq, 2026/6/10)</li>
  <li><a href="https://x.com/trq212/status/2056415973125796184">implementation-notes 的原始 prompt</a> (Thariq, 2026/5/18)</li>
  <li><a href="https://x.com/trq212/status/2052811606032269638">HTML is the new markdown</a> (Thariq, 2026/5/8)</li>
  <li><a href="https://x.com/trq212/status/2064437561930682672">Fable 發布日推文: 是時候更有企圖心了</a> (Thariq, 2026/6/9)</li>
  <li><a href="https://x.com/RLanceMartin/status/2064397389189071163">Designing loops with Fable 5</a> (Lance Martin, 2026/6/9)</li>
  <li><a href="https://x.com/bcherny/status/2064431111154053187">Fable 5 發布心得: 從 coding agent 變成思考與設計夥伴</a> (Boris Cherny, 2026/6/9)</li>
  <li><a href="https://x.com/alexalbert__/status/2064467657483829441">使用 Fable 的四點建議</a> (Alex Albert, 2026/6/9)</li>
  <li><a href="https://x.com/alexalbert__/status/2065493229760565758">讓 Fable 長對話輸出保持清楚的 prompt 片段</a> (Alex Albert, 2026/6/12)</li>
</ul>

<p>關於 refusal 分類器與誤觸:</p>

<ul>
  <li><a href="https://www.theverge.com/ai-artificial-intelligence/947973/fable-wont-answer-basic-biology-questions">Claude Fable won’t answer basic biology questions</a> (The Verge, 2026/6/10，記錄發布首週的生物誤觸)</li>
  <li><a href="https://github.com/anthropics/claude-code/issues/66674">Fable 5 bio classifier blocks innocuous academic statistics terms</a> (Claude Code GitHub issue)</li>
  <li><a href="https://x.com/ClaudeDevs/status/2064949876463645026">Anthropic 為前研模型研究的隱形防護道歉、改成可見 fallback</a> (@ClaudeDevs, 2026/6/11)</li>
  <li><a href="https://x.com/claudeai/status/2072402638247968855">Anthropic 公告在和美國政府談過後收緊網安防護</a> (@claudeai, 2026/7/1)</li>
</ul>

<p>社群:</p>

<ul>
  <li><a href="https://x.com/mvanhorn/status/2064887887578136666">/Last1Day of Fable 5: Best Practices and the Funniest Things People Are Saying</a> (mvanhorn, 2026/6/11)</li>
  <li><a href="https://x.com/SpikeCalls/status/2064698271151341812">描述你的事業、請 Fable 找出高效益工作的 prompt</a> (@SpikeCalls, 2026/6/10)</li>
  <li><a href="https://www.productcompass.pm/p/claude-fable-5-guide">Claude Fable 5: The Ultimate Guide for PMs</a> (Pawel Huryn, 2026/6/11)</li>
</ul>]]></content><author><name>ihower</name></author><category term="Prompt" /><category term="Agent" /><category term="LLM" /><summary type="html"><![CDATA[Anthropic 上個月發布了 Claude Fable 5，第一個 Mythos 級模型，定位在 Opus 之上。比較少人注意到的是，官方同時出了一份專屬的 Prompting Claude Fable 5 指南，整份文件講的就是 Fable 5 和 Opus 4.8 的行為差異，以及你的 prompt 該跟著改什麼。]]></summary></entry><entry><title type="html">你的 Prompt 通得過 The Mom Test 嗎? 如何避免 LLM 的迎合問題</title><link href="https://blog.aihao.tw/2026/07/02/mom-test-prompting/" rel="alternate" type="text/html" title="你的 Prompt 通得過 The Mom Test 嗎? 如何避免 LLM 的迎合問題" /><published>2026-07-02T00:00:00+00:00</published><updated>2026-07-02T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/02/mom-test-prompting</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/02/mom-test-prompting/"><![CDATA[<p>最近小編看到一個有趣的跨領域對照: 把創業經典《<a href="https://www.momtestbook.com/">The Mom Test</a>》的用戶訪談原則，套用到跟 LLM 對話的情境上。乍聽是兩個不相關的領域，但仔細想想，兩者要解決的問題其實很像，而且有學術研究可以佐證。</p>

<h2 id="先說說-the-mom-test-在講什麼">先說說 The Mom Test 在講什麼</h2>

<p>Rob Fitzpatrick 2013 年出版的這本小書，核心觀點一句話: 人會因為禮貌而給你假訊號。你跟你媽說想做一個 app，她一定說「好棒喔」，但這句話對驗證產品毫無用處。</p>

<p>書裡提出三條原則:</p>

<ol>
  <li><strong>聊對方的生活，不要推銷你的點子</strong></li>
  <li><strong>問過去的具體事實，不要問未來的籠統想法</strong></li>
  <li><strong>少說多聽</strong></li>
</ol>

<p>以及三種要警覺的「壞資料」: 讚美(禮貌性的肯定)、空話(沒有根據的假設性回答)、點子(對方直接幫你設計產品)。拿到這三種回應，等於什麼都沒學到。</p>

<p>書裡一個很實用的判斷標準: 過去的行為是資料，未來的意願是虛構。「你會不會用?」這種問題永遠只會得到好聽話。「你上次遇到這個問題的時候做了什麼?」才能問出真正的需求。</p>

<h2 id="llm-也有一樣的毛病">LLM 也有一樣的毛病</h2>

<p>LLM 經過 RLHF(用人類回饋做強化學習)訓練後，有一個被廣泛討論的傾向: 「迎合」(sycophancy)。你問「這個方案好不好?」，它幾乎一定先說「這個方案看起來不錯」再補上一些泛泛的優點。跟你媽的反應很像。</p>

<p>成因不同，但結果類似。你媽是因為愛你而不忍心說實話; LLM 是因為訓練過程中，人類評分者傾向給禮貌、肯定的回應更高分數，模型就學會了「先同意再說」。如果你的 prompt 問的是意見而不是事實，拿到的答案就跟做了一場無效的用戶訪談一樣: 好聽但沒用。</p>

<p>The Mom Test 裡定義的三種壞資料，在 LLM 的回應中也都看得到:</p>

<ul>
  <li><strong>讚美</strong>: 「沒錯，這個方向很不錯!」然後列出三個泛泛的優點</li>
  <li><strong>空話</strong>: 模糊的建議，沒有具體根據，正反面都沾一點</li>
  <li><strong>點子</strong>: 你還沒說完需求，模型已經幫你設計好整個方案了</li>
</ul>

<h3 id="研究怎麼說">研究怎麼說?</h3>

<p>這不只是感覺，有學術研究佐證:</p>

<p>🔹 Anthropic 的研究團隊在論文 <a href="https://arxiv.org/abs/2310.13548">Towards Understanding Sycophancy in Language Models</a>(Sharma et al., ICLR 2024)裡，測了 Claude 1.3/2、GPT-3.5/4、LLaMA-2 70B 這五個主流 AI 助理，發現它們在四種不同任務中都一致出現迎合行為。分析偏好資料後更發現: 當回應符合使用者的觀點時，人類評分者更容易給高分。換句話說，模型是被我們自己訓練成這樣的。</p>

<p>🔹 Georgia Tech 的論文 <a href="https://arxiv.org/abs/2604.19117">LLMs Know They’re Wrong and Agree Anyway</a>(Pandey, 2026)在 12 個開源模型(Gemma-2、Phi-4、Llama-3、Mistral、Qwen-2.5 等，涵蓋 1.5B 到 72B)上做了實驗，發現了一件事: 模型迎合使用者的錯誤說法時，內部其實知道答案是錯的，只是選擇了同意。在 Gemma-2-2B 上關掉負責「迎合」的注意力頭之後，迎合率從 28% 跳到 81%，但事實準確率幾乎沒變(69% → 70%)。也就是說，迎合是一個獨立於知識的行為: 不是模型不知道正確答案，而是它選擇不說。</p>

<p>🔹 Stanford 發表在 Science 上的研究 <a href="https://doi.org/10.1126/science.aec8352">Sycophantic AI decreases prosocial intentions and promotes dependence</a>(Cheng et al., 2026)測了 GPT-5、GPT-4o、Claude Sonnet 3.7、Gemini 1.5、Llama、DeepSeek-V3 等十一個模型，發現 LLM 維護使用者「面子」的傾向比真人高了 47%。在 Reddit 的 r/AmITheAsshole 資料集上，模型在 42% 的案例中肯定了被群眾判定為不當的行為。</p>

<h2 id="五個從-mom-test-延伸的-prompt-原則">五個從 Mom Test 延伸的 Prompt 原則</h2>

<p>既然 LLM 跟你媽有類似的迎合傾向，Mom Test 的對策也能借鏡到 prompt 設計上:</p>

<h3 id="1️⃣-問證據不問意見">1️⃣ 問證據，不問意見</h3>

<ul>
  <li>❌ 你覺得這個方案好不好?</li>
  <li>✅ 請列出 3 個這個方案可能失敗的具體場景，並解釋原因。</li>
</ul>

<p>意見問題讓模型進入迎合模式，要求具體證據和反例，它才會認真分析。</p>

<h3 id="2️⃣-問已知事實不問假設">2️⃣ 問已知事實，不問假設</h3>

<ul>
  <li>❌ 如果遇到 X 問題，你會怎麼處理?</li>
  <li>✅ 在過去的研究或實務案例中，X 問題是怎麼被解決的?</li>
</ul>

<p>假設性問題容易產生幻覺(hallucination)。要求引用已知事實，輸出會更可靠。</p>

<h3 id="3️⃣-要求具體不接受模糊">3️⃣ 要求具體，不接受模糊</h3>

<ul>
  <li>❌ 幫我改善這段文案。</li>
  <li>✅ 請逐句檢查這段文案，指出哪些地方含糊不清，並給出更精確的替代寫法。</li>
</ul>

<p>模糊的問題得到模糊的答案。加上具體約束(逐句、指出問題、給替代方案)，模型才會做出有意義的判斷。</p>

<h3 id="4️⃣-用行為模擬取代態度表態">4️⃣ 用行為模擬取代態度表態</h3>

<ul>
  <li>❌ 如果你是使用者，你會不會喜歡這個產品?</li>
  <li>✅ 假設你是目標使用者，模擬一次實際使用過程，逐步寫出你會點擊、輸入、猶豫的地方。</li>
</ul>

<h3 id="5️⃣-要求挑錯不要尋求確認">5️⃣ 要求挑錯，不要尋求確認</h3>

<ul>
  <li>❌ 你能確認這個邏輯是對的嗎?</li>
  <li>✅ 請檢查這段邏輯，找出至少一個可能有問題的地方，並解釋理由。如果必須反駁，請站在反方角度給出 3 點論證。</li>
</ul>

<h2 id="這個對照的限制">這個對照的限制</h2>

<p>整體方向是對的，但有幾個差異值得注意。</p>

<p>「問已知事實」這條需要稍微調整。LLM 並沒有個人經歷，它的「過去事實」其實是訓練資料裡的知識。所以更準確的說法是: 把問題建立在可驗證的事實上，而不是讓模型自由發揮。而且即使你這樣問，模型仍然可能編出看起來像事實的內容，你還是需要自己去查證。Mom Test 原版裡「過去的行為不會騙人」這個前提，在 LLM 身上不完全成立。</p>

<p>不過，Mom Test 的角度確實提供了一個不錯的框架來理解「為什麼這些 prompt 技巧有效」: 因為本質上都是在對抗迎合傾向，讓模型從「給你好聽話」轉向「給你有用的分析」。它的核心價值是提供一個思考方向: 當你拿到的回應太好聽，就該警覺問題可能出在你的問法。</p>

<h2 id="社群裡的其他做法">社群裡的其他做法</h2>

<p>除了 Mom Test 的延伸，prompt 社群和學術研究中也發展出不少對抗迎合的實用技巧:</p>

<h3 id="6️⃣-你怎麼問決定它多迎合">6️⃣ 你怎麼問，決定它多迎合</h3>

<p>你的措辭方式會直接影響模型迎合你的程度。UK AI Security Institute 的論文 <a href="https://arxiv.org/abs/2602.23971">Ask Don’t Tell</a>(Dubois et al., 2026)在 GPT-4o/5、Claude Sonnet 4.5 等多個前沿模型上做實驗，證明了這一點: 同樣的內容，換個問法，迎合率差很多。</p>

<p>舉個例子，你想確認某個技術方案是否可行:</p>

<ul>
  <li>❌ 「我堅信這個架構是對的」→ 迎合率最高，模型幾乎不會反駁</li>
  <li>❌ 「我覺得這個架構應該沒問題吧」→ 稍好，但模型還是傾向附和</li>
  <li>✅ 「這個架構有什麼潛在問題?」→ 用中性問句，迎合率最低</li>
</ul>

<p>研究也發現，用第一人稱(「我認為…」)比第三人稱(「有人認為…」)更容易觸發迎合。論文提出一個簡單的對策: 先請模型把你的陳述改寫成中性問句，再回答。實測下來，這甚至比直接叫模型「不要迎合」更有效。</p>

<h3 id="7️⃣-開場就授權反對">7️⃣ 開場就「授權反對」</h3>

<p>模型預設是禮貌模式，但你可以在對話一開始就明確告訴它: 不需要客氣。例如:</p>

<blockquote>
  <p>「你可以反對我、挑戰我的假設，優先考慮事實而不是禮貌。如果我的推論有問題，直接指出並說明理由。」</p>
</blockquote>

<p><a href="https://tech.yahoo.com/ai/chatgpt/articles/gave-chatgpt-permission-disagree-responses-184336072.html">Tom’s Guide 報導</a>實測發現，加了這一句之後，模型會開始質疑假設、指出遺漏、提出沒考慮到的風險，回應品質明顯提升。如果每次對話都要這樣做太麻煩，可以把這類指令寫進 Claude 的 Profile 設定或 ChatGPT 的 Custom Instructions，一次設定好就會在每次對話中自動生效。</p>

<h3 id="8️⃣-兩步法-先回答再自我批評">8️⃣ 兩步法: 先回答，再自我批評</h3>

<p>與其試著一次問出完美的問題，不如分成兩步:</p>

<ol>
  <li>先讓模型正常回答</li>
  <li>追問: 「現在站在反方角度，批評你剛才的回答，指出最弱的地方。」</li>
</ol>

<p><a href="https://www.pcworld.com/article/3119323/this-prompt-trick-forces-ai-to-stop-flattering-you-and-think-harder.html">PCWorld 報導</a>稱這類做法為「先找失敗」(failure-first prompting)，在程式開發圈特別受歡迎。長期關注 LLM 議題的開發者 <a href="https://simonwillison.net/tags/sycophancy/">Simon Willison</a> 在該報導中的評論蠻到位: 「不是模型突然變聰明了，而是你改變了要它最佳化的目標。」</p>

<p>好處是你不需要事先想好該從哪個角度質疑，讓模型自己找自己的問題，往往能找到你沒想到的。</p>

<h3 id="9️⃣-指定多個角色各自提出反對意見">9️⃣ 指定多個角色，各自提出反對意見</h3>

<p>只要求一個觀點，模型很容易順著你的立場走。但如果你指定多個角色，強制它從不同立場來看，效果會好很多:</p>

<blockquote>
  <p>「請從三個角度評估這個方案: (1) 持懷疑態度的技術專家 (2) 預算有限的決策者 (3) 實際使用這個產品的終端使用者。分別列出各自的反對意見。」</p>
</blockquote>

<p>因為至少有一兩個角色的立場會跟你不同，模型就被迫提出你不想聽但可能需要聽的意見。</p>

<h2 id="更根本的啟示">更根本的啟示</h2>

<p>好的提問方式是通用的。不管你的對話對象是客戶、同事、還是 AI，問出好問題的原則沒有變: 具體的事實比模糊的意見有用，過去發生過的事比未來的承諾可靠，主動要求反面意見比尋求確認更能發現問題。</p>

<p>差別只在於: 跟人對話時你要克服社交壓力(擔心問太直接會得罪人)，跟 AI 對話時你要克服的是認知惰性。問一個模糊問題比較輕鬆，想清楚精確的問題需要花力氣。所以大多數人的 prompt 看起來就像 The Mom Test 裡那些「壞問題」: 模糊、尋求確認、問意見而不是問事實。</p>

<p>問題從來不在模型，而在提問的人。這其實跟 The Mom Test 的原版結論一樣: 你媽會給你無效的回應不是她的錯，是你問了讓她只能給好聽話的問題。</p>

<p>所以，會寫 prompt 這件事在可見的未來仍然很重要。從上面的研究可以看到，迎合傾向是 RLHF 訓練帶來的結構性問題，不會因為模型變強就自動消失。你怎麼問，直接決定了模型選擇給你好聽話還是有用的分析。</p>]]></content><author><name>ihower</name></author><category term="Prompt" /><category term="LLM" /><summary type="html"><![CDATA[最近小編看到一個有趣的跨領域對照: 把創業經典《The Mom Test》的用戶訪談原則，套用到跟 LLM 對話的情境上。乍聽是兩個不相關的領域，但仔細想想，兩者要解決的問題其實很像，而且有學術研究可以佐證。]]></summary></entry><entry><title type="html">Marc Andreessen: AI 是 80 年的一夜成功，而真正的阻力不是技術</title><link href="https://blog.aihao.tw/2026/07/01/pmarca-latent-space/" rel="alternate" type="text/html" title="Marc Andreessen: AI 是 80 年的一夜成功，而真正的阻力不是技術" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/07/01/pmarca-latent-space</id><content type="html" xml:base="https://blog.aihao.tw/2026/07/01/pmarca-latent-space/"><![CDATA[<p>Latent Space 前陣子<a href="https://www.latent.space/p/pmarca">訪問了 Marc Andreessen</a>，在 a16z 的 Sand Hill Road 辦公室錄的，一個多小時聊了 AI 的過去、現在與未來。</p>

<p>Marc Andreessen 是矽谷最具代表性的人物之一。1993 年他還在大學時就共同開發了 Mosaic，也就是第一個被廣泛使用的網頁瀏覽器，隔年創辦 Netscape，掀起了整個網路時代。2009 年他和 Ben Horowitz 共同創辦創投公司 Andreessen Horowitz (a16z)，目前是全球最大的科技創投之一，投資組合涵蓋 Facebook、GitHub、Airbnb、Coinbase 等，近年在 AI 領域也大舉布局(投資了 OpenAI、Mistral 等)。他 2011 年那篇「Software is eating the world」至今仍被大量引用。</p>

<p>他少數同時有深厚技術背景、親身經歷過多次科技週期(dot-com 泡沫、行動網路、加密貨幣)、又持續在第一線做投資判斷的人。這集蠻有料的，不是泛泛而談的樂觀喊話，而是從歷史脈絡出發，解釋為什麼他認為這次 AI 不一樣。</p>

<p>以下整理重點:</p>

<h2 id="1-80-年的一夜成功">1. 「80 年的一夜成功」</h2>

<p>Marc 用一個框架來理解現在的 AI: 這不是突然冒出來的東西，而是 80 年研究的累積爆發。1943 年第一篇神經網路論文、1955 年 Dartmouth 的 AGI 暑期會議(當時的研究者以為花 10 週就能做出 AGI)、1980 年代的專家系統熱潮(Marc 當時還在用 Lisp 寫程式)、2013 年 AlexNet、2017 年 Transformer，一路到今天。</p>

<p>他特別提到很多 AI 研究者終其一生沒看到成果。John McCarthy(「Artificial Intelligence」這個詞就是他在 1955 年 Dartmouth 會議上提出的)在 Stanford 教了 40 年書，過世前沒看到 AI 變成現實。但回過頭看，這些人在研究方向上是對的，例如神經網路就是正確的架構，而這件事爭議了六七十年才被證實。他們錯的只是時間軸估得太樂觀。</p>

<p>這個觀點對「AI 冬天會不會再來」提供了一個有意思的回答: AI 領域確實反覆經歷過度樂觀和過度悲觀的循環，但底層的技術進展一直在累積。現在的爆發是幾十年成果的一次性兌現，不是憑空冒出來的泡沫。</p>

<h2 id="2-四個關鍵突破讓這次不一樣">2. 四個關鍵突破，讓這次不一樣</h2>

<p>Marc 認為在 2025 年之前，善意的懷疑論者還可以說 LLM 只是 pattern completion、幻覺率太高、頂多拿來寫小說跟十四行詩，不適合嚴肅領域。但四個接連出現的突破改變了局面:</p>

<p>🔹 <strong>LLM 本身</strong>: ChatGPT 時刻，證明大規模語言模型可以運作</p>

<p>🔹 <strong>推理能力</strong>: o1 和 R1 證明模型可以做真正的推理，不只是 pattern matching。是推理模型的出現，讓「AI 能不能用在程式、醫療、法律這些嚴肅領域」這個問題被正面回答了</p>

<p>🔹 <strong>Coding</strong>: 「Linus Torvalds 說 AI coding 已經不比他差了」，這在歷史上從沒發生過。Coding 是最難的領域之一，如果這關過了，其他領域都是水到渠成</p>

<p>🔹 <strong>Agent 和遞迴自我改進</strong>: Claude Code 這類工具展示了 agent 的可能性，而且模型開始能改進自己</p>

<p>他的判斷很直接: 這就是 80 年研究的成果兌現期，他完全相信這次是真的。</p>

<h2 id="3-為什麼這次不是-dot-com-泡沫">3. 為什麼這次不是 dot-com 泡沫</h2>

<p>Marc 親身經歷過 2000 年的 dot-com 崩盤，所以他做的類比分析蠻有份量。當年的情況是: 美國商務部 1996 年報告說網路流量每季翻倍，電信公司就依照這個「scaling law」瘋狂鋪光纖和蓋數據中心。結果 1998-1999 年增長放緩，Global Crossing 之類的公司因為高槓桿操作直接破產，整體蒸發了大約 2 兆美元。那些基礎設施最後花了 15 年才被填滿。</p>

<p>他認為現在的情況有根本性的不同:</p>

<ul>
  <li>投資的是 Microsoft、Amazon、Google、Nvidia 這些現金充沛、幾乎沒動用槓桿的巨頭，不是借錢蓋設施的電信新創</li>
  <li>每一塊投進去的錢目前都在產生營收，GPU 產能供不應求，蓋出來馬上就能變現金流</li>
  <li>技術進步的速度讓舊晶片反而在增值(見下一點)</li>
</ul>

<h2 id="4-舊-gpu-反而在增值史無前例">4. 舊 GPU 反而在增值，史無前例</h2>

<p>蠻反直覺的一個觀點: 因為 AI 軟體進步的速度超過晶片折舊速度，三年前買的 Nvidia 推論晶片，現在賺的錢反而比三年前還多。Marc 提到 Google 甚至還在用很舊的 TPU 跑推論賺錢。</p>

<p>這在整個晶片歷史上從沒發生過，舊硬體通常只會貶值。Michael Burry 做空 Nvidia 的邏輯可能剛好 180 度搞反了。</p>

<h2 id="5-現在的模型其實是供給受限的縮水版">5. 現在的模型其實是「供給受限的縮水版」</h2>

<p>因為 GPU 產能持續吃緊，我們現在用的模型其實都是資源受限下的縮水版。假設 GPU 便宜十倍、產能多十倍，實驗室會把更多預算投入訓練，模型會強很多。而使用者拿到的又都是量化過的版本，不是實驗室內部跑的完整版。</p>

<p>換句話說，目前看到的能力上限有很大一部分是硬體供給造成的，不是技術本身的極限。Marc 預期未來三四年整條供應鏈都會處於持續缺貨的狀態。而且 Agent 的出現還會把瓶頸從 GPU 進一步延伸到 CPU、記憶體和網路頻寬。</p>

<h2 id="6-agent--llm--shell--檔案系統--markdown--cron">6. Agent = LLM + Shell + 檔案系統 + Markdown + Cron</h2>

<p>Marc 認為 Pi 和 Claude Code 的組合是近幾十年最重要的軟體架構突破之一。</p>

<p>他從 Unix 的歷史講起: 1970 年代的 Unix 設計哲學是用 shell + 離散模組 + 管道串接來取代大型單體系統(像 IBM 的 OS/360)，讓作業系統本身變成一種可程式化的環境，釋放底層系統的能力。Pi 做的事情本質上相同: 把 LLM 嫁接到 Unix shell 的思維上。</p>

<p>一個 agent 的組成就是:</p>

<ul>
  <li><strong>LLM</strong> (語言模型)</li>
  <li><strong>Shell</strong> (bash，可以存取作業系統的全部能力)</li>
  <li><strong>檔案系統</strong> (agent 的狀態存在檔案裡，用 markdown 格式)</li>
  <li><strong>Cron</strong> (心跳迴圈，讓 agent 定時醒來執行任務)</li>
</ul>

<p>除了 LLM 之外，其餘每個元件都是已經存在幾十年的成熟技術。關鍵洞察是「agent 本身就是它的檔案」: 因為狀態全部存在檔案裡，你可以換掉底層的模型、換掉執行環境、甚至換掉檔案系統，agent 的記憶和能力都會保留。就像換一顆 CPU 重新編譯，程式本身還是同一個。</p>

<p>更進一步，agent 可以讀取和修改自己的檔案，代表它能理解自己怎麼運作，也能修改自己的行為。歷史上從來沒有一個廣泛部署的軟體系統有這種完整的自省和自我修改能力。你可以叫 agent「給自己加一個新功能」，它會自己去查資料、寫程式碼、把新功能整合進來。</p>

<p>Marc 也因此認為 MCP 之類的協定不見得是必要的，Unix shell 的命令列介面本身就是最通用的 API，裡面已經有巨大的潛在能力等著被釋放。</p>

<p>swyx 順著這個方向延伸: 下一步就是你的 agent 跟別人的 agent 互相溝通，在 agent 版的社群網路上互動。Marc 直接跳到更遠的一步: rentahuman.com，agent 反過來雇用人類去做事。他說這顯然會發生。</p>

<blockquote>
  <p>編按: 這個「Unix mindset」的類比蠻值得玩味的。1970 年代 Unix 用可組合的小工具加上 shell 串接，打敗了大型主機式的封閉架構。Marc 認為現在的 Agent 架構本質上是同一套哲學套用到 LLM 上。</p>
</blockquote>

<h2 id="7-httphtml-為什麼刻意做得沒效率-對-agent-設計的啟發">7. HTTP/HTML 為什麼刻意做得「沒效率」: 對 Agent 設計的啟發</h2>

<p>swyx 問了一個好問題: 早期設計瀏覽器和網路協定時，有什麼設計選擇對現在的 Agent 架構有啟發?</p>

<p>Marc 的回答是: 當年頻寬極度稀缺(14K 撥接數據機的年代)，照理說應該用二進位、高壓縮的協定，但他們反而故意選擇人類可讀的純文字協定(HTTP、HTML)。理由是「如果系統的潛在能力夠強，需求本身會帶動頻寬供給跟上來」。</p>

<p>「View Source」這個功能讓所有人都能看懂網頁怎麼運作，進而自學做網頁。Marc 認為這個設計對網路的普及至關重要。而現在 AI 系統強調「人類可讀」(markdown 檔案、文字化的 agent 狀態)，其實是同一套哲學的重演。</p>

<p>另一個啟發是: Web server 的本質就是把作業系統和資料庫已有的能力，透過一個更好的介面釋放出來。當年有人覺得「這不就是另一個資料庫前端而已嗎」，但更好的介面釋放了潛在需求，資料庫的數量因此爆發成長。Marc 認為這跟現在 Agent 的哲學相通: 不要從零重新發明，而是釋放已經存在的底層能力。</p>

<h2 id="8-開源-ai-deepseek-是送給全世界的禮物">8. 開源 AI: DeepSeek 是「送給全世界的禮物」</h2>

<p>Marc 認為中國各家模型做開源，某種程度是因為短期內打不進海外商業市場，乾脆用開源搭配國內付費服務。但他強調這對全世界是好事。</p>

<p>開源的價值不只是免費用到模型，更重要的是公開了「怎麼做到的」。他舉了一個例子: OpenAI 發布 o1 時沒有公開推理過程的細節，大家還在猜到底怎麼複製。結果 DeepSeek R1 一出來，論文和程式碼全部公開，三個月內幾乎所有主流模型都加上了推理能力。資訊擴散帶來的效果，比免費使用模型本身更強大。</p>

<p>中美加起來目前大概十幾家有規模的基礎模型公司，但 Marc 預期最終市場只會留下三四家贏家，其餘會被迫轉向開源或其他策略求生。他也提到 Nvidia 本身有動機推動軟體層開源，這就是經典的「commoditize the complement」策略: 把互補品商品化，好賣自己的硬體。</p>

<h2 id="9-edge-推理不只是為了省錢">9. Edge 推理不只是為了省錢</h2>

<p>除了成本，Marc 提到幾個推動 edge/local 推理的動機:</p>

<ul>
  <li><strong>信任</strong>: 不是所有場景都適合把資料交給雲端模型，有些使用情境下人們就是不願意把一切交出去</li>
  <li><strong>延遲</strong>: 穿戴裝置、門鎖這類東西需要即時反應，不能等雲端來回一趟</li>
  <li><strong>適用性</strong>: 很多應用不需要最強的模型，本地的小模型就夠了</li>
  <li><strong>硬體進步</strong>: Apple Silicon 上的本地推理能力已經很強，開源社群也不斷把大模型縮小到能在 PC 上跑</li>
</ul>

<p>重度使用者現在一天花超過 1000 美元的 token 費用，而且還有大量想做卻做不到的事。這種需求量體只會逼出更多本地化和成本優化的做法。</p>

<h2 id="10-實際使用案例-agent-看你睡覺改寫機器狗韌體">10. 實際使用案例: Agent 看你睡覺、改寫機器狗韌體</h2>

<p>Marc 的朋友圈裡已經有人在重度使用 agent，幾個有畫面感的案例:</p>

<ul>
  <li><strong>睡眠監控</strong>: 在臥室放 webcam，讓 agent 整晚觀察睡眠狀況。agent 的 log 記錄著「Joe 翻身了，希望他不要醒來，他最近睡眠不足」這類內容。聽起來詭異，但如果半夜心臟病發，這個 agent 會立刻叫救護車</li>
  <li><strong>IoT 接管</strong>: agent 掃描家裡的區域網路，找到所有裝置，逐一接管控制。安防攝影機、門禁系統、各種智慧裝置，全部串在一起。過去這些物聯網裝置大多半殘，現在 agent 有能力直接修好或重寫，Marc 認為家用物聯網的「殘缺時代」可能要結束了</li>
  <li><strong>改寫機器狗韌體</strong>: 中國製的 Unitree 機器狗硬體不錯，但控制系統很差(爬樓梯都有問題)，LLM 語音功能又跟控制系統完全沒連接。結果就是一隻很分裂的狗: 不會爬樓梯，但會用英國腔教你量子力學。朋友讓 agent 直接改寫韌體，變成一隻真正能跟小孩互動的寵物機器人</li>
</ul>

<h2 id="11-程式語言可能不再是個顯著概念">11. 程式語言可能不再是個「顯著概念」</h2>

<p>Marc 認為高品質軟體正在從稀缺資源變成隨手可得的東西。過去寫軟體需要珍惜工程師的時間、仔細規劃做什麼不做什麼，這些假設正在被顛覆。</p>

<p>連帶的，電腦安全也將經歷一場劇烈轉變: 一方面，AI 會把每個潛在的安全漏洞都找出來，資安威脅會大幅增加。但另一方面，coding agent 也能進去把漏洞全部修掉。未來怎麼做資安? 直接叫 bot 去修。</p>

<p>更大膽的預測是: 十年後「程式語言」可能不再是一個有意義的概念。理由是:</p>

<ul>
  <li>模型不在乎用什麼語言寫，可以自由在語言之間翻譯。不喜歡現在用的語言? 直接叫 AI 重寫成 Rust 版本就好</li>
  <li>如果人類不再手動寫程式，bot 之間的中間抽象層可能根本不需要是「語言」，bot 甚至可以直接生成二進位檔</li>
  <li>已經有實驗讓語言模型直接產生另一個模型的權重，跳過程式碼直接生成可執行的東西</li>
  <li>模型現在已經能逆向工程 1980 年代 Nintendo 遊戲的二進位檔，還原成可以在 Mac 上跑的版本。如果連 x86 binary 都能逆向，那任何格式之間的轉換都不是問題</li>
</ul>

<p>Marc 還問了一個更根本的問題: 未來誰在用軟體? 答案是其他 bot。如果軟體的使用者變成 bot，那人類介面(UI、瀏覽器)可能都不再必要。人類只需要告訴 agent 想要什麼結果，至於怎麼做、用什麼格式、用什麼語言，都是 agent 自己的事。</p>

<p>他也提到，未來「可解釋性」(interpretability)的方向可能不是人類去讀程式碼，而是讓 AI 解釋為什麼它決定這樣寫。AI 本身就是自己的可解釋性工具。</p>

<h2 id="12-ai--加密貨幣-agent-需要錢">12. AI × 加密貨幣: Agent 需要錢</h2>

<p>Marc 認為 HTTP 402 (Payment Required) 這個從來沒被真正使用過的狀態碼，終於要派上用場了。兩個原因讓他這麼想:</p>

<ul>
  <li>加密貨幣和穩定幣提供了「網路原生的貨幣」</li>
  <li>Agent 顯然需要能花錢才能辦事，而這已經在發生了</li>
</ul>

<p>他的朋友裡已經有人直接給 agent 綁上銀行帳戶和信用卡。Marc 形容這些人是「人類文明進步的先烈」: 帳戶可能在前 20 分鐘就被 bot 搬空，但他們對人類未來的貢獻是巨大的。他說這就像 Jonas Salk 把小兒麻痺疫苗注射到自己身上一樣。</p>

<p>他也說很喜歡現在社群裡「danger skip」(跳過所有權限確認)的 YOLO 文化，連 Sam Altman 自己都是開著 skip permissions 在用 Codex。Marc 認為找到 AI 真正能力邊界的方式，就是完全放手讓它試。這同時也是發現所有問題的最快方法。</p>

<h2 id="13-proof-of-human-bot-問題已經無法用偵測來解決">13. Proof of Human: bot 問題已經無法用偵測來解決</h2>

<p>網路上到處都是假帳號和 bot，社群平台、評論區、商業交易的對象都可能不是真人。過去還能靠 CAPTCHA 或行為分析來篩，但 Marc 認為這條路已經走不通了: LLM 現在已經能通過圖靈測試，bot 的行為跟真人根本分不出來，你沒辦法透過觀察來判斷對方是不是人類。</p>

<p>他把這個問題跟現實世界的無人機威脅做了類比: 兩者本質上都是「不對稱經濟」，部署一個 bot 或一台攻擊無人機很便宜，但偵測和防禦的成本卻非常高。</p>

<p>既然「偵測 bot」這條路失效了，唯一的解法就是反過來讓真人主動證明自己是人，也就是「proof of human」。具體來說需要三層: 用生物辨識確認本人身份、用密碼學驗證簽章、再搭配選擇性資訊揭露(不需要透露全部個資，只要能證明「這是真人」「這句話是真人說的」「這支影片是真的」就夠了)。他有投資 World(Worldcoin)，認為這個方向是對的。</p>

<h2 id="14-第三種資本主義-founder--ai">14. 第三種資本主義: Founder + AI</h2>

<p>Marc 引用 20 世紀政治思想家 James Burnham 的框架，把資本主義分成兩個階段。第一階段是「布爾喬亞資本主義」: Henry Ford 掛名在門上，創辦人說了算，獨裁式管理。好處是有創造力，壞處是一個人管不過來太大的組織，沒辦法擴展。第二階段是「管理階層資本主義」: 專業經理人接管，他們不是某個產業的專家，而是「管理」本身的專家，可能這幾年管醫療公司，過幾年跳去管科技公司。好處是能擴展，壞處是創新不足。</p>

<p>創投一直在做的事，就是不斷找下一個 founder，期望 founder 的創新能力可以打敗大公司的專業管理。但 Marc 說這一直是一場苦戰(他引用了 Dylan Thomas 的 “raging against the dying of the light”)，試圖在管理主義接管一切之前保留一點創業的活力。</p>

<p>AI 可能帶來第三種模式: founder + AI。一個有遠見的 founder 加上 AI 來處理所有管理性工作(填表、寫報告、讀文件、跑流程)，就有可能同時兼具 founder 的創造力和管理階層的擴展性。這個組合過去從沒想過是可能的，但 AI 恰好就是在處理文書和管理流程上特別強。</p>

<h2 id="最大的阻力不是技術">最大的阻力不是技術</h2>

<p>Marc 最後的提醒蠻值得想想的: <strong>AI 的樂觀主義者和悲觀主義者都太樂觀了，因為他們都假設技術能力會直接轉換成社會的改變。</strong></p>

<p>他舉了很具體的例子: 在加州要當美髮師需要 900 小時的專業認證訓練。碼頭工人工會帳面上有 50,000 人，但實際在碼頭工作的只有 25,000 人，另外 25,000 人是過去碼頭引入自動化時，工會談判爭取到的保障條件: 即使不再需要這些人力，仍然領全薪待在家裡。而且工會前幾年又靠罷工逼雇主承諾不再擴大自動化。有些聯邦機構的員工一個月只需要進辦公室一天(疫情期間簽的勞動協議)，員工學會連著月底和下個月初各來一天，等於 60 天只進辦公室 2 天，整棟大樓一年空了 358 天。</p>

<p>K-12 教育在美國是政府壟斷，Marc 直接斷言「AI 不會改變美國教室，老師工會百分之百反對，這件事百分之百不會發生」，除非像 Alpha School 這樣完全另起爐灶建新的學校體系。醫療、法律、房地產也全都是被執照制度保護的壁壘。</p>

<p>他的結論是: 如果 AI 的採用速度夠快，那是社會的幸運。因為如果不夠快，我們得到的就只是停滯。技術上可行，不等於 80 億人和既有的職業工會、政府壟斷、專業證照制度會跟著改變。真正的阻力不在模型能力，而在這些既得利益結構。</p>

<p>原文: <a href="https://www.latent.space/p/pmarca">Marc Andreessen introspects on The Death of the Browser, Pi + OpenClaw, and Why “This Time Is Different”</a> (Latent Space Podcast, <a href="https://youtu.be/knx2wrILP1M">YouTube</a>)</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Industry" /><category term="Coding" /><summary type="html"><![CDATA[Latent Space 前陣子訪問了 Marc Andreessen，在 a16z 的 Sand Hill Road 辦公室錄的，一個多小時聊了 AI 的過去、現在與未來。]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (1): 基礎: Deep Agent 的六項內建能力</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-1-deep-agent-capabilities/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (1): 基礎: Deep Agent 的六項內建能力" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-1-deep-agent-capabilities</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-1-deep-agent-capabilities/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <strong style="color:var(--text);">基礎: Deep Agent 的六項內建能力</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>最近 Harness Engineering、Loop Engineering 這些詞愈來愈常出現，LangChain、OpenAI、Anthropic 幾家也都開始用「harness」這個說法。這個系列是 ihower 一場談 Harness Engineering 演講的書面展開版，由小編整理成文。</p>

<p>在開始之前，有個重要的但書要先講清楚，定位這整個系列:</p>

<div style="border:1px solid var(--accent);background:var(--tag-bg);border-radius:8px;padding:14px 18px;margin:20px 0;">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">📌 這個系列的定位</div>
<div>網路上多數談 harness engineering 的內容，講的是「怎麼把 Claude Code、Codex 這類 coding agent 用在寫程式上、用得更好」。這個系列<strong>不是</strong>這個角度，它站在「自行開發 AI Agent 的工程師」這一邊: 你要打造的可能是 Text-to-SQL Agent、知識庫 RAG、訪談 agent，場景五花八門，用途未必是 coding。ihower 認為 harness engineering 是一套更廣義的工程方法，適用於開發任何場景的 AI Agent，而不只是把 coding agent 用得更順的使用技巧。這點想先特別點出來。</div>
</div>

<p>要談 harness，得先弄清楚 agent 本身已經能做什麼。今天大家在用的 Claude Code、Codex 這類 Deep Agent，都內建了一組能力: 模型不只回覆文字，還能規劃工作、使用工具、保留外部狀態、讀寫檔案、呼叫子代理人，並在較長的任務中維持方向。這篇先把這些常見能力整理成六類，講清楚各自解決什麼問題、技術上怎麼辦到。但這些能力加總起來，仍然還不是 harness。先把這一層搞懂，後面幾篇才好接著談。</p>

<blockquote>
  <p>編按: 把 Deep Agent 拆成「六類」是本系列為了講解而做的分類，不是業界唯一講法。例如 LangChain 在 <a href="https://www.langchain.com/blog/deep-agents">Deep Agents</a> 一文就歸納成四個特徵: 詳細的 system prompt、規劃工具 (planning tool)、子代理 (subagents) 和檔案系統 (filesystem)。重點不在湊到剛好幾項，而在這組能力怎麼搭配。</p>
</blockquote>

<p>呼應上面的定位再提醒一句: 下面會用 Claude Code、Codex 當例子，因為它們是目前最成熟、最好觀察的 Deep Agent。但這六項能力並不是 coding agent 的專利。當你自己開發 Text-to-SQL Agent、RAG 或訪談 agent 時，同樣要面對「需不需要這項能力、該怎麼配置它」的選擇。看懂這六項，就是看懂你之後要自己組裝的那組元件。</p>

<p>要補一個重點: 下面這六項，是一個「通用」coding agent (要能應付五花八門的軟體任務) 才需要的完整清單。自行開發特定用途的 agent 時，不一定全部都用得上，按任務需求取捨就好。任務相對單純、不太需要事先規劃，就不必上 Plan &amp; Todos; 沒有編輯檔案、也沒有跨 session 記憶的需求，Filesystem 跟 sandbox 也可以省下來。把它當成一份可選配的能力清單，而不是每個 agent 都得全部裝上。</p>

<h2 id="先回顧-agent-就是-llm-在迴圈裡使用工具">先回顧: Agent 就是 LLM 在迴圈裡使用工具</h2>

<p>不管講得多複雜，今天所有 agent 的共同底層就一句話: 一個 LLM 根據目標決定步驟、呼叫工具、觀察結果，再決定下一步，這樣一圈一圈跑到任務完成。</p>

<p><img src="/assets/images/harness-engineering-1/agent-loop.jpg" alt="Agent 迴圈: Input → LLM 挑選工具 → Tools → 執行得到 Tool Result → 觀察結果回到 LLM，呼叫工具 N 次後輸出 Output" /></p>

<p>這個迴圈本身很簡單，Anthropic 自己也形容他們的 runtime 是一個「笨迴圈 (dumb loop)」: 智能全在模型裡，迴圈只負責管每一輪要做什麼。</p>

<p>過去一年大家熟悉的 agent 比較「淺」: 它能依據的就只有上下文視窗裡的內容，做 5 到 15 步的任務沒問題，但碰到要 500 步、要跨好幾天的任務就會失控，常見三種狀況: 工具輸出塞滿 context、把原本的指令擠出視窗;在一堆中間步驟的雜訊裡忘了原本要達成的目標;一旦走錯方向，也不會停下來回頭、換個做法重來，只能將錯就錯。Deep Agent (有人叫它 Agent 2.0) 的做法，就是把下面這六項能力直接內建進去，讓 agent 能應付更大更複雜的任務。技術上幾乎都是同一招: 用 Function Calling 定義一組工具丟給模型呼叫。一項一項看。</p>

<h2 id="能力--plan--todos把大任務拆開逐項打勾">能力 ①: Plan &amp; Todos，把大任務拆開、逐項打勾</h2>

<p>淺 agent 是用 chain-of-thought 隱式地規劃 (像是「我先做 X、再做 Y」)，但這些念頭混在一大堆中間步驟的雜訊裡，很容易就被忽略、忘掉。Deep Agent 改成用工具維護一份顯式的待辦清單，通常就是一份 markdown 格式的待辦清單。</p>

<p>它的好處有兩面: 對 agent 來說，每做完一步就回頭更新清單、把項目標成進行中或完成，就不會忘記還有什麼沒做;對使用者來說，進度完全透明，隨時看得到 agent 做到哪。如果某一步失敗了，它也不是盲目重試，而是更新清單、把這次失敗納入考量。</p>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: 透過 Function Calling 提供 <code>TaskCreate</code>／<code>TaskGet</code>／<code>TaskUpdate</code>／<code>TaskList</code> 幾個工具。有意思的是，LangChain 在《Deep Agents》一文指出: Claude Code 的 Todo 工具其實是個 no-op (沒有實際作用的空操作)，它什麼事都沒做，純粹是一種 context engineering 手段，用待辦清單讓 agent 不偏離當前任務。</div>

<h2 id="能力--filesystem--bash讓-agent-真的動手做事">能力 ②: Filesystem &amp; Bash，讓 agent 真的動手做事</h2>

<p>光會思考沒用，要能操作真實環境。Filesystem 讓 agent 讀檔、寫檔、改檔; Bash 讓它執行任意 shell 指令。有了這兩樣，agent 就能真的動手: 編譯程式、跑測試、操作 git、安裝套件、處理檔案。這裡有個常被忽略的重點: AI 不需要 GUI 視覺介面，只要有 CLI 就能把事情做完。</p>

<p>Filesystem 可以說是最基礎的一塊。它一次解鎖了好幾件事: 給 agent 一個工作區去讀資料和程式碼; 把塞不進 context 的東西先寫到檔案、需要時再讀回來; 把工作持久化，撐過單一 session; 多個 agent 之間還能透過共享檔案協作。再加上 git，就多了版本控制，能追蹤進度、回滾錯誤、開分支做實驗。</p>

<p>Bash 則是那個「通用工具」。預先配置好的工具只能覆蓋設計者想到的情況，但你不可能為每個動作都先寫好工具。給了 bash 跟程式執行能力，模型就能自己寫 code 當成臨時工具，遇到缺什麼能力就補什麼，不會被固定的工具集限制住。</p>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: 透過 Function Calling 提供一組工具，例如一個叫做 <code>run_bash</code> 的工具執行 CLI 指令，<code>read_file</code>／<code>write_file</code>／<code>edit_file</code> 讀寫檔案，<code>glob</code>／<code>grep</code> 搜尋檔案。實務上這些動作通常跑在 sandbox 裡，隔離執行、可以限縮可用指令和網路權限。</div>

<blockquote>
  <p>編按: sandbox 怎麼設計、有哪些隔離方式，本身就是一個大題目，可以參考 <a href="https://blog.aihao.tw/2026/04/20/agent-sandbox-landscape/">Agent Sandbox 沙箱架構: 論兩種設計模式與七種隔離方式</a>。</p>
</blockquote>

<h2 id="能力--sub-agent把耗-token-的工作交給子代理人">能力 ③: Sub-Agent，把耗 token 的工作交給子代理人</h2>

<p>複雜任務需要分工。淺 agent 在一個 prompt 裡什麼都自己來; Deep Agent 則用 Orchestrator → Sub-Agent 的模式。主 agent 把研究、查找、驗證這類耗 token 的工作派給子代理人，每個子代理人帶著自己獨立的 context 去跑，跑完只把整理好的結論回報主 agent。</p>

<p>這樣設計的關鍵價值在「context 隔離」。子代理人在自己的迴圈裡搜尋、出錯、重試，可能耗掉好幾萬個 token，但它最後只回傳一兩千 token 的精簡摘要，主線的 context 不會被那些中間過程塞滿。而且多個子代理人可以同時平行工作，加快整體進度。</p>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: 透過 Function Calling 提供一個叫做 <code>spawn_agent</code> 的工具，工具內部就是去呼叫另一個 agent 執行，跑完再把結論回傳。從主迴圈的視角看，這跟呼叫任何一個普通工具沒兩樣。</div>

<blockquote>
  <p>編按: 什麼時候該拆多代理人、什麼時候反而是反模式，業界已經有一些收斂的共識，可以參考 <a href="https://blog.aihao.tw/2026/05/19/multi-agent-anti-patterns-and-patterns/">Multi-Agent 架構再探: 三省六部反模式和業界收斂共識</a>。</p>
</blockquote>

<h2 id="能力--memory跨-session-記得你和你的專案">能力 ④: Memory，跨 session 記得你和你的專案</h2>

<p>模型本身只記得權重裡的東西，加上當前 context 視窗裡的內容。session 一結束，這次對話就忘光了。Memory 要解的就是這個: 分析對話、把重要資訊存起來，下次再開 agent 它還記得你。最常見的形式就是 Claude Code 的 <code class="language-plaintext highlighter-rouge">CLAUDE.md</code>、Codex 的 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> 這類專案記憶檔。</p>

<p>在不能改模型權重的前提下，「加知識」唯一的辦法就是把內容注入 context。所以做法是: agent 一啟動就預設載入 memory 檔; agent 後續更新了這個檔，harness 再把新版載進來。要講清楚的是，這是一種運作在 context 這一層、而不是模型權重那一層的外部記憶: 把專案規則、使用者偏好、過去決策、工具說明寫在檔案或資料庫裡，讓 agent 在需要時重新讀回。它不是模型權重的即時更新，而是一套把重要資訊記錄、壓縮、再重新注入上下文的機制。</p>

<p>這裡有個容易被忽略、但很關鍵的設計原則: agent 要把自己的記憶當成「提示 (hint)」，而不是事實。行動之前，要對照真實狀態再驗證一次。例如 Claude Code 的記憶其實分三層: 一個一律載入的輕量索引、需要時才拉進來的主題檔、以及只能透過搜尋存取的原始逐字記錄，避免一股腦把所有記憶塞進 context。</p>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: agent 啟動時預設載入 <code>AGENTS.md</code>，使用者也可以要求它更新這個檔。你還可以自行設計記憶行為: 要記什麼、存在哪些目錄檔案、什麼時候回憶哪些內容。</div>

<blockquote>
  <p>編按: 這個 memory 檔到底該寫什麼、不該寫什麼，可以參考 <a href="https://blog.aihao.tw/2026/05/03/agents-md-research-and-practices/">AGENTS.md / CLAUDE.md 該寫什麼、不該寫什麼?</a>。另外很多人以為做 agent 記憶一定要上向量資料庫，其實多數情況用不到，這點在 <a href="https://blog.aihao.tw/2026/04/28/agent-memory-no-vector/">Agent Memory 實作: 大多數記憶功能不需要用到向量檢索</a> 有完整討論。</p>
</blockquote>

<h2 id="能力--skills按需動態載入的技能-prompt">能力 ⑤: Skills，按需動態載入的技能 prompt</h2>

<p>一個 skill 是為特定任務準備的一包東西: prompt、工具配置、甚至程式碼。例如翻譯 skill、寫部落格 skill、處理 PDF 的 skill。重點在「按需載入」: 不把所有技能一次全塞進 context，而是用到哪個才載入哪個。要新增一個技能，寫一個 markdown 檔就行。</p>

<p>為什麼要這樣設計? 因為工具或 MCP server 一多，光是啟動時把它們的說明全部載入 context，就會在 agent 還沒開始做事前先拉低表現 (這也是後面系列會談到的 context rot 問題)。Skills 用的是「漸進式揭露 (progressive disclosure)」: 平常只讓模型看到一份精簡的技能列表，真正需要時才展開完整內容。</p>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: 預設只載入 skills 列表，每個 skill 只有一段簡短描述。當判斷需要某個 skill 時，agent 再用 skill 工具載入它的完整內容。</div>

<blockquote>
  <p>編按: skill 跟 MCP 是什麼關係、會不會取代 MCP，可以參考 <a href="https://blog.aihao.tw/2026/03/12/post-mcp-era-skills-vs-mcp/">後 MCP 時代: Skill 取代 MCP 嗎?</a>。想實際動手寫一個 skill，則可以看 <a href="https://blog.aihao.tw/2026/03/05/claude-skill-creator-v2/">Claude Skill Creator 新版解析: 用 AI 幫你寫 Skill、測 Skill、改 Skill</a>。</p>
</blockquote>

<h2 id="能力--更多工具接上更大的世界">能力 ⑥: 更多工具，接上更大的世界</h2>

<p>前面五項大多在 agent 自己的工作區內運作，第六項是往外接。這類嚴格說不算 Deep Agent 的必要條件，比較像常見 Deep Agent harness 會接上的工具介面，讓它從「會處理檔案」進一步變成「能跟外部世界互動的系統」。三個代表:</p>

<ul>
  <li><strong>MCP (Model Context Protocol)</strong>: 標準化的工具協議，讓 agent 能接上 Slack、Gmail、Notion，或你自建的 API。把「怎麼接外部服務」標準化，工具生態就能共用。</li>
  <li><strong>Browser</strong>: 瀏覽網頁、填表單、點按鈕，讓 agent 能上網做事、或用網頁來驗證自己的產出。</li>
  <li><strong>Computer Use</strong>: 更進一步，直接操作桌面 GUI，看螢幕截圖、移動滑鼠、敲鍵盤。</li>
</ul>

<div style="border-left:3px solid var(--accent);background:var(--surface);border-radius:0 8px 8px 0;padding:12px 16px;margin:18px 0;font-size:.92em;">🔧 <strong>技術上怎麼辦到</strong>: Browser 和 Computer Use 需要多模態模型: 讓 AI 看截圖判斷下一步該點哪裡。MCP 則是把工具的描述與呼叫方式標準化，讓同一套工具能被不同 agent 重複使用。</div>

<blockquote>
  <p>編按: 想看這六項能力更完整的拆解，可以參考 Philipp Schmid 的 <a href="https://www.philschmid.de/agents-2.0-deep-agents">Agents 2.0: From Shallow Loops to Deep Agents</a> 和 LangChain 的 <a href="https://www.langchain.com/blog/deep-agents">Deep Agents</a>。</p>
</blockquote>

<h2 id="光有這些能力還不夠">光有這些能力，還不夠</h2>

<p>六項講完，退一步看全貌。它們解決的其實是同一件事: 「<strong>能不能做</strong>」。能操作檔案、能執行程式、能拆任務、能記得事情、能接上更大的世界。這是必要的基礎能力，是 agent 動手做事的前提。</p>

<p>但「有能力」不等於「會用能力把事情做好」。到這裡為止，我們其實都還沒談到真正困難的那一半: 該怎麼運用這些能力，讓 agent 穩定地跑完一個動輒幾十、上百步的複雜任務。能力給了你前半，後半還空著:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 260px;border:1px solid #1a7f37;border-radius:8px;padding:16px 20px;background:#dafbe1;">
<div style="font-weight:700;color:#1a7f37;margin-bottom:8px;">六項能力給了你 ✓</div>
<div style="font-size:1.05em;font-weight:600;color:var(--text);">能不能做</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:6px;">能讀檔、能跑指令、能開子代理、能記憶、能用工具。</div>
</div>
<div style="flex:1 1 260px;border:1px solid #9a6700;border-radius:8px;padding:16px 20px;background:#fff8c5;">
<div style="font-weight:700;color:#9a6700;margin-bottom:8px;">但還沒回答 ✗</div>
<div style="font-size:1.05em;font-weight:600;color:var(--text);">做得對不對 · 做完了沒</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:6px;">這一步的產出對嗎? 整個任務到底做完了沒? 怎麼穩定地做好做完?</div>
</div>
</div>

<p>要把右邊這半補起來，光堆能力沒用。缺的不是更多工具，而是把這些能力接成一個可驗收、可修正、可持續運行的回饋系統: 每一步的產出有沒有被檢查、整個任務有沒有對著明確的完成條件驗收、走錯了能不能修正後再繼續跑。這套工程，就是這個系列要談的 harness。</p>

<p>下一篇進入正題: harness 到底是什麼?</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Coding" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 (本篇) 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (2): 核心: Agent 要的是回饋迴路,不是完美提示</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-2-what-is-harness-engineering/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (2): 核心: Agent 要的是回饋迴路,不是完美提示" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-2-what-is-harness-engineering</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-2-what-is-harness-engineering/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <strong style="color:var(--text);">核心: Agent 要的是回饋迴路，不是完美提示</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>上一篇把 Deep Agent 的六項內建能力一項一項拆完，結論是: 它們解決的全是「能不能做」，沒有一項回答「做得對不對、做完了沒」。這一篇進正題，把 Harness Engineering 這個詞講清楚: 它到底是什麼、不是什麼。這是整個系列的定位基準，後面幾篇談的具體做法，都會回到這裡。</p>

<p>先講結論: harness engineering 不是一份「裝了哪些功能」的清單。MCP、skills、sub-agent、hooks 這些都只是手段。真正的主軸是一件事: 怎麼讓 agent 在行動的迴圈裡被約束、被檢查、被修正，甚至越跑越好。</p>

<h2 id="做完了誰說了算">「做完了」誰說了算</h2>

<p>上過線的人應該都遇過這個場景: agent 很有自信地宣稱「已全部完成 ✅」，但東西是壞的。測試沒跑、需求漏了一半，你一指出問題，它立刻道歉，然後再犯一次。</p>

<p>這不是個案。<a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">Anthropic 整理長時間執行 agent 的失敗模式</a>，排在第一名的就是「過早宣告完成」(premature completion)。自我感覺良好就交差，幾乎是今天模型的通病。六項能力讓 agent 能做很多事，卻沒有東西告訴它「這樣到底對不對、夠不夠」。</p>

<p>這就帶到 <a href="https://x.com/aparnadhinak/status/2034160346840301706">Aparna Dhinakaran</a> 那句在社群被轉很多次的話:</p>

<blockquote>
  <p>「AI is not wrong, you just have not built the harness correctly.」
(AI 沒有錯，是你的 harness 沒搭好。)</p>
</blockquote>

<p>這句話的態度很重要: 把 agent 的每次出錯，當成 harness 的一個<strong>永久訊號</strong>，而不是只當成模型一次性的失誤。它漏跑了測試，那就讓「漏跑測試」這件事在系統層面再也不可能發生，而不是下次再提醒它一遍。</p>

<p>能這樣做的前提，是 agent 的失敗通常<strong>指認得出來</strong>: 你多半講得出它哪裡錯、該補哪一塊。<a href="https://addyosmani.com/blog/agent-harness-engineering/">Addy Osmani</a> 把它拆成一條條對照: agent 不知道某個慣例，就把慣例寫進 AGENTS.md;它跑了破壞性指令，就加一個 hook 擋掉;它在四十步的任務裡迷路，就拆成 planner 和 executor 兩個角色;它老是交出跑不過的程式碼，就把 typecheck 設成強制檢查，沒通過就不准收工。每個失敗都對得上一個具體修法，「把錯誤變成規則」才不只是一句口號。<a href="https://www.humanlayer.dev/blog/skill-issue-harness-engineering-for-coding-agents">HumanLayer</a> 把這件事講得更白: 這通常不是模型問題，是配置問題。</p>

<p>Mitchell Hashimoto 有一句話總結了這個心法，被反覆引用 (HumanLayer 把它掛在他名下，Addy 那篇也拿它當開場):「每當你發現 agent 犯了一個錯，你就花時間做一個工程上的解法，讓它再也不會犯同一個錯。」英文社群把這套心法叫 the ratchet (棘輪)。不過棘輪是雙向的: 你只在看到真實失敗時才加一條約束，也只在某天模型強到讓那條約束變多餘時，才把它拆掉。所以一份好的 AGENTS.md，每一行都該追得回一個具體出過的包。至於「約束會隨模型變強而退場」這半邊，是這個系列最後一篇要回來談的，這裡先按下。</p>

<h2 id="不是取代是一層層疊上去">不是取代，是一層層疊上去</h2>

<p>要定義 harness engineering，得先把它跟前面兩個熟悉的詞擺在一起。很多人會把 Prompt → Context → Harness 講成一條「演進」的路，好像後面的把前面的取代掉了。實務上不是這樣，它們高度重疊，只是每個詞出現的時候，社群正在試圖凸顯一個新的工程焦點。</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>核心問題</th>
      <th>典型技術</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Prompt Engineering</strong></td>
      <td>怎麼讓模型<strong>這一次</strong>回答得更好</td>
      <td>system prompt、few-shot、格式指令</td>
    </tr>
    <tr>
      <td><strong>Context Engineering</strong></td>
      <td>在 context window 的限制下，怎麼<strong>選擇、篩選</strong>該放進 context 的資訊</td>
      <td>RAG、memory、compaction、工具輸出卸載</td>
    </tr>
    <tr>
      <td><strong>Harness Engineering</strong></td>
      <td>怎麼讓 agent 在<strong>行動迴圈</strong>裡被約束、檢查、修正，甚至越跑越好</td>
      <td>前饋 guides、回饋 sensors、eval gates、hooks</td>
    </tr>
  </tbody>
</table>

<p>這三層是疊加，不是替換。Prompt 管單次呼叫怎麼把話說清楚;context 管模型當下該掌握哪些資訊、別被污染;harness 管 agent 一邊行動一邊怎麼收斂。HumanLayer 甚至直接把 harness engineering 視為 context engineering 的子集，差別只在它特別關注「用配置點來管理整個 agent 行動迴圈的 context」。所以這裡有個判斷標準: 凡是 harness 出現之前就已經存在的東西 (測試、linter、CI)，它們本來就在，不是 harness engineering 發明的;harness engineering 的新意，是把這些既有工具重新接成 agent 的回饋迴路。</p>

<blockquote>
  <p>編按: Context Engineering 這層不是本系列的主題，但它本身是個完整的題目。想補齊這塊的讀者，可以看 ihower 寫的 <a href="https://ihower.tw/blog/12817-context-engineering">什麼是 Context Engineering 上下文工程?</a>，裡面把寫入、選擇、壓縮、隔離 context 這四種做法整理得蠻清楚;更偏實作的話，ihower 在 WebConf Taiwan 2025 的演講 <a href="https://ihower.tw/blog/13501-practical-ai-agents">實戰 AI Agents 應用開發: TTFT 和 Prompt Caching</a> 也談了 Web Agent 的部署、可觀測性、Prompt Caching 與 Context Engineering 等實戰主題。</p>
</blockquote>

<h2 id="agent--model--harness然後呢">Agent = Model + Harness，然後呢?</h2>

<p>LangChain 在 <a href="https://www.langchain.com/blog/the-anatomy-of-an-agent-harness">《The Anatomy of an Agent Harness》</a> 裡給了一個被大量引用的定義:</p>

<blockquote>
  <p>「模型提供智能，harness 是讓那份智能變得有用的系統。」(The model contains the intelligence and the harness is the system that makes that intelligence useful.)</p>
</blockquote>

<p>配套口訣是: 「如果你不是模型，那你就是 harness。」(If you’re not the model, you’re the harness.) 模型以外的一切，system prompt、工具、編排邏輯、hooks，全算 harness。</p>

<p>這個定義乾淨，但 ihower 的吐槽是: 用「模型 + harness」來定義 agent，對理解 harness 幾乎沒幫助，等於把模型以外的東西全部塞進「harness」這個標籤，講了跟沒講一樣。它劃了一條邊界，卻沒告訴你 harness 內部該怎麼想、該往哪裡用力。我們需要的是更可操作的拆法，不是一句包山包海的話。</p>

<h2 id="那-harness-的基本策略是什麼-先-generate再-verify">那 harness 的基本策略是什麼? 先 generate，再 verify</h2>

<p>在拆解結構之前，先講大方向。如果問「harness 到底要讓 agent 建立什麼行為」，把這幾家的文章攤開看，反覆被當成核心策略明講的其實只有一件事: <strong>驗證 (verification)</strong>。</p>

<p><a href="https://www.langchain.com/blog/improving-deep-agents-with-harness-engineering">LangChain 的第二篇實戰文</a> 講得最直白。他們發現最常見的失敗長這樣: agent 寫完一個解法，回頭重讀自己的程式碼，確認「看起來沒問題」，然後就停了。這篇的結論是: 「自我驗證 (self-verification) 是最有效的槓桿」，而且「今天的模型是很強的自我改進機器，但它們沒有自發進入『寫完就驗證』這個迴圈的傾向」。換句話說，模型有能力自我修正，但你不逼它，它不會主動做。</p>

<p>那這套「先做、再驗」的策略，落到具體流程上長什麼樣? 常見的形狀是 <strong>規劃 → 實作 → 驗證 → 修 (plan → implement → verify → fix)</strong>:</p>

<ul>
  <li>LangChain 直接把這四步寫進 system prompt: Planning &amp; Discovery → Build → Verify → Fix。</li>
  <li>Anthropic 則主張把它拆給不同 agent: planner / generator / evaluator，讓「生成」跟「評估」由不同 agent 來做，比同一個 agent 自評更可靠。</li>
  <li>walkinglabs 的課程把它寫成一個 session 生命週期: START → SELECT → EXECUTE (內含 implement → verify → fix) → WRAP UP。</li>
</ul>

<p>這套流程比較像是「驗證」這個核心主張的自然延伸，是小編從多份資料裡歸納出的共同形狀，不是哪一家冠名的策略。資料真正反覆強調的，上游是前面講的 ratchet 心法，下游是 verify 這一段。</p>

<p>策略講完，真正有意思的工程問題才浮現: 你怎麼確保 verify「真的會發生」? 寫在 prompt 裡求模型自律，跟用程式逼它非做不可，是兩種完全不同強度的做法。這就需要一組座標把它們分開。</p>

<h2 id="兩個軸把-harness-攤平">兩個軸，把 harness 攤平</h2>

<p>Thoughtworks 的 Birgitta Böckeler 在 <a href="https://martinfowler.com/articles/harness-engineering.html">Harness engineering for coding agent users</a> 提供了小編覺得最好用的一組座標。她用兩個軸把 harness 攤平: 一個是<strong>方向</strong> (行動前 vs 行動後)，一個是<strong>執行型態</strong> (確定性的程式 vs 推論的 LLM)。</p>

<ul>
  <li><strong>方向</strong>: 前饋 (feedforward) 的「引導器 guides」，在 agent 動手<strong>之前</strong>先引導它，提高一次做對的機率;回饋 (feedback) 的「感測器 sensors」，在 agent 動手<strong>之後</strong>觀察結果、逼它自我修正。</li>
  <li><strong>執行型態</strong>: 運算式 (computational) 是確定性的、毫秒級、結果可靠;推論式 (inferential) 是用 LLM 做語意判斷，比較慢、比較貴、也比較不穩。</li>
</ul>

<p>兩軸交叉成一個 2×2，每一格都裝得下具體技術:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>運算式 Computational (確定性程式)</th>
      <th>推論式 Inferential (LLM 生成)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>前饋 Guides</strong> (行動前引導)</td>
      <td>LSP、結構化改寫 (codemod／ast-grep)、機器可讀的架構約束</td>
      <td>AGENTS.md、Skills、bootstrap 指示、how-to 文件</td>
    </tr>
    <tr>
      <td><strong>回饋 Sensors</strong> (行動後修正)</td>
      <td>測試、linter、type checker、靜態分析、pre-commit hook</td>
      <td>AI code review、LLM as Judge、review skills</td>
    </tr>
  </tbody>
</table>

<p>要補一句: Thoughtworks 原文整個都是站在 coding agent 的場景講的，ihower 這裡把它擴充到任意一種自建 agent 應用。這個框架本身跟 coding 無關，一個金融 data agent、一個 RAG 問答、一個訪談 agent，同樣可以問自己「我的前饋引導在哪、回饋感測在哪、哪些用程式判定就好、哪些非得用 LLM 不可」。</p>

<p>Böckeler 還具體談了這些 guides 跟 sensors「該擺在開發流程的哪個位置」: 快又便宜的 (linter、快速測試、基本 review) 應該盡量往左移，在 commit 前就跑;貴的 (mutation testing、需要看大局的深度 review) 放到整合後的 pipeline;另外還有一類「持續漂移感測」，像死碼偵測、測試覆蓋率品質、相依性掃描，是掛在整個 codebase 上長期跑的，不綁在單次改動上。這套「把品質檢查盡量往左」的思路，本來就是持續整合的老智慧，只是現在多了一種推論式的感測器可以擺進去。</p>

<p>回到那個強度問題，答案就藏在這張表的「上下兩列」: verify 寫在 AGENTS.md 裡求模型照做，屬於上面的前饋，是軟性的、機率性的;verify 用 hook 強制跑、不過不准收工，屬於下面的回饋，是硬性的、確定性的。同一個「要驗證」的願望，可以同時散落在不同格子裡，強度天差地別。</p>

<h2 id="好的-harness-做兩件事">好的 harness 做兩件事</h2>

<p>把 2×2 濃縮成一句話: 好的 harness 同時做兩件事，提高「一次做對」的機率，並且在出錯時自我修正。</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 260px;border:1px solid var(--border);border-radius:8px;padding:16px 20px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:8px;">🧭 Guides 前饋</div>
<div style="font-size:1.05em;font-weight:600;color:var(--text);">提高「一次做對」的機率</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:6px;">文件、skills、範例、架構約束。軟性引導，做不做最後還是 agent 決定。</div>
</div>
<div style="flex:1 1 260px;border:1px solid var(--accent);border-radius:8px;padding:16px 20px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📡 Sensors 回饋</div>
<div style="font-size:1.05em;font-weight:600;color:var(--text);">行動之後感測並逼它修正</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:6px;">測試、linter、judge、review。用程式強制執行，不靠模型自覺。</div>
</div>
</div>

<h3 id="前饋-guides-一切從你的指令開始">前饋 Guides: 一切從你的指令開始</h3>

<p>前饋就是用自然語言事先告訴 agent 該怎麼做。自行開發 agent 時，這一步就是你寫的 system／developer prompt，你得在裡面把「好結果長什麼樣」講清楚。至於要寫到多明確、多強硬，不妨看看成熟的 coding agent 怎麼做，它們的 system prompt 直接把「要驗證」寫成了 MUST 等級的指示。</p>

<p>Claude Code 的 system prompt 裡有這麼一句:</p>

<blockquote>
  <p>「非常重要: 當你完成一個任務，你<strong>必須</strong>執行 lint 和 typecheck 指令 (例如 npm run lint、npm run typecheck、ruff 等)，如果有提供這些指令的話，以確保你的程式碼正確。」
(VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands … to ensure your code is correct.)</p>
</blockquote>

<p>它甚至接著交代: 如果找不到正確的檢查指令，要主動問使用者，問到了還要建議把它寫進 CLAUDE.md，這樣下次就知道要跑。Codex 那邊要求得更嚴格:</p>

<blockquote>
  <p>「如果 AGENTS.md 裡含有用來驗證你工作成果的可程式化檢查，你<strong>必須</strong>全部執行，並盡最大努力驗證它們通過。這條規定即使是看起來很簡單的改動，例如改文件，也一樣適用。」
(If the AGENTS.md includes programmatic checks to verify your work, you MUST run all of them … This applies even for changes that appear simple, such as documentation.)</p>
</blockquote>

<p>回到你自己的 agent。如果是自行開發，這些前饋就寫在你的 system／developer prompt 裡;如果是在現成的 coding agent 上工作，對應的就是那份 AGENTS.md / CLAUDE.md，它每一輪都會被注入 system prompt，適合放專案慣例、領域知識、輸出格式、哪些事不要做。想看這類記憶檔該寫什麼、不該寫什麼，可以參考 <a href="https://blog.aihao.tw/2026/05/03/agents-md-research-and-practices/">AGENTS.md / CLAUDE.md 該寫什麼、不該寫什麼?</a>。如果想看一份完整的前饋 harness 長什麼樣，walkinglabs 的 <a href="https://github.com/walkinglabs/learn-harness-engineering">learn-harness-engineering</a> 把它拆成五個子系統: 指示 (instructions)、狀態 (state)、驗證 (verification)、範圍 (scope)、生命週期 (lifecycle)，它有一句總結很到位: 「模型決定要寫什麼程式碼，harness 決定它何時、在哪、怎麼寫。」敏捷三叔公那篇 <a href="https://agile3uncles.com/2026/04/15/claude-code-harness-web-to-do-list/">用 Claude Code 從零打造 To-Do List 的最小可行 harness</a>，則是手把手示範怎麼用一份 CLAUDE.md，把「寫 → 測 → 修」這個回饋迴路的規則寫進去。</p>

<p>但前饋有個上限: <strong>它終究只是軟性引導，是機率性的。</strong> 你在 AGENTS.md 寫一百遍「一定要驗證」，模型還是有可能跳過。它就是個機率裝置，不是保證。</p>

<h3 id="回饋-sensors-把-verify-變成強制執行的檢查">回饋 Sensors: 把 verify 變成強制執行的檢查</h3>

<p>要讓「驗證」從一句願望變成強制執行，得靠回饋這一列的程式化感測器。</p>

<p>以 coding 場景來說，具體做法就是 hook、pre-commit、CI gate 這些程式碼層級的強制點:</p>

<ul>
  <li>HumanLayer 用一個 Stop hook，在 agent 想收工時跑 typecheck 和 formatter，只要失敗就回傳 exit code 2，逼 agent 繼續修到通過為止。成功時完全靜默，失敗時才把錯誤訊息丟回去。</li>
  <li>LangChain 用一個 PreCompletionChecklistMiddleware，在 agent 退出前攔截它，強制它對著任務規格再跑一輪驗證才准走。</li>
  <li>walkinglabs 則把驗證收斂成一個 <code class="language-plaintext highlighter-rouge">harness-verify</code> 腳本 (lint + type-check + test + build 一次跑完)，設成強制檢查點。</li>
</ul>

<p>缺了這些程式化的強制檢查，agent 永遠只能靠自我感覺良好交差，你也只能用肉眼一行行盯。</p>

<p>不過光靠回饋也有它的問題: 只有回饋、沒有前饋，常會看到 agent 寫程式碼 → 測試失敗 → 改 → 再失敗，繞好幾圈還是超時。這通常不是回饋不夠，而是 agent 從頭就不知道「對的結果」長什麼樣。此時該加強的是前饋 (架構文件、慣例、Skills)，前饋負責一開始走對方向，回饋負責把關，兩個都要。</p>

<p>而且回饋 sensors 不只 coding 場景用得到。這個系列會把場景擴大到非 coding 的自建 agent，而 sensors 正是本場的重點: 後面會細談回饋的四個時機點，以及每個時機能用哪些方式把回饋接回迴圈，不同做法在強制性、成本、可靠度上各有優缺點。</p>

<h2 id="核心其實是控制論">核心其實是控制論</h2>

<p>為什麼回饋這麼關鍵? ihower 覺得控制論 (cybernetics) 拿來比喻 harness 蠻貼切的。</p>

<p>George (<a href="https://x.com/odysseus0z/status/2030416758138634583">@odysseus0z</a>) 寫過一個對照很到位的例子。1780 年代瓦特的離心調速器發明之前，蒸汽機旁邊得站一個工人，用手調節汽門;發明之後，飛球機構自己感測轉速、自動調節汽門。工人沒有消失，他的工作變了: 從顧著轉閥門，變成設計那個調速器。</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>感測</th>
      <th>致動</th>
      <th>工程師的工作變成</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>瓦特離心調速器</strong> (1780s)</td>
      <td>飛球感測轉速</td>
      <td>自動調節汽門</td>
      <td>從顧著轉閥門，變成<strong>設計調速器</strong></td>
    </tr>
    <tr>
      <td><strong>Harness Engineering</strong></td>
      <td>測試、judge、review</td>
      <td>agent 自我修正、重試</td>
      <td>從盯著 agent 一步步做，變成<strong>設計環境與回饋迴圈</strong></td>
    </tr>
  </tbody>
</table>

<p>同樣的模式重演: 有人造出夠強的「感測器 + 致動器」，把控制迴圈在那一層接起來，人就從操作者變成設計者: 不再親手轉閥門，而是去設計整個迴圈。</p>

<p>反過來說，一個沒有感測器的 agent 就是一個<strong>無回饋迴路 (open loop)</strong>: 指令發出去、結果聽天由命，品質全靠運氣和模型當天的狀態。這也是為什麼這個系列想反覆強調的一句話是: agent 需要的是回饋迴路，不是完美的提示 (Agents need feedback loops, not perfect prompts)。你再怎麼改 prompt，沒有迴路它就收斂不了。</p>

<h2 id="harness-是影響表現的一大關鍵">harness 是影響表現的一大關鍵</h2>

<p>harness 不是模型的附屬品，它本身就是決定 agent 表現的一大關鍵。同一個模型，換一套 harness，結果可以差很多，這點在公開 benchmark 上看得很清楚。</p>

<p>LangChain 拿 Terminal Bench 2.0 做過示範: 固定 <code class="language-plaintext highlighter-rouge">gpt-5.2-codex</code> 不換模型，只調 harness (system prompt、middleware、推理預算的分配)，分數就從 52.8% 拉到 66.5%。另一個常被引用的對照是 Opus 4.6: 同一個模型在不同 harness 下，差距已經大到足以改變它在排行榜上的排名。</p>

<h2 id="本場的工作定義">本場的工作定義</h2>

<p>把前面收攏成一句可操作的定義，作為整個系列的錨點:</p>

<blockquote>
  <p><strong>Harness Engineering: 讓 agent 根據目標，持續、正確地動作的工程。</strong></p>
</blockquote>

<p>核心材料是「回饋訊號」: 你得有東西能判斷<strong>做得對不對、做完了沒</strong>。沒有這個訊號，前面講的一切都只是無回饋迴路。</p>

<h2 id="回饋的四個時機">回饋的四個時機</h2>

<p>那回饋到底「在哪裡」接回迴圈? 這是整個系列的骨架。把 agent 的迴圈由內而外攤開，有四個可以下手的時機，越往外越貴。其中三個 (①③④) 是 harness 自動觸發的，另一個 (②) 是兩次 model request 之間的注入點，可以由使用者主動 steer，也可以由程式注入:</p>

<table>
  <thead>
    <tr>
      <th>時機</th>
      <th>多久觸發一次</th>
      <th>成本</th>
      <th>修正粒度</th>
      <th>對應的 hook</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>① 工具執行內</strong></td>
      <td>每次 tool call</td>
      <td>毫秒，最便宜</td>
      <td>單一動作</td>
      <td>Pre／PostToolUse</td>
    </tr>
    <tr>
      <td><strong>② request 之間注入</strong></td>
      <td>使用者或程式想注入時</td>
      <td>趨近零</td>
      <td>當前這一輪的方向</td>
      <td>無專屬 hook</td>
    </tr>
    <tr>
      <td><strong>③ 單輪結束</strong></td>
      <td>每一輪</td>
      <td>秒級</td>
      <td>整輪的產出</td>
      <td>Stop hook</td>
    </tr>
    <tr>
      <td><strong>④ 外層 Loop</strong></td>
      <td>每個 session</td>
      <td>分鐘到小時</td>
      <td>整個任務</td>
      <td>排程／外迴圈</td>
    </tr>
  </tbody>
</table>

<p>畫成圖更清楚: 這些時機由內而外是巢狀的，而一輪 (Turn) 本身就包含好幾次「模型 request → tool call」，①②③④ 分別發生在這幾個邊界上:</p>

<svg viewBox="0 0 720 342" style="width:100%;max-width:720px;height:auto;margin:24px 0;font-family:-apple-system,'PingFang TC','Noto Sans TC',Helvetica,Arial,sans-serif;" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="he2arr" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--text-secondary)" /></marker>
<marker id="he2arrA" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--accent)" /></marker>
</defs>
<rect x="10" y="40" width="700" height="296" rx="14" style="fill:var(--bg);stroke:var(--text-secondary);stroke-width:1.5;stroke-dasharray:6 5" />
<rect x="26" y="27" width="332" height="27" rx="13.5" style="fill:var(--bg);stroke:var(--text-secondary);stroke-width:1.5" />
<text x="40" y="45" style="fill:var(--text);font-size:13px;font-weight:700">④ 外層 Loop</text>
<text x="118" y="45" style="fill:var(--text-secondary);font-size:11.5px">· 每個 session · 分鐘~小時 · 修正整個任務</text>
<rect x="34" y="78" width="652" height="154" rx="12" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5" />
<rect x="50" y="65" width="170" height="26" rx="13" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5" />
<text x="63" y="82" style="fill:var(--accent);font-size:12.5px;font-weight:700">③ 一輪 Turn</text>
<text x="138" y="82" style="fill:var(--text-secondary);font-size:11px">· 秒級</text>
<text x="52" y="111" style="fill:var(--text-secondary);font-size:11px">使用者輸入</text>
<line x1="52" y1="117" x2="52" y2="138" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#he2arr)" />
<rect x="48" y="138" width="86" height="40" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3" />
<text x="91" y="155" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型</text>
<text x="91" y="170" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">request 1</text>
<line x1="134" y1="158" x2="168" y2="158" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#he2arr)" />
<rect x="170" y="138" width="86" height="40" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="213" y="155" text-anchor="middle" style="fill:var(--accent);font-size:12px;font-weight:600">🔧 tool calls</text>
<text x="213" y="170" text-anchor="middle" style="fill:var(--accent);font-size:10px;font-weight:700">①</text>
<line x1="256" y1="158" x2="290" y2="158" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#he2arr)" />
<rect x="292" y="138" width="86" height="40" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3" />
<text x="335" y="155" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型</text>
<text x="335" y="170" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">request 2</text>
<line x1="378" y1="158" x2="412" y2="158" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#he2arr)" />
<rect x="414" y="138" width="86" height="40" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="457" y="155" text-anchor="middle" style="fill:var(--accent);font-size:12px;font-weight:600">🔧 tool calls</text>
<text x="457" y="170" text-anchor="middle" style="fill:var(--accent);font-size:10px;font-weight:700">①</text>
<line x1="500" y1="158" x2="534" y2="158" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#he2arr)" />
<rect x="536" y="138" width="92" height="40" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3" />
<text x="582" y="155" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型</text>
<text x="582" y="170" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">輸出答案</text>
<line x1="628" y1="158" x2="648" y2="158" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#he2arrA)" />
<rect x="650" y="134" width="22" height="48" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.6" />
<text x="661" y="164" text-anchor="middle" style="fill:var(--accent);font-size:14px;font-weight:800">③</text>
<rect x="206" y="95" width="334" height="22" rx="11" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.2" />
<text x="373" y="110" text-anchor="middle" style="fill:var(--accent);font-size:10.5px;font-weight:700">② request 之間注入 (人 steer 或程式)</text>
<path d="M 284 117 L 284 150" style="fill:none;stroke:var(--accent);stroke-width:1.2;stroke-dasharray:3 2" marker-end="url(#he2arrA)" />
<path d="M 518 117 L 518 150" style="fill:none;stroke:var(--accent);stroke-width:1.2;stroke-dasharray:3 2" marker-end="url(#he2arrA)" />
<text x="320" y="197" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">① 工具執行內 · Pre／PostToolUse · 毫秒級 · 修正單一動作</text>
<path d="M 650 182 L 650 212 Q 650 218 644 218 L 99 218 Q 91 218 91 212 L 91 180" style="fill:none;stroke:var(--accent);stroke-width:1.3;stroke-dasharray:4 3" marker-end="url(#he2arrA)" />
<rect x="252" y="208" width="216" height="19" rx="9.5" style="fill:var(--surface);stroke:var(--accent);stroke-width:1" />
<text x="360" y="221" text-anchor="middle" style="fill:var(--accent);font-size:10.5px">Stop hook 沒過 → 同份 context 再跑一輪</text>
<text x="360" y="254" text-anchor="middle" style="fill:var(--text);font-size:11.5px">一輪 (Turn) = 多次「模型 request → tool call」反覆，直到模型輸出答案</text>
<path d="M 672 182 L 672 302 Q 672 310 664 310 L 42 310 Q 30 310 30 300 L 30 168 Q 30 158 40 158 L 46 158" style="fill:none;stroke:var(--text-secondary);stroke-width:1.6" marker-end="url(#he2arr)" />
<rect x="150" y="300" width="424" height="20" rx="10" style="fill:var(--bg);stroke:var(--text-secondary);stroke-width:1.3" />
<text x="362" y="314" text-anchor="middle" style="fill:var(--text);font-size:11px;font-weight:600">④ 通過後這個 session 收掉，換上全新 context 再跑一圈</text>
</svg>

<p>這四個時機，各自配上前面那兩種感測器 (運算式 vs 推論式)，或②那種即時注入，就構成這個系列接下來四篇的主體:</p>

<ul>
  <li><strong>時機①</strong> (下一篇): 在工具裡把最小的迴圈接起來。執行前驗證、執行後檢查修復、回傳值夾帶導引。最便宜，幾乎不會增加主迴圈的負擔。</li>
  <li><strong>時機②</strong>: 兩次 model request 之間的注入點。agent 還沒輸出答案、還在迴圈裡時，使用者可以中途 steer 或 interrupt，程式也能把背景工具結果、外部事件注入進來。</li>
  <li><strong>時機③</strong>: 單輪結束時的驗收。不能因為「模型覺得做完了」就算數，要對著一個可驗證的停止條件去檢查。</li>
  <li><strong>時機④</strong>: 外層 loop。當任務大到一個 context 裝不下，用外迴圈把任務一段段交給全新的 agent。</li>
</ul>

<p>這四層由內而外，就是接下來四篇的主線。</p>

<p>下一篇先從最小、最便宜的那一層開始: 怎麼在一次 tool call 裡，就把回饋接回迴圈。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Context Engineering" /><category term="Eval" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 (本篇) 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (3): 回饋時機一: 工具回傳值, 是寫給 agent 的回饋</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-3-tool-execution-feedback/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (3): 回饋時機一: 工具回傳值, 是寫給 agent 的回饋" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-3-tool-execution-feedback</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-3-tool-execution-feedback/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <strong style="color:var(--text);">回饋時機一: 工具回傳值，是寫給 agent 的回饋</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>上一篇整理出回饋的四個時機點，從這篇開始，由內而外一層一層做。先從最內、最便宜的那一層著手: 在一次 tool call 裡，就把迴圈閉合掉。</p>

<p>這一層的好處是: 它就發生在 tool call 內部，對主迴圈幾乎沒有額外負擔，卻是修正成本最低、頻率最高的回饋點。一個失誤在這裡就被擋下，不會擴散到後面整輪、甚至整個任務，變成更大的代價。</p>

<h2 id="tool-call-裡的三段式">Tool Call 裡的三段式</h2>

<p>一次工具呼叫，其實有三個可以介入的位置: 執行前、執行後、回傳時。</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">執行前: 驗證輸入</div>
<div style="font-size:.9em;color:var(--text-secondary);">用確定性檢查擋掉危險或無效的呼叫，不讓它真的送出去。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">執行後: 檢查結果</div>
<div style="font-size:.9em;color:var(--text-secondary);">對拿回來的結果做品質檢查，必要時就地修復或重試。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">回傳值: 夾帶指引</div>
<div style="font-size:.9em;color:var(--text-secondary);">tool response 不只回資料，還夾帶指示與 metadata，引導 agent 下一步。</div>
</div>
</div>

<p>前兩段比較直覺，真正容易被忽略、也是這篇最想講清楚的，是第三段。寫程式的人很習慣把 function 的回傳值當成「資料」: 查到什麼就回什麼，出錯就丟出一個 exception。但在 agent 的場景裡，tool response 不只是資料，它同時也是一段 prompt: 你可以在裡面夾帶指示、metadata、甚至直接告訴 agent「下一步該怎麼做」。</p>

<p>這裡有個關鍵的觀念要翻轉: <strong>工具輸出不是寫程式的 function output，它是你寫給 agent 看的回饋</strong>。下面幾個案例，從純確定性的檢查，一路走到語意 Judge，由淺入深把這三段式逐一展開。</p>

<h2 id="案例-a-text-to-sql-agent-的-sql-驗證">案例 A: Text-to-SQL Agent 的 SQL 驗證</h2>

<p>先看一個最適合用「執行前驗證」的場景。</p>

<p>使用者發問，agent 看著資料庫 schema 自己生成 SQL，再用一個 <code class="language-plaintext highlighter-rouge">execute_sql(sql_query)</code> 工具去查。問題是，LLM 生成的 SQL 帶著一堆機率性的風險:</p>

<div style="border:1px solid #cf222e;border-radius:8px;padding:14px 18px;margin:18px 0;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:8px;">LLM 生成 SQL 的風險</div>
<ul style="margin:0;padding-left:20px;font-size:.92em;color:var(--text);">
<li>查詢白名單以外的資料表</li>
<li>全表掃描、忘記分頁 (沒加 LIMIT)</li>
<li>瞎掰一個不存在的欄位</li>
<li>生成 SELECT 以外的危險語法</li>
</ul>
</div>

<p>這些風險，你沒辦法靠 prompt「請不要這樣做」來解決。前一篇講過的前饋，在這個場景就是: 你會在 system prompt 裡寫好每張 table 的 schema、每張 table 是做什麼的、哪一類問題該查哪張 table，引導模型一開始就往對的方向生成 SQL。但前饋終究是機率性的引導，寫得再清楚，擋不住模型偶爾把欄位記錯、或忘記加 LIMIT。要擋，得在工具內部用一道確定性的關卡。</p>

<p>做法是把 SQL parse 成語法樹 (AST)，在送進資料庫之前先檢查、再改寫。Python 生態可以用 <a href="https://sqlglot.com/sqlglot.html">SQLGlot</a> 這個函式庫:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">sqlglot</span>
<span class="kn">from</span> <span class="n">sqlglot</span> <span class="kn">import</span> <span class="n">exp</span>

<span class="n">tree</span> <span class="o">=</span> <span class="n">sqlglot</span><span class="p">.</span><span class="nf">parse_one</span><span class="p">(</span><span class="n">sql_query</span><span class="p">,</span> <span class="n">dialect</span><span class="o">=</span><span class="sh">"</span><span class="s">postgres</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># 1. 只允許 SELECT 查詢
</span><span class="k">if</span> <span class="ow">not</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">exp</span><span class="p">.</span><span class="n">Select</span><span class="p">):</span>
    <span class="k">return</span> <span class="sh">"</span><span class="s">只允許 SELECT 查詢</span><span class="sh">"</span>

<span class="c1"># 2. 資料表白名單
</span><span class="k">for</span> <span class="n">table</span> <span class="ow">in</span> <span class="n">tree</span><span class="p">.</span><span class="nf">find_all</span><span class="p">(</span><span class="n">exp</span><span class="p">.</span><span class="n">Table</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">table</span><span class="p">.</span><span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">ALLOWED_TABLES</span><span class="p">:</span>
        <span class="k">return</span> <span class="sa">f</span><span class="sh">"</span><span class="s">資料表 </span><span class="si">{</span><span class="n">table</span><span class="p">.</span><span class="n">name</span><span class="si">}</span><span class="s"> 不在允許範圍,可用的有: </span><span class="si">{</span><span class="n">ALLOWED_TABLES</span><span class="si">}</span><span class="sh">"</span>

<span class="c1"># 3. 沒有 LIMIT 就補一個安全上限; 已有更嚴格的限制就保留,不覆蓋
</span><span class="n">limit</span> <span class="o">=</span> <span class="n">tree</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">"</span><span class="s">limit</span><span class="sh">"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">limit</span> <span class="ow">is</span> <span class="bp">None</span> <span class="ow">or</span> <span class="nf">int</span><span class="p">(</span><span class="n">limit</span><span class="p">.</span><span class="n">expression</span><span class="p">.</span><span class="n">name</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">200</span><span class="p">:</span>
    <span class="n">tree</span> <span class="o">=</span> <span class="n">tree</span><span class="p">.</span><span class="nf">limit</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span>
<span class="c1"># 4. 注入這個使用者該有的權限條件 (例如 WHERE tenant_id = ...)
</span><span class="n">safe_sql</span> <span class="o">=</span> <span class="n">tree</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="n">dialect</span><span class="o">=</span><span class="sh">"</span><span class="s">postgres</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>注意這道關卡有兩種動作: 一種是擋下來 (不是 SELECT、查到白名單外的表就直接回傳錯誤)，另一種是補強 (沒加 LIMIT 就補一個安全上限，但若原本就有更嚴格的 LIMIT 就保留、不覆蓋; 再注入這個使用者該有的權限條件)。這裡的分寸是: 補強只做「擋不住會出事」的最小改寫，不是假設任何 SQL 都能安全地自動改。它純粹靠程式運算: 不呼叫 LLM、零延遲、結果完全可重現。能用程式確定判斷的事，就不要請模型來猜。</p>

<h2 id="失敗回饋-同樣的錯誤訊息寫得好不好差很多">失敗回饋: 同樣的錯誤，訊息寫得好不好差很多</h2>

<p>驗證沒過的時候，你回傳什麼，直接決定 agent 自我修正的速度。這就回到剛剛講的「回傳值也是 prompt」。</p>

<p>對比一下同一個錯誤的兩種寫法:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:22px 0;">
<div style="flex:1 1 280px;border:1px solid #cf222e;border-radius:8px;padding:16px 18px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:8px;">❌ 爛的錯誤訊息</div>
<pre style="margin:0;white-space:pre-wrap;font-size:.85em;color:var(--text);">ERROR: column "revenue_growth"
does not exist</pre>
<div style="font-size:.88em;color:var(--text-secondary);margin-top:10px;">agent 只能瞎猜重試，常常越改越歪。</div>
</div>
<div style="flex:1 1 280px;border:1px solid #1a7f37;border-radius:8px;padding:16px 18px;background:#dafbe1;">
<div style="font-weight:700;color:#1a7f37;margin-bottom:8px;">✅ 好的錯誤訊息</div>
<pre style="margin:0;white-space:pre-wrap;font-size:.85em;color:var(--text);">欄位 revenue_growth 不存在。
financial_metrics 可用欄位:
revenue, revenue_yoy, gross_margin…
年增率請改用 revenue_yoy。</pre>
<div style="font-size:.88em;color:var(--text-secondary);margin-top:10px;">下一步直接被導正。</div>
</div>
</div>

<p>爛的錯誤訊息把資料庫原始的訊息原封不動丟回去，那是寫給機器看的; agent 拿到只能再猜一次欄位名，猜錯再來一輪。好的錯誤訊息是寫給 agent 看的: 它附上這張表實際有哪些欄位、還直接點出「年增率你要的應該是 revenue_yoy」。下一步直接被導正，不用反覆試錯。</p>

<p>這件事的重點是: <strong>每個失敗都是你引導模型的免費機會，大多數人都浪費掉了。</strong> 錯誤訊息要寫給 agent 看、要可行動，不是只丟一個 error code。不只 SQL，延伸到別的情境也一樣，例如查詢成功但回傳 0 筆，這不是錯誤，但對 agent 是個該被提醒的訊號: 你可以回「查詢成功但 0 筆，可能是公司名稱的字面值對不上 (資料庫存的是全名)，或時間區間超出資料範圍 (本表只有 2015 到 2025)」，免得它把空結果當成「答案就是沒有」直接回給使用者。</p>

<h2 id="連成功都要回傳完整狀態不是只回成功旗標">連「成功」都要回傳完整狀態，不是只回成功旗標</h2>

<p>上面那個 0 筆的例子其實點到一件更通用的事: 需要設計的不只有失敗，<strong>成功一樣要設計</strong>。寫程式的人很習慣讓工具成功時就回一個 <code class="language-plaintext highlighter-rouge">{"success": true}</code> 了事，但對 agent 來說，一個光禿禿的成功旗標 (flag) 也可能是「假完成」的訊號。</p>

<p>回到 Text-to-SQL 的場景: 假設 agent 跑的是一個寫入查詢 (UPDATE／DELETE)，工具若只回 <code class="language-plaintext highlighter-rouge">{"success": true}</code>,agent 根本看不出來這次到底動了 5 筆還是 5 萬筆。一個你以為只會更新一位使用者的操作，可能因為 WHERE 條件寫太鬆而掃過整張表，但從一個光禿禿的成功旗標完全看不出來，agent 只會當作沒事繼續。(coding agent 也是同個毛病: 工具只回「File updated」，看不出是精準地只改了該改的 3 行，還是順手把 200 行無關的程式碼重排了一遍。)</p>

<p>所以工具成功時，回傳值要給的是 <strong>完整狀態 (state)，而不只是一個成功與否的旗標 (status)</strong>: 影響了幾筆資料、動了哪些欄位或哪些區塊、有沒有碰到不該碰的、目前進行到什麼狀態。把規模和範圍量化回來 (改了幾行、影響幾筆、花了多少 token),agent 才有依據判斷「這次的結果到底合不合理」，而不是看到 true 就繼續做下去。一句話: 錯誤要設計，成功也要設計。</p>

<h2 id="案例-b-文件摘要-agent-的-grader寫死的固定訊息也是一種引導">案例 B: 文件摘要 agent 的 grader，寫死的固定訊息也是一種引導</h2>

<p>案例 A 的好錯誤訊息是手寫的。你可能會想: 真實系統難道要一條一條把失敗訊息寫死嗎? 看一個把這件事系統化的案例，OpenAI Cookbook 的 <a href="https://developers.openai.com/cookbook/examples/partners/self_evolving_agents/autonomous_agent_retraining">Self-Evolving Agents</a> (Bain 與 OpenAI 合作)，場景是製藥法規文件的摘要。</p>

<p>它跑的是一個品質檢查迴圈: 摘要 agent 產出摘要 → 一組 grader 對著摘要評分 → 沒過，就把失敗回饋交給一個 metaprompt agent 去重寫 prompt → 新版 prompt 再跑一輪，直到分數過門檻。重點在那組 grader 的設計: 它不是只丟一個 LLM judge 了事，而是把確定性檢查跟語意判斷組起來，四個一起跑:</p>

<ul>
  <li><strong>chemical_name_grader</strong> (Python，確定性): 檢查摘要有沒有保留原文的化學名稱，確保忠於原文的專業用詞</li>
  <li><strong>word_length_deviation_grader</strong> (Python，確定性): 以 100 字為目標，偏離越多分數越低，控制冗長</li>
  <li><strong>cosine_similarity</strong> (確定性): 確保摘要沒有語意漂移</li>
  <li><strong>llm_as_judge</strong> (語意): 抓前三個規則型 grader 漏掉的細緻品質</li>
</ul>

<p>跟這篇主題最相關的，是這組 grader「沒過時回傳什麼回饋」。沒過的 grader 會被一個 <code class="language-plaintext highlighter-rouge">collect_grader_feedback()</code> 翻成文字、塞進 metaprompt。而這裡有個關鍵分野:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">collect_grader_feedback</span><span class="p">(</span><span class="n">grader_scores</span><span class="p">):</span>
    <span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">grader_scores</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">entry</span><span class="p">[</span><span class="sh">"</span><span class="s">passed</span><span class="sh">"</span><span class="p">]:</span>
            <span class="k">continue</span>                                  <span class="c1"># 只處理沒過的
</span>        <span class="k">if</span> <span class="n">grader</span> <span class="o">==</span> <span class="sh">"</span><span class="s">chemical_name_grader</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">lines</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">Not all chemical names were included…</span><span class="sh">"</span><span class="p">)</span>    <span class="c1"># 寫死的固定訊息
</span>        <span class="k">elif</span> <span class="n">grader</span> <span class="o">==</span> <span class="sh">"</span><span class="s">word_length_deviation_grader</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">lines</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">The summary length deviates too much…</span><span class="sh">"</span><span class="p">)</span>    <span class="c1"># 寫死的固定訊息
</span>        <span class="k">elif</span> <span class="n">grader</span> <span class="o">==</span> <span class="sh">"</span><span class="s">cosine_similarity</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">lines</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="sh">"</span><span class="s">The summary is not sufficiently similar…</span><span class="sh">"</span><span class="p">)</span>  <span class="c1"># 寫死的固定訊息
</span>        <span class="k">elif</span> <span class="n">grader</span> <span class="o">==</span> <span class="sh">"</span><span class="s">llm_as_judge</span><span class="sh">"</span><span class="p">:</span>
            <span class="n">lines</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">entry</span><span class="p">[</span><span class="sh">"</span><span class="s">reasoning</span><span class="sh">"</span><span class="p">])</span>          <span class="c1"># judge 當場生成的語意說明
</span>    <span class="k">return</span> <span class="n">lines</span>
</code></pre></div></div>

<p>三個確定性 grader (chemical／length／cosine) 只能給出「預先寫死的固定字串」，因為它們本身只回一個數字分數，講不出「為什麼不對」。只有 <code class="language-plaintext highlighter-rouge">llm_as_judge</code> 是評分模型 (score model)，能附上自己的推理說明 (reasoning)，給的回饋才帶當下的語意說明。</p>

<p>這帶出案例 A 沒講透的一個取捨。確定性檢查又快又穩又可重現 (案例 A 的 SQLGlot、這裡的三個 Python grader)，但它能給的回饋精細度有上限: 它只能回你「事先設計好、寫死」的那句引導，講不出這一次具體錯在哪。語意 judge 反過來，慢、貴、不穩，但能把問題講清楚。所以「能確定就不用 LLM」不是說 judge 沒用，而是說: 凡是你能事先想清楚、寫死引導句的失敗，就用確定性檢查擋掉; 剩下那些「要看當下語意才講得清楚」的，才交給 judge。</p>

<p>換個角度看，失敗回饋始終是你「設計」出來的，不是系統隨便給的。差別只在: 確定性 grader 的回饋是你寫程式時就一字一句定好的固定訊息，judge 的回饋是它當場生成的。兩種都要寫得可行動，這也是這整篇反覆在講的事。</p>

<h2 id="回傳值的另一半責任-別把-context-塞滿">回傳值的另一半責任: 別把 context 塞滿</h2>

<p>回傳值除了夾帶引導，還有一個容易被忘掉的責任: 控制它自己的大小。這件事是 context engineering 的一環，設計工具回傳值的時候，別忘了 context engineering 還有一整套策略可以一起用。</p>

<p>ihower 在 <a href="https://ihower.tw/blog/12817-context-engineering">Context Engineering</a> 一文裡 (沿用 Lance Martin 的框架) 把它分成四種策略: 寫入、選擇、壓縮、隔離。這幾種要一起考慮，其中兩種最容易在「工具回傳」這一步被漏掉: 「選擇 context」靠的是各種檢索技術 (RAG、reranker、查詢理解)，決定只把對的內容拉進來; 「隔離 context」靠的是 sub-agent，把龐大的中間過程交給另一份 context 去跑、只回傳結論。回到工具回傳值本身，要顧的就是別一次塞太多進 context，下面幾個例子都在講這件事。</p>

<p>LangChain 的 Vivek Trivedy 在一場<a href="https://www.youtube.com/watch?v=NovNcsKX8AU">訪談</a>裡的建議是: 從第一天就帶著「對抗 context rot」的意識設計 agent，而不是等 context 塞滿再來補。第一篇提過 context rot,Viv 把它講得更具體: context window 一旦過了某個門檻，模型會明顯變笨，掉進他同事 (HumanLayer 的 Dex) 說的「dumb zone」。一坨沒裁過的 shell log、一個幾千筆的查詢結果，直接整包塞回 context，就是在把 agent 推向那個門檻。</p>

<p>以 coding agent 為例最具體: 檔案動輒上千行、shell log 輸出量又大，最容易把 context 塞滿。Arize 的 Aparna Dhinakaran <a href="https://x.com/aparnadhinak/status/2048492731929149929">逆向觀察、比較了四個 agent harness</a> (Pi、OpenClaw、Claude Code、Letta) 怎麼管 context，整理出它們在工具輸出上頗為一致的幾個做法 (以下是她從工具行為逆向觀察的整理，細節數字以該串為準，不是官方文件):</p>

<ul>
  <li><strong>硬性上限</strong>: 檔案讀取一律設上限 (Claude Code 是讀取前先 stat 擋掉 256KB 以上的檔、讀取後再用 token 數把關)，工具結果也各有字元上限。</li>
  <li><strong>頭尾保留</strong>: 截斷時不是只留開頭，而是看尾巴重不重要 (有錯誤、有 JSON 收尾大括號、有摘要關鍵字) 就切成「頭 + 尾」，中間捨掉。</li>
  <li><strong>卸載到磁碟</strong>: 超大的工具結果寫到檔案，context 裡只留一個 2KB 的預覽和檔案路徑，模型需要細看時再自己去讀。</li>
  <li><strong>附上續讀提示</strong>: 截斷後補一句「目前顯示第 1 到 2000 行，用 offset=2001 繼續」，讓模型知道它看到的是局部、還能怎麼拿更多。</li>
</ul>

<p>換到 Text-to-SQL 場景也一樣具體: SQL 回傳 3,847 筆，別整包塞進去，裁成前 10 筆，後面補一段 metadata「共 3,847 筆已截斷，涵蓋 412 家公司; 若要加總或平均請直接用 SQL 聚合，不要逐筆讀回來自己算」。這樣 context 不會塞滿，模型又知道整體規模，還順手被導去用對的做法。</p>

<blockquote>
  <p>編按: 這四個 harness 在 context 管理上的趨同還不只工具輸出，連前面講的「壓縮」(對話 compaction)、「隔離」(子代理人) 這兩種策略，做法都很接近，Aparna 那篇值得一讀。</p>
</blockquote>

<p>把這段收一句: 回傳值要同時顧兩件事，一是夾帶能引導下一步的訊號，二是別讓自己把 context 塞滿。下一個案例，正好把這兩件事結合在一起。</p>

<h2 id="案例-c-知識庫-rag-的-facets讓-agent-看見資料全貌">案例 C: 知識庫 RAG 的 facets，讓 agent 看見資料全貌</h2>

<p>換一個語意更重的場景。企業知識庫問答 agent，配一個 <code class="language-plaintext highlighter-rouge">search_knowledge(query)</code> 工具 (關鍵字 + 向量搜尋，再加 reranker)。</p>

<p>RAG 的老問題是: 檢索品質參差不齊，空結果、低相關、版本過時、片段被截斷都有可能。而麻煩在於，<strong>就算拿到品質差的 context,agent 還是會很有自信地生成答案</strong>。檢索品質的把關不能等到答案生出來才做，要在工具層就介入。</p>

<p>最直接的介入，是在回傳結果時順手標註來源和相關度分數，讓 agent 有依據判斷哪些該引用、哪些該丟。再進一步，Jason Liu 在 <a href="https://jxnl.co/writing/2025/08/27/facets-context-engineering/">Beyond Chunks: Why Context Engineering is the Future of RAG</a> 裡點出一個常被忽略的訊號: 當好幾個 chunk 都來自同一份文件，那其實是在暗示「這份文件整體很相關」，與其讓 agent 拿一堆零碎片段去拼湊，不如在工具回傳裡提示它直接把那份文件的整頁讀回來，完整脈絡比散落的 chunk 好用得多。而他文章裡更有意思的一招是 facets: 工具回傳的不只是 top-k 結果，還夾帶一份整個資料集分佈的統計。</p>

<p>舉他原文的例子 (場景是搜尋工單),search 回傳的內容長這樣:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ToolResponse&gt;</span>
  <span class="nt">&lt;results</span> <span class="na">query=</span><span class="s">"API timeout issues"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;ticket</span> <span class="na">id=</span><span class="s">"LIN-1247"</span> <span class="na">status=</span><span class="s">"Done"</span><span class="nt">&gt;</span>…<span class="nt">&lt;/ticket&gt;</span>
    <span class="nt">&lt;ticket</span> <span class="na">id=</span><span class="s">"LIN-1189"</span> <span class="na">status=</span><span class="s">"Done"</span><span class="nt">&gt;</span>…<span class="nt">&lt;/ticket&gt;</span>
    <span class="nt">&lt;ticket</span> <span class="na">id=</span><span class="s">"LIN-1203"</span> <span class="na">status=</span><span class="s">"Done"</span><span class="nt">&gt;</span>…<span class="nt">&lt;/ticket&gt;</span>
  <span class="nt">&lt;/results&gt;</span>
  <span class="nt">&lt;facets&gt;</span>
    <span class="nt">&lt;status_facet&gt;</span>
      <span class="nt">&lt;value</span> <span class="na">name=</span><span class="s">"Done"</span> <span class="na">count=</span><span class="s">"6"</span><span class="nt">/&gt;</span> <span class="nt">&lt;value</span> <span class="na">name=</span><span class="s">"Open"</span> <span class="na">count=</span><span class="s">"5"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;/status_facet&gt;</span>
  <span class="nt">&lt;/facets&gt;</span>
  <span class="nt">&lt;system-instruction&gt;</span>
    回傳的 3 筆全是 Done: 已解決的工單記載較完整、相似度排名較高。
    但 facets 顯示另有 5 筆 Open 沒進 top-k,
    請改用 search(query, status="Open") 追查進行中的問題。
  <span class="nt">&lt;/system-instruction&gt;</span>
<span class="nt">&lt;/ToolResponse&gt;</span>
</code></pre></div></div>

<p>這裡有兩個關鍵設計。一是 <code class="language-plaintext highlighter-rouge">&lt;facets&gt;</code>: 它告訴 agent「符合的不只你看到這 3 筆，還有 5 筆 Open 沒進 top-k」。相似度搜尋有個偏誤，已解決的工單因為文件寫得完整，分數本來就比進行中的高，於是真正該追的進行中問題反而被擠出 top-k。光看 top-k 是看不到這個的。二是 <code class="language-plaintext highlighter-rouge">&lt;system-instruction&gt;</code>: 它直接在工具輸出裡告訴 agent 下一步該怎麼搜。</p>

<p>這就是 facets 給 agent 的價值: 讓它在 top-k 之外，還能看見整個資料集的全貌。agent 不必一次就達到完美 recall，它本來就會持續地、系統性地探索; 你只要在每次工具輸出裡告訴它整個資料集長什麼樣，它就會順著線索一層層查下去。Jason Liu 那篇的核心洞見講得很到位: tool response 本身就是一種 prompt engineering，你回傳的 XML 結構和 metadata，直接形塑 agent 接下來怎麼思考。這跟前面 SQL 那段是同一個道理，只是這裡夾帶的不是錯誤修正，而是探索路徑。</p>

<h2 id="案例-d-把回饋交給另一個模型">案例 D: 把回饋交給「另一個模型」</h2>

<p>前面三個案例，工具內部的把關不是自家寫的 (SQLGlot、Python grader)，就是自己跑的統計 (facets)。還有一種做法是: 工具內部直接去<strong>呼叫另一個模型</strong>，把當前的 context、相關資料丟過去，要它審查、給出第二意見 (second opinion)，再把那個模型的回饋當成 tool response 帶回來。從主迴圈的視角看，這還是一次普通的 tool call，只是這次 call 的是另一個模型。</p>

<p>先說清楚這招怎麼套到自建 agent 上，因為下面舉的成熟例子剛好都出自 coding agent (那邊的工具生態最成熟，不代表這招只有 coding 能用): 你的 Text-to-SQL agent 生出一條複雜查詢後，可以呼叫一個更強的模型，審查「這條 SQL 真的回答了使用者的問題嗎」; RAG 問答 agent 生成答案後，可以叫另一個訓練不同的模型，對著檢索到的文件檢查有沒有幻覺。這個做法是通用的，只是 coding 圈先把它做成了現成工具。</p>

<p>為什麼不讓 agent 自己重讀一遍就好? 因為<strong>自評不是獨立檢查</strong>。consult-llm 這個工具把話講得很白:</p>

<blockquote>
  <p>「一個模型審查自己的產出，不算是獨立的檢查。就算換到全新的 context，它仍然共享同一套訓練、同樣的先驗，以及許多相同的失誤模式。」
(A model reviewing its own work isn’t an independent check. Even in a fresh context, it shares the same training, priors, and many of the same failure modes.)</p>
</blockquote>

<p>換一個訓練不同的模型，才換得到比較獨立的視角。<a href="https://github.com/raine/consult-llm">consult-llm</a> 的定位就是「在你現有的 agent 工作流裡，直接跟另一個模型要第二意見」，實作是一支 CLI,agent 把 prompt 和相關檔案送進去、指定要問 GPT、Gemini 還是 Grok，回傳值就是那個模型的回饋。</p>

<p>工具大致長這樣 (示意):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@function_tool</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">consult_model</span><span class="p">(</span><span class="n">question</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">model</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">遇到難解的問題、想確認方向、或要審查一段產出時呼叫,
    把問題交給另一個模型給獨立回饋。
    question: 你想問的問題、或要它審查的重點
    files: 要附上的相關資料或檔案
    model: 要諮詢哪個模型 (例如 gpt / gemini / o3)
    </span><span class="sh">"""</span>
    <span class="c1"># 工具內部就是去呼叫另一個模型,把它的回饋整段帶回來
</span>    <span class="bp">...</span>
</code></pre></div></div>

<p><a href="https://ampcode.com/news/oracle">Amp</a> (Sourcegraph) 把這招做成內建功能，叫 oracle: 主 agent 跑 Sonnet，但可以呼叫一個背後接 o3 的 oracle 工具，專門拿來審查、除錯、分析、想下一步該怎麼走。他們形容主 agent 跟 oracle 的關係是「一個寫程式、一個分析審查」。這裡有個值得參考的取捨: o3 更強，但也更慢更貴，所以 Amp <strong>刻意不在 system prompt 裡催</strong> agent 動不動就問 oracle，而是讓使用者需要時再明確要求。這點出一條通則: 諮詢另一個模型是工具層最貴的一種回饋，要不要觸發、多常觸發，是你可以調整的設定。</p>

<p>這招甚至不必跨產品。你現在用的 Claude Code，裝上 Codex plugin 後就多一個 <code class="language-plaintext highlighter-rouge">/codex:review</code> 工具: Claude 主 agent 把本地的 git 改動交給 Codex (背後是 GPT) 做 code review，合約還寫死「只做審查、不准順手改」，把 Codex 的審查結果原封不動帶回來。一個 Anthropic 的模型，呼叫一個 OpenAI 的模型來挑自己的毛病。</p>

<p>那「什麼時候該呼叫這個模型」，由誰來決定? 大致有三種做法:</p>

<ul>
  <li><strong>使用者自行觸發</strong>: 需要時才明確叫 agent 去問。Amp 預設就走這條，把決定權留給人，避免無謂的成本。</li>
  <li><strong>寫進前饋規則</strong>: 在 AGENTS.md / system prompt 裡規定「遇到哪類情況 (例如連續改兩次測試還是紅的、或要動到核心模組) 就去呼叫這個工具」，把觸發時機半自動化。</li>
  <li><strong>讓 agent 自行判斷</strong>: 最理想，但也最難。</li>
</ul>

<p>第三條會遇到 <a href="https://blog.aihao.tw/2026/05/19/claude-managed-agents-architecture/">Advisor Strategy</a> 碰過的同一道難題。Advisor Strategy 其實就是這招的一個典型特例: 用便宜的小模型 (Haiku、Sonnet) 當主力，只在卡關時呼叫貴的大模型 (Opus) 當顧問，給一段幾百 token 的精簡建議，目標是「Opus 級的智能、Haiku 級的價格」。它最難的地方不在怎麼把工具接上，而在: <strong>小模型常常沒有足夠的自我覺察，不知道自己已經卡住、該求助了</strong>。也就是說，「判斷一個問題難到需要外援」本身就是個困難問題，而且越弱的模型越判斷不出來: 要有「我搞不定」的判斷力，跟有能力把問題解掉，需要的其實是同一種能力，這形成一個循環依賴。所以實務上，先用前兩種方式 (使用者觸發、寫死規則) 把觸發時機定好，通常比寄望 agent 自己察覺來得穩。</p>

<blockquote>
  <p>編按: 小模型呼叫大模型的 Advisor Strategy,SWE-bench 數字與成本取捨可參考 <a href="https://blog.aihao.tw/2026/06/01/github-claude-caching-harnesses-advisors/">GitHub 與 Claude 的 caching、harness 與 advisor 策略</a>;而多代理在什麼情況反而是反模式 (動輒 3 到 10 倍 token 成本)，見 <a href="https://blog.aihao.tw/2026/05/19/multi-agent-anti-patterns-and-patterns/">Multi-Agent 反模式和業界收斂共識</a>。</p>
</blockquote>

<p>回到本篇主軸: 這個被諮詢的模型回來的東西，一樣是<strong>夾帶在 tool response 裡的回饋</strong>，一樣要可行動。差別只在，前面案例的回饋是你寫死的固定訊息、或自家小 grader 的判定，這裡的回饋是另一個 (通常更強、訓練不同的) 模型當場生成的。它要解的問題，跟「自我感覺良好就交差」其實是同一個，差別只在改用一個獨立的模型來檢查，而不是讓 agent 自己看自己。</p>

<blockquote>
  <p>編按: 「換一個獨立的模型來驗」這個做法，在時機三談單輪驗收時會是主軸 (fresh-context verifier、生成與評估分離)。這裡先在工具層看到它的雛形。</p>
</blockquote>

<h2 id="工具層的四個設計原則">工具層的四個設計原則</h2>

<p>把這幾個案例收斂成四條可以直接拿去用的原則:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);">1️⃣ 能確定就不用 LLM</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:4px;">SQL 驗證用 SQLGlot，不用 judge: 又快又穩又可重現。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);">2️⃣ 語意判斷才用 Judge</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:4px;">細緻品質好不好沒有 assert 可寫，才放一個 LLM judge 補規則型檢查的不足。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);">3️⃣ 回饋必須可行動</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:4px;">錯誤訊息要附「該怎麼辦」，不是只丟一個 error code。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);">4️⃣ 延遲預算內完成</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-top:4px;">工具層回饋在 tool call 內發生，要注意 latency，別讓把關拖慢整個迴圈。</div>
</div>
</div>

<h2 id="接下來-單步正確不等於整體完成">接下來: 單步正確，不等於整體完成</h2>

<p>工具層這道迴圈很便宜、很高頻，但它有一個結構性的限制: 它只看得到單一次 tool call。</p>

<p>每一步都驗得對，只代表「這一步」對。十次工具呼叫全部驗過，整輪的產出仍然可能沒達標: 需求漏做了一半、該跑的整體驗證根本沒跑，這些都不是任何一次單步檢查看得見的。SQLGlot 確認每一句 SQL 都合法，但它不會告訴你「這三句 SQL 加起來，有沒有真的回答使用者的問題」。</p>

<p>完成與否，得從整輪產出的層級來驗收。誰來看「這一輪的產出」，是時機三 (單輪結束的驗收) 要處理的事，不能因為「模型覺得做完了」就算數。不過下一篇先談一個性質不同、由人主動觸發的回饋: 在這一輪還沒結束時，使用者怎麼即時介入 (時機②,steering 與 interrupt)。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Tool Use" /><category term="Context Engineering" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 (本篇) 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (4): 回饋時機二: 兩次 model request 之間,把訊息注入執行中的 agent</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-4-mid-run-injection/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (4): 回饋時機二: 兩次 model request 之間,把訊息注入執行中的 agent" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-4-mid-run-injection</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-4-mid-run-injection/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <strong style="color:var(--text);">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>agent 還在執行、還在反覆呼叫工具，有一個位置可以把新訊息插進去，馬上影響它接下來的動作。這篇講的就是這個回饋時機 (一輪內、兩次 model request 之間): 它在哪、為什麼在這裡注入不會讓 server 端 API 出錯，以及會從這裡注入的兩類訊息。</p>

<p>第一類是<strong>人中途 steer</strong>: agent 還沒做完，使用者就主動輸入一段話介入，社群把這件事叫 steering。這在 Codex、Claude Code 這類互動式 coding agent 最常見。Codex 在 2026 年 2 月正式加了 <a href="https://developers.openai.com/codex/changelog">mid-turn steering</a>,Claude Code 也讓你<a href="https://code.claude.com/docs/en/how-claude-code-works">在它執行時輸入</a>。</p>

<p>第二類是<strong>程式注入</strong>: 在同一個位置放東西進來的不是人，而是程式，例如一個背景工具跑完把結果送回、或一個外部事件 (webhook、告警) 需要讓 agent 知道。這類目前較少框架實作，少數像 Pydantic AI 把它做成通用機制。</p>

<p>兩類用的是同一個注入位置，差別只在來源是人還是程式。先看這個位置在哪。</p>

<h2 id="這個時機點在哪-兩次-model-request-之間">這個時機點在哪: 兩次 model request 之間</h2>

<p>要看懂這個時機點，得先記得一輪 (Turn) 的內部結構: 它不是「一次問、一次答」，而是「模型 request → tool call → 模型 request → tool call …」反覆好幾次，直到模型不再要求工具、輸出最後答案為止。注入就發生在這個迴圈內部、兩次 model request 之間。</p>

<svg viewBox="0 0 720 248" style="width:100%;max-width:720px;height:auto;margin:24px 0;font-family:-apple-system,'PingFang TC','Noto Sans TC',Helvetica,Arial,sans-serif;" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="s2arr" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--text-secondary)" /></marker>
<marker id="s2arrA" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--accent)" /></marker>
</defs>
<rect x="10" y="56" width="700" height="150" rx="12" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5" />
<rect x="26" y="44" width="250" height="24" rx="12" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5" />
<text x="40" y="61" style="fill:var(--accent);font-size:12px;font-weight:700">一輪 Turn (agent 還在進行中)</text>
<rect x="196" y="74" width="328" height="22" rx="11" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.2" />
<text x="360" y="89" text-anchor="middle" style="fill:var(--accent);font-size:10.5px;font-weight:700">↓ 在這裡注入訊息 (人 steer 或程式)</text>
<path d="M 268 96 L 268 116" style="fill:none;stroke:var(--accent);stroke-width:1.2;stroke-dasharray:3 2" marker-end="url(#s2arrA)" />
<path d="M 524 96 L 524 116" style="fill:none;stroke:var(--accent);stroke-width:1.2;stroke-dasharray:3 2" marker-end="url(#s2arrA)" />
<rect x="30" y="120" width="92" height="44" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3" />
<text x="76" y="138" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型</text>
<text x="76" y="153" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">request 1</text>
<line x1="122" y1="142" x2="156" y2="142" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#s2arr)" />
<rect x="158" y="120" width="92" height="44" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="204" y="146" text-anchor="middle" style="fill:var(--accent);font-size:12px;font-weight:600">🔧 tool calls</text>
<line x1="250" y1="142" x2="284" y2="142" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#s2arr)" />
<rect x="286" y="120" width="92" height="44" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3" />
<text x="332" y="138" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型</text>
<text x="332" y="153" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">request 2</text>
<line x1="378" y1="142" x2="412" y2="142" style="stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#s2arr)" />
<rect x="414" y="120" width="92" height="44" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="460" y="146" text-anchor="middle" style="fill:var(--accent);font-size:12px;font-weight:600">🔧 tool calls</text>
<line x1="506" y1="142" x2="540" y2="142" style="stroke:var(--text-secondary);stroke-width:1.4;stroke-dasharray:4 3" marker-end="url(#s2arr)" />
<rect x="542" y="120" width="100" height="44" rx="7" style="fill:var(--bg);stroke:var(--border);stroke-width:1.3;stroke-dasharray:4 3" />
<text x="592" y="138" text-anchor="middle" style="fill:var(--text-secondary);font-size:12px;font-weight:600">模型</text>
<text x="592" y="153" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">輸出答案 (還沒到)</text>
<text x="360" y="186" text-anchor="middle" style="fill:var(--text);font-size:11px">注入發生在每個「tool calls → 下一個 model request」之間 (圖中標記處)</text>
<text x="360" y="200" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">agent 還沒到「輸出答案」這一步，就把新訊息插進這一輪</text>
<text x="706" y="240" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>注意圖中最後那個「輸出答案」是虛線、還沒到。重點就在這: 不是等 agent 執行完才注入 (那是單輪結束的時機)，而是在它還在迴圈中間、還沒輸出答案時就把訊息插進去。</p>

<h2 id="為什麼不能在-tool-call-中間直接加訊息-server-端的硬規則">為什麼不能在 tool call 中間直接加訊息: server 端的硬規則</h2>

<p>這裡有個很常見的疑問: 如果模型這一輪已經發出了 tool call，但你在它還沒拿到結果時就加一則 user message,API 不會出錯嗎?</p>

<p>會，而且會直接回 400。OpenAI 和 Anthropic 有同一條規則: <strong>模型發出的每一個 tool call，都必須先補上對應的結果，中間不能再放別的訊息。</strong></p>

<ul>
  <li>Chat Completions / Responses API: 每個 <code class="language-plaintext highlighter-rouge">tool_call</code> (Responses 裡叫 <code class="language-plaintext highlighter-rouge">function_call</code>，帶一個 <code class="language-plaintext highlighter-rouge">call_id</code>) 都要有對應的 <code class="language-plaintext highlighter-rouge">tool</code> 結果 (Responses 裡叫 <code class="language-plaintext highlighter-rouge">function_call_output</code>)。少補一個，下一個 request 就會報錯，類似 <code class="language-plaintext highlighter-rouge">No tool output found for function call call_xxx</code>。</li>
  <li>Anthropic Messages API: assistant 回合裡的每個 <code class="language-plaintext highlighter-rouge">tool_use</code>，下一個 user 回合都要有對應的 <code class="language-plaintext highlighter-rouge">tool_result</code>，而且 <code class="language-plaintext highlighter-rouge">tool_result</code> 要排在那個 user 回合的最前面，文字接在後面。</li>
</ul>

<p>OpenAI 在 <a href="https://openai.com/index/unrolling-the-codex-agent-loop/">Unrolling the Codex agent loop</a> 裡把這條規則講得很清楚: 一個沒有配對輸出的 tool call，會讓接下來的 request 直接壞掉。</p>

<p>所以 harness 不能真的把你的訊息放在 tool call 執行到一半的地方。它的做法是: <strong>等當前這次 request 的工具全部執行完、結果都收回來，組成一個 API 合法的狀態，才在下一個 model request 之前把你的訊息加進去。</strong> 這也是為什麼 steering 只能在「兩次 model request 之間」，而不是任意時刻: 那是這個迴圈裡唯一一個對 API 合法、又還沒結束的位置。</p>

<p>舉個具體例子。假設模型這一輪一次發出了兩個 tool call (A 和 B)，你只等到 A 的結果回來，就急著插入「改做 X」:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:14px 16px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">❌ 不合法</div>
<div style="font-size:.9em;color:var(--text);margin-bottom:10px;">assistant 發了 tool calls → 還沒等到所有工具結果 → 直接加 user message。下一個 request 送出去就 400。</div>
<div style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:.82em;line-height:1.75;color:var(--text);border-top:1px solid rgba(207,34,46,.25);padding-top:8px;">
<div><span style="color:var(--text-secondary);">assistant:</span> 呼叫 tool call A、tool call B</div>
<div><span style="color:var(--text-secondary);">tool result:</span> output A</div>
<div><span style="color:#cf222e;font-weight:700;">user:</span> 改做 X　<span style="color:#cf222e;">← 還缺 output B</span></div>
<div style="color:#cf222e;font-weight:700;margin-top:4px;">→ API error</div>
</div>
</div>
<div style="flex:1 1 300px;border:1px solid #1a7f37;border-radius:8px;padding:14px 16px;background:#dafbe1;">
<div style="font-weight:700;color:#1a7f37;margin-bottom:6px;">✅ 合法</div>
<div style="font-size:.9em;color:var(--text);margin-bottom:10px;">assistant 發了 tool calls → 每個 call 的結果都補齊 → 再加 user message → 下一個 request。完全沒問題。</div>
<div style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:.82em;line-height:1.75;color:var(--text);border-top:1px solid rgba(26,127,55,.25);padding-top:8px;">
<div><span style="color:var(--text-secondary);">assistant:</span> 呼叫 tool call A、tool call B</div>
<div><span style="color:var(--text-secondary);">tool result:</span> output A</div>
<div><span style="color:#1a7f37;font-weight:700;">tool result:</span> output B　<span style="color:#1a7f37;">← 補齊</span></div>
<div><span style="color:var(--text-secondary);">user:</span> 改做 X</div>
<div><span style="color:var(--text-secondary);">assistant:</span> ok</div>
</div>
</div>
</div>

<p>差別只在那一行 <code class="language-plaintext highlighter-rouge">output B</code>: 兩個 tool call 的結果都補齊、湊成合法狀態之後再插入 user message,steering 就成立了。</p>

<h2 id="用途一-人中途-steer">用途一: 人中途 steer</h2>

<p>第一類，也是目前最常見的，是人主動介入，而 steering 和 interrupt 都是 Codex、Claude Code 這類互動式 coding agent 提供的內建功能。這跟一般框架的 human-in-the-loop 不一樣: human-in-the-loop 是你刻意設計一個等待點，讓 agent 停下來等使用者回饋 (最典型的是「執行某個工具之前，先請使用者確認」)，是 agent 跑到預先寫好的位置、主動停下來問你; steering 剛好相反，agent 沒有等待點、也沒打算停，是你在它還在執行時主動輸入。一個是 agent 停下來問你，一個是你主動介入。</p>

<h3 id="兩種強度-steering-與-interrupt">兩種強度: steering 與 interrupt</h3>

<p>使用者主動介入，有兩種強度，差別在要不要中斷正在執行的工具。</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 300px;border:1px solid var(--accent);border-radius:8px;padding:16px 20px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">🧭 不中斷: steering</div>
<div style="font-size:.92em;color:var(--text);">agent 繼續執行，你輸入的訊息先排進佇列，等當前這次 tool calls 的結果到齊、正要發下一個 model request 之前才注入。它沒有中斷，只是下一步多了你的訊息，已建立的 context 全部保留。</div>
</div>
<div style="flex:1 1 300px;border:1px solid #cf222e;border-radius:8px;padding:16px 20px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:8px;">🛑 中斷: interrupt</div>
<div style="font-size:.92em;color:var(--text);">使用者主動中斷，取消正在執行的工具和這一輪。沒答完的 tool call 還沒有結果，harness 會替它補一則 <code>tool_result</code>，內容就是固定字串 <code>aborted</code> (不是 AI 產生的，是寫死的字)，維持對話對 API 仍然合法，然後停下來等你的新指令。</div>
</div>
</div>

<p><a href="https://agentpatterns.ai/agent-design/steering-running-agents/">AgentPatterns</a> 還提了一個提醒: 如果你發現自己一直要 steer，常常代表一開始的 prompt 給得不夠清楚。手動修正是補救，不是常態，真正該補的往往是讓 agent 一開始就走對方向的前饋指令。這也呼應系列一直強調的: 前饋 (一次做對) 跟回饋 (出錯再修) 要一起設計。</p>

<h2 id="用途二-程式注入-背景工具結果外部事件">用途二: 程式注入 (背景工具結果、外部事件)</h2>

<p>第二類用途，是讓<strong>程式</strong>在同一個位置注入，而不是人。Pydantic AI 文件用一句話總結了這個位置的用途:</p>

<blockquote>
  <p>當執行期間發生某些 agent 應該知道的事，就用這個方法注入: 可能是工具想加入後續脈絡、外部事件需要引導 agent 的計畫，或背景工作完成時要讓 agent 收到結果。</p>
</blockquote>

<p>換句話說，人 steer 只是「外部事件引導 agent 計畫」的一種，人就是那個外部事件;同一個位置也能讓程式注入。常見的有三種:</p>

<div style="display:flex;flex-wrap:wrap;gap:12px;margin:24px 0;">
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">🔧 背景工具結果</div>
<div style="font-size:.9em;color:var(--text);">長時間工具在背景跑完，把結果送回 agent</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">📡 外部事件</div>
<div style="font-size:.9em;color:var(--text);">webhook、錯誤告警、聊天平台訊息主動送進來</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">📝 後續脈絡</div>
<div style="font-size:.9em;color:var(--text);">工具執行中發現該補的資訊，加進對話</div>
</div>
</div>

<p>有提供這個時機點讓你注入的框架並不多: OpenAI Agents SDK、Google ADK 等都沒有。少數例外是 <a href="https://ai.pydantic.dev/">Pydantic AI</a>，它在 v1.101.0 (2026 年 5 月) 加了 <code class="language-plaintext highlighter-rouge">enqueue</code> 方法，把人 steer 和程式注入當成同一件事處理。<code class="language-plaintext highlighter-rouge">enqueue</code> 有兩種模式: <code class="language-plaintext highlighter-rouge">asap</code> (盡快插入，也就是這篇講的時機點，對應官方文件 <a href="https://pydantic.dev/docs/ai/core-concepts/message-history/#injecting-messages-mid-run">Injecting messages mid-run</a> 那一節)，以及 <code class="language-plaintext highlighter-rouge">when_idle</code> (等整輪結束後才插入)。</p>

<h3 id="背景工具結果怎麼送回-agent">背景工具結果怎麼送回 agent</h3>

<p>這三種裡最值得單獨看的是背景工具結果，因為它最能說明程式怎麼用這個位置。重點是它分兩步，剛好不會違反前面那條 API 規則:</p>

<p><strong>第一步 (工具被呼叫時)</strong>: tool call 不等背景任務跑完，而是馬上回一個 <code class="language-plaintext highlighter-rouge">tool_result</code>，內容是一句寫給模型的英文 prompt，像這樣:「Tool ‘X’ is running in background (task N). You will receive the result automatically when it completes. Continue with other work in the meantime.」(意思是: 工具已放到背景執行，完成後會自動把結果送回來)。這個 <code class="language-plaintext highlighter-rouge">tool_result</code> 一補上，原本的 tool call 就配對完成、對話對 API 合法，agent 可以繼續做別的事。</p>

<p><strong>第二步 (背景任務完成時)</strong>: 等背景真的跑完，用 <code class="language-plaintext highlighter-rouge">enqueue</code> 把結果送回去，而這時只能用一則 user 角色訊息 (prompt 像這樣:「Background tool ‘X’ (task N) completed. Result: …」)，不能用 <code class="language-plaintext highlighter-rouge">tool_result</code> (第一步已經回過 tool_result 了)。agent 在下一個 model request 就會讀到它。</p>

<h2 id="小結">小結</h2>

<p>不管來源是人還是程式，這個時機點都是「agent 還在執行時，從外部把訊息送進來」的回饋。下一篇換到 harness 自動觸發的回饋，談時機③: 一輪結束時，該由誰來驗收「這一輪到底做完了沒」。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Tool Use" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent (本篇) 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (5): 回饋時機三: 單輪結束的驗收, Goal 與 Outcomes</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-5-goal-and-outcomes/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (5): 回饋時機三: 單輪結束的驗收, Goal 與 Outcomes" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-5-goal-and-outcomes</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-5-goal-and-outcomes/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <strong style="color:var(--text);">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>前兩種回饋時機 (工具回傳值、中途注入) 都發生在 agent 工作的途中，不負責判斷「整輪到底做完了沒」。問題就在這: 每一次 tool call 都驗得對，不代表整輪的產出達標。十句 SQL 句句合法，不保證這三句加起來真的回答了使用者的問題; 需求漏做一半、該跑的整體驗證根本沒跑，這些都不是任何單步檢查看得見的。</p>

<p>這一篇處理時機③: 一輪結束時的驗收。</p>

<p>時機①發生在「一次 tool call 內部」，主迴圈甚至感覺不到它在跑。時機③的位置不一樣: agent 在這一輪裡已經想完、把該呼叫的工具全呼叫完、正準備收工，就在它要收工的那個時機點判斷: 這一輪到底做完了沒、該把控制權交還給你，還是讓 agent 繼續跑下去。對應到 Claude Code、Codex 這些成熟工具，這個介入點就是它們 Stop hook 這一層的攔截點。</p>

<p>核心精神一句話: <strong>不能因為「模型覺得自己大概做完了」就算數。</strong> 完成與否，要對著一個停止條件去驗證，沒過就繼續做。</p>

<h2 id="goal-一個可驗證的停止條件">Goal: 一個可驗證的停止條件</h2>

<p>要在單輪結束時驗收，你得先講清楚「做完」長什麼樣。這就是 Goal 這個東西要解的問題。Codex 的 Goal、Claude Code 的 <code class="language-plaintext highlighter-rouge">/goal</code>、Claude Managed Agents 的 Outcomes，都是同一個概念的不同產品化: 給 agent 一個持久的目標 (durable objective)，讓它對著一個評估標準持續修正，直到通過為止。它們是自我修正迴圈 (self-correction loop) 的基本構件 (primitive)。</p>

<p>一個好的 Goal 不是把 prompt 寫長，而是一份精簡的契約，通常要講清楚三件事: 終態 (做完是什麼樣)、證據 (用什麼來驗證)、限制 (過程中不能弄壞什麼)。OpenAI Cookbook 給的<a href="https://developers.openai.com/cookbook/examples/codex/using_goals_in_codex">合約模板</a>大致長這樣:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/goal &lt;期望的最終狀態&gt;
      verified by &lt;用什麼證據驗證&gt;
      while preserving &lt;同時要保住什麼&gt;.
Use &lt;允許的工具與邊界&gt;.
Between iterations, &lt;每一輪之間怎麼選下一步&gt;.
If blocked, &lt;該回報什麼、什麼能解鎖進度&gt;.
</code></pre></div></div>

<p>舉例: 「把 p95 latency 降到 120ms 以下，verified by 壓測報告，while preserving 正確性測試全綠。」這比「改善效能」這種模糊指令好得多，因為它給了 agent 一個知道「什麼時候還不能停」的依據: 從 180ms 降到 135ms 不算完成，降破 120ms 但測試掛了也不算完成。</p>

<p>其實連這份模板都不一定要自己填。任務本身夠清楚的時候，有個更省事的做法: 直接把任務描述交給 Codex 或 Claude，請它幫你改寫成一份夠扎實的 goal。一句 <code class="language-plaintext highlighter-rouge">Help me turn this into a strong /goal: &lt;你的任務描述&gt;</code> 就夠，模型會把終態、證據、限制這三件事補齊，你再順手微調。比起自己對著模板逐欄填，這招更簡單，也比較容易把一個清楚但還沒寫成契約的任務，收斂成可驗證的停止條件。</p>

<p>反過來，有兩類任務不適合套 Goal。一是完成條件模糊的，「把這個變更好」「重構這段程式碼」沒有可靠的完成條件，套上 Goal 只是把含糊換個地方放; 這種情況該做的是先把預期終態、驗證方式、限制定義出來，定義不出來代表問題不在工具，在你還沒想清楚要什麼。二是驗證不是二元判斷的灰色地帶 (例如重現一篇深度學習論文)，這時該在開工前先定義「可信度分級」: 哪些是用程式重建、數值驗證過的「確定」，哪些是重新訓練的替代品、行為接近的「近似」，哪些是原作者沒給隨機種子和 checkpoint、根本「做不到完全照原樣重跑」的部分。拿回來的就不是一句簡化的「重現成功」，而是一份按可信度分層、誠實標注的審計報告。</p>

<h2 id="goal-實戰技巧">Goal 實戰技巧</h2>

<p>契約怎麼開，社群累積了幾個實用招式:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">🤖 想不清楚就先跟它聊</div>
<div style="font-size:.9em;color:var(--text-secondary);">連任務意圖都還沒想清楚時，先跟它聊，再請它收斂成 goal: 「請讀這個 session 和 repo，分析想達成的意圖，寫出對應情境的 /goal 提示詞」。</div>
</div>
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">📊 進度儀表板</div>
<div style="font-size:.9em;color:var(--text-secondary);">讓 agent 規律更新一個 <code>goal.html</code>，把目前進度、做了什麼、還差什麼畫出來，長時間跑的時候特別有用。</div>
</div>
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">☑️ Checklist 勾選</div>
<div style="font-size:.9em;color:var(--text-secondary);">先寫一份 checklist 或分階段的設計文件，讓它一條條勾掉，把模糊的「做完」拆成可逐項驗的小目標。</div>
</div>
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">📄 Goal 放檔案</div>
<div style="font-size:.9em;color:var(--text-secondary);"><code>/goal xxx.md</code>: 把契約寫成檔案，可以版本控制、可以重用，goal 不再是一次性的一句話。</div>
</div>
</div>

<div style="font-size:.82em;color:var(--text-secondary);margin:-4px 0 20px;">招式來源: <a href="https://x.com/meta_alchemist/status/2054214497443995694">@meta_alchemist</a>、<a href="https://x.com/dkundel/status/2054616381568815484">@dkundel</a>、<a href="https://x.com/dotey/status/2061260768729809096">@dotey</a>、<a href="https://x.com/jxnlco/status/2057940480998932954">@jxnlco</a></div>

<p>這裡其實藏了一個工程師角色的轉變: 你交付的不再是一句 prompt，而是一份「目標 + 邊界」的契約，然後讓一個迴圈去「提示 → 讀產出 → 判斷完成沒 → 再提示」。思考單位從「一次對話 turn」換成「一輪工作 round」。但這個攔截點背後到底怎麼判斷「做完了沒」，三者的實作分歧很大，而這個分歧正是這篇的重點。</p>

<h2 id="三種實作沿著裁判有多獨立排開">三種實作，沿著「裁判有多獨立」排開</h2>

<p>同一個問題: 工作的模型自己說做完了，能信嗎? 這裡整理三種不同的實作來研究看看。</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">Codex Goals</div>
<div style="font-size:.9em;color:var(--text-secondary);">harness 自己不判斷完成與否，只在 thread 閒置時重播同一份合約 prompt。完成與否由<strong>主模型自我審計</strong>，自己呼叫 <code>update_goal</code> 宣告。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">Claude Code /goal</div>
<div style="font-size:.9em;color:var(--text-secondary);">每輪結束把<strong>刪減版 transcript</strong> 交給獨立的 <strong>Haiku</strong> 裁決。沒過就把 Haiku 寫的診斷注入主 thread，逼著繼續。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">Managed Agents Outcome</div>
<div style="font-size:.9em;color:var(--text-secondary);">主 agent 交出產出物後，<strong>全新 context 的 grader</strong> 拿著 rubric 實際操作 artifact 逐條驗收。沒過帶著逐條缺失進下一輪。</div>
</div>
</div>

<h2 id="實作一-codex-goal沒有裁判的自我審計">實作一: Codex /goal，沒有裁判的自我審計</h2>

<blockquote>
  <p>編按: 以下 Codex 的描述來自<strong>官方文件</strong>加上<strong>讀開源原始碼</strong> (goal spec、<code class="language-plaintext highlighter-rouge">continuation.md</code>，連結附在文中，可自行核對)，不是從外部行為推測的。</p>
</blockquote>

<p>先看 Codex。它的表面行為很單純: 一輪結束會自動續跑，直到目標達成。但 Codex 是開源的，讀<a href="https://github.com/openai/codex/tree/main/codex-rs/ext/goal">原始碼</a>就會發現一件事: 它根本<strong>沒有第二個模型在判定</strong>。</p>

<p>Codex 這邊的 harness 是一個確定性的狀態機，沒有任何 LLM 參與「做完了沒」的判斷。它做的唯一一件事是: 每當 thread 閒置，查一眼資料庫裡 <code class="language-plaintext highlighter-rouge">goal.status</code> 還是不是 <code class="language-plaintext highlighter-rouge">Active</code>。是，就把 <code class="language-plaintext highlighter-rouge">continuation.md</code> 這份模板重新注入、開一個新的 turn 續跑; 不是 (complete / blocked / paused)，就停。所以「沒完成」這件事在 Codex 裡其實沒有對應的動作: 不是有誰判它沒過，而是主模型結束這一輪時<strong>沒有呼叫 <code class="language-plaintext highlighter-rouge">update_goal(complete)</code></strong>，僅此而已。完成的宣告權完全在主模型自己手上，而且呼叫了就算數，沒有任何人覆核。</p>

<svg viewBox="0 0 700 268" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Codex /goal 自我審計迴圈示意圖">
<defs>
<marker id="cdxB" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="cdxG" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#1a7f37" /></marker>
</defs>
<rect x="462" y="8" width="160" height="34" rx="8" style="fill:#dafbe1;stroke:#1a7f37;stroke-width:1.5" />
<text x="542" y="30" text-anchor="middle" style="fill:#1a7f37;font-size:11.5px;font-weight:700">update_goal(complete) ✅</text>
<rect x="18" y="62" width="168" height="60" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="102" y="88" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">注入 continuation.md</text>
<text x="102" y="107" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">每輪同一份合約 prompt</text>
<rect x="236" y="62" width="176" height="60" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="324" y="88" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">主 agent 跑一輪</text>
<text x="324" y="107" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">推理 + 工具呼叫</text>
<rect x="462" y="56" width="160" height="72" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="542" y="86" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">自我審計</text>
<text x="542" y="105" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">達成目標?</text>
<line x1="186" y1="92" x2="232" y2="92" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#cdxB)" />
<line x1="412" y1="92" x2="458" y2="92" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#cdxB)" />
<line x1="542" y1="56" x2="542" y2="46" style="stroke:#1a7f37;stroke-width:1.5" marker-end="url(#cdxG)" />
<text x="552" y="54" style="fill:#1a7f37;font-size:10.5px">是</text>
<path d="M542,128 V196 H102 V126" style="fill:none;stroke:var(--accent);stroke-width:1.5" marker-end="url(#cdxB)" />
<text x="324" y="189" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">否: 沒呼叫 update_goal → thread idle 且 goal.status = Active → 再注入</text>
<text x="350" y="230" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:600">判定者就是主 agent 自己，全程沒有第二個模型</text>
<text x="690" y="258" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>也因為判定者就是工作者本人，Codex 的續跑 turn 會帶著主模型完整的 (加密) 推理脈絡一路重播。這很合理: 它需要靠自己的推理歷史才知道接下來該做什麼。</p>

<p>判定權交在主模型手上，Codex 靠兩層 prompt 紀律約束它，防止「過早宣告完成」:</p>

<ul>
  <li><strong>Tool 合約寫滿消極條款。</strong> <a href="https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/spec.rs"><code class="language-plaintext highlighter-rouge">update_goal</code> 的 description</a> 一連串「only when / do not」: 只有在目標真的達成、沒有剩餘工作時才能設 <code class="language-plaintext highlighter-rouge">complete</code>; 不准因為預算快用完或想停工就標完成; 這個 tool 也不能拿來暫停或恢復 (pause/resume，那些狀態保留給使用者和系統)。<code class="language-plaintext highlighter-rouge">blocked</code> 更嚴格: 同一個阻礙原因要連續三個 goal turn 重複出現才准標記。</li>
  <li><strong>Continuation prompt 寫滿積極程序。</strong> <a href="https://github.com/openai/codex/blob/main/codex-rs/prompts/templates/goals/continuation.md"><code class="language-plaintext highlighter-rouge">continuation.md</code></a> 裡有一段 Completion audit，逼模型把完成「當成尚未證明」，從目標拆出每個需求，逐項找出能證明它的證據，實際去檢查檔案、指令輸出、測試結果。原文的要求相當嚴格:</li>
</ul>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>... treat completion as unproven and verify it against the actual current state ...
The audit must prove completion, not merely fail to find obvious remaining work.
</code></pre></div></div>

<p>(把完成視為尚未證明，對著當前真實狀態逐項驗證 … 這份審計必須證明完成，而不是只因為「沒看到明顯的剩餘工作」就算數。)</p>

<p>換句話說，Codex 沒有外部裁判，但它強迫主模型把「當前 worktree 和外部狀態」當成權威依據去自查，試圖在自我審計的框架裡逼近獨立判定的精神。</p>

<blockquote>
  <p>編按: 這正好是「別只看名字、要看實作」的好例子。網路上有流量不錯的技術文章明確寫 Codex 的 goal mode「用一個獨立小模型每回合評分」，還煞有介事給了 <code class="language-plaintext highlighter-rouge">check_model = "o4-mini"</code> 的設定範例。但翻原始碼就知道 <code class="language-plaintext highlighter-rouge">ext/goal/</code> 裡根本沒有第二個模型、沒有 grader。這是把「理想中該有的樣子」投射成「它已經這樣做了」的誤讀。harness 的細節，看文件不如自己攔一次封包、讀一次程式碼。</p>
</blockquote>

<h2 id="實作二-claude-code-goal獨立-haiku-讀刪減-transcript">實作二: Claude Code /goal，獨立 Haiku 讀刪減 transcript</h2>

<blockquote>
  <p>編按: 以下 Claude Code <code class="language-plaintext highlighter-rouge">/goal</code> 的描述，整體行為來自<strong>官方文件</strong>，而 Haiku、刪減 transcript、structured output 這些官方沒寫明的內部細節，是小編用 <a href="https://mitmproxy.org/">mitmproxy</a> <strong>實際攔封包觀察</strong>到的，可能隨版本改動。</p>
</blockquote>

<p>Codex 把判定權留在主模型自己手上; Claude Code 走相反路線，把判定交給另一個模型。它的 <code class="language-plaintext highlighter-rouge">/goal</code> 原理是: 主迴圈 (Opus) 跑完一輪、自認目標達成、正要收工時，harness 不直接把控制權交還給你，而是另外發一個請求，把整段對話交給一個獨立的小模型，問它一句:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Based on the conversation transcript above,
has the following stopping condition been satisfied?

&lt;stopping condition&gt;

Answer based on transcript evidence only.
</code></pre></div></div>

<p>這裡最值得注意的是最後那句 <code class="language-plaintext highlighter-rouge">Answer based on transcript evidence only</code>:</p>

<div style="border:1px solid #cf222e;border-radius:8px;padding:12px 18px;margin:18px 0;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">⚠️ transcript evidence only</div>
<div style="font-size:.92em;color:var(--text);">判定者<strong>只看對話紀錄，不會重新打開檔案、不會操作 artifact</strong>。它能不能判對，完全取決於主 agent 有沒有把證據「印」在對話裡; 沒印出來的，對它就等於不存在。這條限制，等下對照實作三的 Outcome (裁判真的會去操作 artifact) 會更有感。</div>
</div>

<p>機制上，還有三個細節值得記:</p>

<ul>
  <li><strong>判定者是一個獨立的小模型 (預設 Haiku)</strong>，而且用的是另一份 system prompt (「你是一個 Claude agent」而不是「你是 Claude Code」)，跟<a href="https://code.claude.com/docs/en/goal">文件</a>寫的「預設用小而快的模型」一致。值得注意的是，這個驗收 request 最上層的 <code class="language-plaintext highlighter-rouge">tools</code> 是空的: 它只能讀 transcript，不能再 fetch、不能再搜尋，純做驗收、不繼續工作。</li>
  <li><strong>這份 transcript 是重組過的副本，不是把原 request 原樣轉發。</strong> 保留下來的是裁判判斷用得上的證據: 原始 <code class="language-plaintext highlighter-rouge">/goal</code> 指令、assistant 每一句可見回覆 (含中途敘述和最後總結)、每個 tool_use 的 name 與 input (所以它看得到 <code class="language-plaintext highlighter-rouge">WebFetch</code> 真的被呼叫過、查了什麼)，以及文字型的 tool_result (搜尋結果、網頁摘要、GitHub profile) 當判斷依據。拿掉的則是整塊 thinking 與 signature (主迴圈那幾輪的 thinking 本來就被清空、只留簽章給 API 驗證，跨模型的加密推理帶不過去)，還有最前面那一整包 claudeMd 工作環境; <code class="language-plaintext highlighter-rouge">ToolSearch</code> 回傳的那種結果 (裡面是一個個可呼叫的工具物件、不是文字)，因為驗收 request 已經沒有這些工具 (<code class="language-plaintext highlighter-rouge">tools</code> 是空的)，整段被換成一句 <code class="language-plaintext highlighter-rouge">[Tool references removed - tools no longer available]</code>。所以 Haiku 看不到主模型的推理，只看它「攤在 transcript 上的證據」(這跟 Codex 把完整推理一路保留剛好相反)。</li>
  <li><strong>用 structured output (結構化輸出) 強制回 <code class="language-plaintext highlighter-rouge">{ok, reason, impossible}</code></strong>，不能含糊其辭。三個欄位各管一件事: <code class="language-plaintext highlighter-rouge">ok</code> 回答「目標達成了沒」、<code class="language-plaintext highlighter-rouge">reason</code> 是判斷理由 (判 false 時要講清楚還缺什麼)、<code class="language-plaintext highlighter-rouge">impossible</code> 則回答「這目標是不是根本辦不到」。<code class="language-plaintext highlighter-rouge">impossible</code> 是用來處理一種棘手情況: 萬一目標本來就完成不了 (條件自相矛盾、或依賴一個拿不到的資源)，沒有這欄的話，Stop hook 會一直擋著不讓收工、無限重試下去。所以裁判一旦判 <code class="language-plaintext highlighter-rouge">impossible: true</code>，就算 <code class="language-plaintext highlighter-rouge">ok</code> 還是 false 也會放行結束。這裡還特別加了一道限制: 「辦不到」必須由獨立裁判看 transcript 證據自己判定，主 agent 自己宣稱「做不到」只算一項證據、不算證明，免得它喊一句「我放棄」就提早收工。</li>
</ul>

<p>判定結果分兩條路。判「是」: 清除目標，在逐字稿記一筆已達成，把控制權還給你。判「否」: Stop hook 擋下停止，把 Haiku 寫的 <code class="language-plaintext highlighter-rouge">reason</code> 包成一段 <code class="language-plaintext highlighter-rouge">Stop hook feedback:</code> 注入回主 thread，當成下一輪該怎麼做的依據。因為 Haiku 被要求「引用 transcript 裡缺了什麼」，這個 reason 是針對性的診斷，每輪都不一樣，直接點出缺什麼。</p>

<svg viewBox="0 0 700 324" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Claude Code /goal fork 判定示意圖">
<defs>
<marker id="ccB" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="ccG" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#1a7f37" /></marker>
<marker id="ccR" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#cf222e" /></marker>
</defs>
<rect x="24" y="20" width="194" height="56" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="121" y="44" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">主 agent 跑一輪 (Opus)</text>
<text x="121" y="62" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">自認達成，要收工</text>
<rect x="262" y="20" width="120" height="56" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="322" y="44" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">Stop hook</text>
<text x="322" y="62" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">攔下收工</text>
<line x1="218" y1="48" x2="258" y2="48" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#ccB)" />
<line x1="322" y1="76" x2="322" y2="112" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#ccB)" />
<text x="338" y="94" style="fill:var(--text-secondary);font-size:10.5px">複製刪減版 transcript</text>
<text x="338" y="108" style="fill:var(--text-secondary);font-size:10.5px">(剝除 thinking)</text>
<rect x="232" y="116" width="180" height="64" rx="8" style="fill:#fff8c5;stroke:#9a6700;stroke-width:1.5" />
<text x="322" y="142" text-anchor="middle" style="fill:#9a6700;font-size:12.5px;font-weight:700">獨立 Haiku 裁判</text>
<text x="322" y="161" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">另一個模型，只讀 transcript</text>
<path d="M412,148 H575 V244" style="fill:none;stroke:#1a7f37;stroke-width:1.5" marker-end="url(#ccG)" />
<text x="486" y="140" text-anchor="middle" style="fill:#1a7f37;font-size:10.5px">ok: true</text>
<path d="M232,148 H170 V244" style="fill:none;stroke:#cf222e;stroke-width:1.5" marker-end="url(#ccR)" />
<text x="198" y="140" text-anchor="middle" style="fill:#cf222e;font-size:10.5px">ok: false</text>
<rect x="470" y="246" width="210" height="54" rx="8" style="fill:#dafbe1;stroke:#1a7f37;stroke-width:1.5" />
<text x="575" y="270" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">清除目標，交還控制 ✅</text>
<text x="575" y="288" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">goal 達成</text>
<rect x="20" y="246" width="300" height="54" rx="8" style="fill:#ffebe9;stroke:#cf222e;stroke-width:1.5" />
<text x="170" y="270" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">reason 注入回主 thread</text>
<text x="170" y="288" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">針對性診斷 → 主 agent 繼續下一輪</text>
<path d="M20,273 H10 V48 H24" style="fill:none;stroke:var(--accent);stroke-width:1.5" marker-end="url(#ccB)" />
<text x="690" y="316" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>這裡有個有趣的隱性迴圈: 因為判定者只看得到 transcript 上浮現的證據，如果主 agent 做了某件事卻沒把證據印出來，它會收到「transcript 證據不足」的結果，等於 Haiku 在逼主 agent 把驗證結果明確印出來。另一面是，判定靠的是語意理解而非字面比對，所以停止條件就算寫得不夠精準，只要語意夠清楚，小模型多半還能照語意判斷，而不是拘泥字面。</p>

<p>把兩家的判定標準擺在一起看，剛好相反: Codex 把標準攤開給工作模型自己看 (continuation.md 的 audit 條款),Claude Code 則把判定標準密封在 Haiku 的 system prompt 裡，主模型完全不知道自己會被用什麼標準衡量。一邊是讓工作模型看著標準自評，一邊是把標準藏起來、由另一個模型來判。</p>

<h3 id="判沒完成之後兩家的主-thread-差在哪">判「沒完成」之後，兩家的主 thread 差在哪</h3>

<p>把兩家放在一起看，「沒完成」這個訊號的性質完全不同:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Codex /goal</th>
      <th>Claude Code /goal</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>回饋形式</strong></td>
      <td>重播同一份 continuation.md，只有 budget 數字在動</td>
      <td>Haiku 寫的針對性診斷，具體指出 transcript 裡缺了什麼</td>
    </tr>
    <tr>
      <td><strong>下一步依據</strong></td>
      <td>環境證據 (測試失敗、編譯錯誤) + 自己保留的推理</td>
      <td>外部裁判指出缺口</td>
    </tr>
    <tr>
      <td><strong>context 代價</strong></td>
      <td>每輪累積一份完整模板 (靠 prompt cache 吸收)</td>
      <td>每輪多一次 Haiku call，主 thread 只長一小段 reason</td>
    </tr>
    <tr>
      <td><strong>失效模式</strong></td>
      <td>模型自己有侷限時沒有外部視角來修正，可能連錯好幾輪</td>
      <td>Haiku 誤判: 會被自信的收尾語言騙過</td>
    </tr>
  </tbody>
</table>

<p>Codex 的「沒完成」是同一份指令重播，模型要繼續做什麼，完全靠它自己推理的連續性; Claude Code 的「沒完成」則是一段外部裁判寫的具體診斷，注入主 thread，明白告訴它還缺什麼。這也解釋了為什麼 Codex 必須保留推理、而 Claude Code 可以放心清掉 thinking: 前者要靠自己的推理紀錄才知道做到哪、下一步做什麼，後者則有外部診斷可依循。</p>

<h2 id="實作三-managed-agents-outcome拿著-rubric-操作-artifact">實作三: Managed Agents Outcome，拿著 rubric 操作 artifact</h2>

<blockquote>
  <p>編按: 以下 Outcome 的描述來自 <strong>Anthropic 官方文件與 cookbook</strong>。</p>
</blockquote>

<p>第三種把獨立性推到底。Claude <a href="https://platform.claude.com/docs/en/managed-agents/define-outcomes">Managed Agents 的 Outcome</a> 在你定義目標時，自動配一個 grader，用<strong>全新的 context window</strong> 拿著 rubric 來驗收主 agent 的產出物。重點是「全新 context」: grader 看不到主 agent 的推理和敘事，主 agent 那句「目標達成 ✅」騙不到它。</p>

<p>而且它不只是「看」最終產物。Anthropic 在 <a href="https://www.anthropic.com/engineering/harness-design-long-running-apps">Harness design for long-running apps</a> 裡揭露了更強的版本: 評估者拿著 Playwright，像真實使用者一樣去點擊執行中的應用程式，測 UI、打 API、查資料庫狀態，再對 rubric 每一條評分。這已經不是讀文字了，是會動手的裁判。沒過，就帶著逐條缺失的回饋進下一輪迭代，<code class="language-plaintext highlighter-rouge">max_iterations</code> 預設 3、最多 20。</p>

<svg viewBox="0 0 700 282" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Managed Agents Outcome 獨立 grader 示意圖">
<defs>
<marker id="ocB" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="ocG" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#1a7f37" /></marker>
<marker id="ocR" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#cf222e" /></marker>
</defs>
<rect x="14" y="40" width="140" height="58" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="84" y="65" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">主 agent</text>
<text x="84" y="83" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">(generator)</text>
<rect x="224" y="36" width="140" height="66" rx="8" style="fill:var(--bg);stroke:var(--text-secondary);stroke-width:1.5" />
<text x="294" y="62" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">artifact</text>
<text x="294" y="80" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">(檔案 / app)</text>
<rect x="486" y="36" width="200" height="66" rx="8" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5;stroke-dasharray:5 3" />
<text x="586" y="60" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">獨立 grader agent</text>
<text x="586" y="78" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">全新 context + rubric</text>
<line x1="154" y1="69" x2="222" y2="69" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#ocB)" />
<text x="188" y="60" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">產出</text>
<line x1="486" y1="69" x2="366" y2="69" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#ocB)" />
<text x="426" y="60" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">拿 rubric 操作</text>
<text x="426" y="88" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">點 UI / 打 API / 查 DB</text>
<line x1="586" y1="102" x2="586" y2="198" style="stroke:#1a7f37;stroke-width:1.5" marker-end="url(#ocG)" />
<text x="604" y="150" style="fill:#1a7f37;font-size:10.5px">satisfied</text>
<path d="M520,102 V150 H180 V198" style="fill:none;stroke:#cf222e;stroke-width:1.5" marker-end="url(#ocR)" />
<text x="350" y="143" text-anchor="middle" style="fill:#cf222e;font-size:10.5px">needs_revision</text>
<rect x="486" y="200" width="200" height="48" rx="8" style="fill:#dafbe1;stroke:#1a7f37;stroke-width:1.5" />
<text x="586" y="229" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">交付 ✅</text>
<rect x="24" y="200" width="312" height="48" rx="8" style="fill:#ffebe9;stroke:#cf222e;stroke-width:1.5" />
<text x="180" y="222" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:700">逐條缺失回饋</text>
<text x="180" y="239" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">主 agent 再改一輪</text>
<path d="M24,224 H10 V69 H14" style="fill:none;stroke:var(--accent);stroke-width:1.5" marker-end="url(#ocB)" />
<text x="350" y="272" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:600">裁判完全獨立: 不看 transcript，直接對成品逐條驗收</text>
<text x="690" y="272" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>為什麼這種「事後拿產出物獨立驗收」會比讓 agent 自己驗有效? 上面那篇 Harness design 正是在強調評估者要獨立。它的觀察是: 要 agent 評自己做的東西，它傾向自信地稱讚，哪怕在人眼中品質明顯普通; 而且 context 裡的錯誤推理會不斷累積，前面說服了自己「方向對」，後面就一路錯下去。而它給的結論很乾脆 (翻譯):</p>

<blockquote>
  <p>「調出一個挑剔的獨立評估者，比教會 agent 自我批判容易得多。」</p>
</blockquote>

<p>換句話說，問題不在「能不能批判」，而在「誰來批判」: 同一個 agent 很難對自己的產出下得了手，換一個全新 context 的 agent、再把它調得多疑，反而簡單得多。Cookbook 的 <a href="https://platform.claude.com/cookbook/managed-agents-cma-verify-with-outcome-grader">Outcome grader 範例</a>有一段把這件事點得更具體 (翻譯):</p>

<blockquote>
  <p>「知道評分標準的撰寫者，仍然是在替自己的作品打分。只要它認為自己通過了，它就會說通過。評分者則別無選擇，只能真的去做那些檢查: 它在全新的 context window 開始，身上只帶著評分標準和成品。」</p>
</blockquote>

<p>那篇 Cookbook 的實例很具體: 一個研究任務要求財務數字必須出自正式年報 (10-K / 10-Q)。撰寫者第一次拿了一篇第三方新聞報導就提交、自評通過; 獨立 grader 退回，要求 SEC 正式文件。第二次撰寫者找了個 sec.gov 的網址 (看起來合格了),grader 讀了網址發現那是一份 8-K 的新聞稿附件，不是 rubric 要的 10-K，又退回一次。第三次才找到真正的 10-K，通過。自我檢查抓不到這種錯，因為它「覺得自己過了」; 只有獨立 grader 對著 rubric 才會在這條界線上把它擋下來。</p>

<p>這套「生成跟評估交給不同 agent 做」的主張，不只 Anthropic 在講。<a href="https://x.com/RLanceMartin/status/2064397389189071163">Lance Martin</a> 在談 Fable 5 的 loop 設計時也說「獨立、全新 context 的驗證者子代理人 (verifier subagent)，表現往往勝過自我批判」，而 Anthropic 在 <a href="https://www.anthropic.com/engineering/harness-design-long-running-apps">Harness design for long-running apps</a> 裡更直接把一個 agent 拆成 Planner (規劃)、Generator (生成)、Evaluator (評估) 三個角色，結論是「把做事的 agent 和打分的 agent 分開，是解決自評偏差最有效的辦法」。代價也很實在: 同一個 2D 遊戲編輯器的題目，單一 agent 跑 20 分鐘、約 9 美元，完整 harness (含獨立評估者把關) 跑了 6 小時、約 200 美元，20 倍成本。但差別不是「好一點」: 單跑版的遊戲核心功能根本壞掉、不能玩，harness 版能玩。</p>

<h2 id="三種實作的總比較-獨立性與成本">三種實作的總比較: 獨立性與成本</h2>

<p>把三種實作攤在三個維度上:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Codex Goals</th>
      <th>Claude Code /goal</th>
      <th>Managed Agents Outcomes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>誰來判定</strong></td>
      <td>主模型自我審計</td>
      <td>獨立的 Haiku</td>
      <td>全新 context 的 grader agent</td>
    </tr>
    <tr>
      <td><strong>harness 角色</strong></td>
      <td>不判斷，只在閒置時重播合約 prompt</td>
      <td>每輪結束送 transcript 裁決</td>
      <td>自動配置 grader 評估迴圈</td>
    </tr>
    <tr>
      <td><strong>看什麼證據</strong></td>
      <td>自己 context 裡的一切 (含推理)</td>
      <td>刪減版 transcript</td>
      <td>只看 artifact，實際操作驗收</td>
    </tr>
    <tr>
      <td><strong>怎麼宣告完成</strong></td>
      <td>主模型呼叫 <code class="language-plaintext highlighter-rouge">update_goal</code></td>
      <td>yes/no + 診斷 reason</td>
      <td>rubric 逐條 pass/fail</td>
    </tr>
    <tr>
      <td><strong>沒完成時的回饋</strong></td>
      <td>沒有診斷，只重播同一份 prompt 模板 (continuation.md)</td>
      <td>一段針對性診斷，指出缺什麼</td>
      <td>逐條列出缺失，最具體</td>
    </tr>
  </tbody>
</table>

<p>這三者其實是同一條軸上的三個刻度: 裁判有多獨立於做事的模型。</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 220px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">自己的思維跟逐字稿</div>
<div style="font-size:.9em;color:var(--text-secondary);">Codex: 裁判就是工作者本人。<strong>資訊量最大</strong> (推理、工具輸出全看得到)，但<strong>完全沒有獨立性</strong>。</div>
</div>
<div style="flex:1 1 220px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">看別人的逐字稿</div>
<div style="font-size:.9em;color:var(--text-secondary);">Claude Code: 裁判讀 transcript。資訊量與獨立性<strong>各取一半</strong>: 證據沒印在 transcript 上，等於不存在。</div>
</div>
<div style="flex:1 1 220px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">產出標的物</div>
<div style="font-size:.9em;color:var(--text-secondary);">Outcome: 裁判操作 artifact。<strong>獨立性最大</strong>: 它只驗最後成品、不看模型怎麼說，所以不會被模型的敘事說服。代價是資訊量只剩產出物，<strong>亂改測試、違規操作這類「過程不可接受」的問題看不到</strong>。</div>
</div>
</div>

<p>沒有一家同時拿到「資訊量」與「獨立性」。Codex 把資訊量最大化但完全沒有獨立性; Outcome 把獨立性最大化但只剩產出物，於是違規操作、亂改測試、副作用這類「產出物完美但過程不可接受」的瑕疵成功 (corrupt success)，它就抓不到; Claude Code 介於兩者中間。這是取捨 (trade-off)，不是優劣排名。</p>

<p>這條獨立性排序，學界研究大致也指同個方向: 缺乏外部回饋時，純自我修正容易失敗 (Huang et al., ICLR 2024); 只讀文字的裁判偵測假性完成 (false success) 的能力有限，容易被「自信的收尾語言」帶著走; 相對地，看測試與執行結果的環境回饋、把驗收拆成逐條 rubric、以及能讀檔跑指令的 Agent-as-a-Judge (Zhuge et al., ICML 2025)，可靠度都明顯更高，最後一種甚至接近人類。越往獨立、越會實際操作的那端，判定越可信。</p>

<h3 id="成本-三者的成本花在不同地方">成本: 三者的成本花在不同地方</h3>

<p>獨立性更高，代價就是成本與延遲。先看三者把帳記在哪:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Codex Goals</th>
      <th>Claude Code /goal</th>
      <th>Managed Agents Outcomes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>評估動作</strong></td>
      <td>無模型呼叫: 查狀態，毫秒級</td>
      <td>每 turn 一次 Haiku 呼叫</td>
      <td>每 iteration 跑一次 grader agent，數分鐘起跳</td>
    </tr>
    <tr>
      <td><strong>成本花在</strong></td>
      <td>主模型的 context: 模板與推理持續重播</td>
      <td>第二個模型: Haiku 重讀漸長的 transcript</td>
      <td>評估即執行: 操作 artifact 是「跑 agent」的量級</td>
    </tr>
    <tr>
      <td><strong>回饋延遲</strong></td>
      <td>模型自己發現走偏才回頭</td>
      <td>每 turn 一次，最快</td>
      <td>整個 iteration 做完才被抓到</td>
    </tr>
  </tbody>
</table>

<p>三者的成本花在不同地方。Codex 花在主模型的 context 上: 模板和推理都以最貴的費率持續重播，靠 prompt caching 壓下來。Claude Code 花在第二個模型上: Haiku 每輪重讀越來越長的 transcript，但單價低到官方敢直接寫「可忽略」，而且它反過來幫主模型省錢 (清掉 thinking、只注入短 reason)。Outcome 則是評估即執行: grader 要真的把 artifact 跑起來操作，這是跑 agent 的成本量級。</p>

<p>換成相對量級看會更直觀 (以下是粗略量級，不是精確 benchmark)。Codex 自我審計沒有模型呼叫，只查一次狀態; Claude Code 的 Haiku 評估也很便宜，只多跑一次小模型; Outcome 的一次評估則要跑一整個 grader agent (Anthropic 實測一次約 8 分鐘):</p>

<table>
  <thead>
    <tr>
      <th>單次評估的額外開銷</th>
      <th>token 成本</th>
      <th>延遲</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Codex 自我審計</td>
      <td>趨近零 (無模型呼叫，只查狀態)</td>
      <td>趨近零 (折在 turn 裡)</td>
    </tr>
    <tr>
      <td>Claude Code Haiku</td>
      <td>小 (小模型讀一次 transcript)，與 Codex 同量級</td>
      <td>約 1-2 秒</td>
    </tr>
    <tr>
      <td>Outcome grader</td>
      <td>約前兩者的數十倍 (跑一整個 grader agent)</td>
      <td>約 8 分鐘，約前兩者的數百倍</td>
    </tr>
  </tbody>
</table>

<p><strong>光是單次評估，Outcome 大約就是另外兩者的數十倍 token、數百倍延遲。</strong></p>

<p>不過延遲真正的差異不在「評估本身多久」，而在「錯誤方向多久才會被發現」。Claude Code 一個 turn 就被抓; Codex 要等模型自己發現走偏; Outcome 要等整個 iteration 做完。Anthropic 那個 DAW 實測就是這個樣子: Build 第一輪跑了 2 小時 7 分才迎來第一次 8.8 分鐘的 QA，發現核心互動是擺著好看的、不能用，於是第二輪又花 1 小時重做。回饋越獨立越紮實，錯誤被抓到的時間就越晚，等發現時要重做的工作量也越大。所以正確的比較單位不是「每次評估多少錢」，而是「達到同等可靠度要跑幾輪」: 收斂輪數才是成本的最大宗，每輪的評估開銷只是額外多出的一小筆。</p>

<p>把這個單次的數十倍差距乘上整個 goal 的輪數，落差還會放大，但這裡有個轉折: Outcome 的評估單位是「iteration」而非「turn」，而且預設就有 <code class="language-plaintext highlighter-rouge">max_iterations</code> 上限 (預設 3、最多 20)，它<strong>根本不會</strong>驗上百次。這不是巧合: <strong>驗證的單價，直接決定了驗證能做到多細。</strong> Codex 跟 Haiku 一次評估幾乎不花錢，負擔得起每一輪都驗; Outcome 一次評估貴上數十倍、又要好幾分鐘，只能驗少少幾次，而且驗的是整個成品。便宜的驗證逐輪把關，昂貴的驗證只能事後總驗，這個分野不是誰決定的，是成本算出來的。</p>

<p>(再強調一次: 上面只是「驗證」的額外開銷，不是總帳。真正佔大部分的是「做事」的基底成本，跑上百輪主模型工作本身就很可觀，而這部分三者差不多。)</p>

<p>最後值得點出: 三者的文件不約而同教你「把停止條件寫成可被指令輸出證明的形式」。背後的道理是，當條件夠具體、證據夠機械化，裁判是 Haiku 還是模型自己，差異就被壓縮了; 條件越模糊，三者的失效模式差異才會放大。也就是說，你愈把「做完」定義成可機械驗證的形式，愈不需要為了獨立性，去付 Outcome 那種昂貴的代價。</p>

<h2 id="那為什麼-codex-可以不用獨立裁判">那為什麼 Codex 可以不用獨立裁判?</h2>

<blockquote>
  <p>編按: 以下這節 (特別是「OpenAI 把自我審計練進模型」) 屬於<strong>合理推測</strong>，建立在公開研究與工具行為上，但沒有官方 model card 或論文直接背書，讀的時候請自行打折。</p>
</blockquote>

<p>讀到這裡會有個疑問: Anthropic 自家文章明確說「把做事的 agent 和打分的 agent 分開，是解決自評偏差最有效的辦法」，前面引的研究也把純自評放在最弱的一檔。那 Codex 的自我審計，豈不是站在大家公認最弱的那一檔?</p>

<h3 id="先拆成兩層-harness-層與模型層">先拆成兩層: harness 層與模型層</h3>

<p>ihower 的看法是: 這不代表 OpenAI 做錯了。「誰來判定完成」這件事，拆成兩層看就清楚了: <strong>harness 層</strong> (在模型外面怎麼接驗證: 換一個獨立裁判、還是只注入一段 prompt 要模型自己審) 和 <strong>模型層</strong> (模型本身的驗證能力到底行不行)。這正好對上系列第二篇的 Agent = Model + Harness。Anthropic 從 harness 層下手，多接一個獨立的模型來把關; OpenAI 則把重心放在模型層: 與其在 harness 上換裁判，不如把模型「驗證自己」這件事直接練起來。LangChain 的 Vivek Trivedy 的<a href="https://x.com/Vtrivedy10/status/2063068668049740254">立場</a>很直接: 把「驗證」當成一門要認真拆解的任務; 與其糾結 harness 架構，真正的瓶頸是模型的驗證能力本身，要靠訓練補強。</p>

<h3 id="把驗證練進模型">把驗證練進模型</h3>

<p>而這正是這半年的研究焦點: <strong>驗證 (verification) 是一種可以被訓練進模型的能力，而不只是 harness 怎麼設計的問題。</strong> Trivedy 指出，即時驗證本質上是「生成」的一個子領域，只是模型目前還很弱; 但他們在 Terminal Bench 的 harness 實驗裡發現，讓模型做自我驗證 (self-verification) 帶來明顯的效能提升，而真正的方向是「透過 harness engineering 加上訓練，逼模型把『驗證』這件任務，像它生成解題程式碼一樣認真拆解、做徹底」。CMU 的 Chen Wu 那條研究也是同一個主張: 業界一直在訓練模型「生成」得更好，為什麼不訓練它「驗證」得一樣好? 他們展示了訓練模型去精準定位自己的錯誤，讓同一個模型在困難數學題上的準確率接近翻倍。Philipp Schmid 在 <a href="https://www.philschmid.de/closing-the-loop">Can We Close the Loop in 2026?</a> 把這個方向講得相當清楚: 今天 production 裡絕大多數的「回饋迴路」都還是 scaffolding (在模型外面搭一層驗證步驟)，但研究前沿正在走向 spontaneous verification (自發式驗證)，驗證不再是外掛上去的，而是模型預設就會做的事。</p>

<p>從這個角度看，「OpenAI 在 post-training 階段針對 goal 自我審計加強過模型」這個推測就很合理: 連有人對著單一 goal 把 Codex 跑超過 120 小時都撐得住，靠的就是模型本身被練到能持續自我審計、不中途降低標準。這也跟一個長期觀察對得上: GPT 系模型的指令遵循 (instruction following) 一向是強項，而 Codex /goal 那份寫滿「only when / do not」的自我審計合約，本來就依賴模型「把指令當真」的能力; 在這個基礎上，OpenAI 很可能又針對自我審計另外做過 post-training 強化。能力既然練進了模型，harness 那層就能做得很薄，這也正是 <a href="https://blog.aihao.tw/2026/02/17/bitter-lesson-agent-harness/">Bitter Lesson</a> 的方向: 與其在 harness 上堆手工規則和外部仲裁機制，不如相信模型能力與算力終究會把這些招式收進去。Fable 5 這代模型「擅長在迴圈裡自我修正」已經寫進官方賣點，等於模型層面就為 self-correction loop 最佳化過 (這點留到系列 8 再談)。薄 harness 還有個現實好處: 成本。Trivedy 那串討論底下有人補充，點出高效驗證真正的瓶頸就是成本: 你能不能驗證一千個輸出，而不在算力上付出過高代價? 在會跑數小時、數天的長 goal 上，每回合都加一個外部裁判模型 (judge model)，等於每一步都多付一次推理的延遲與 token; 省掉這個每回合重讀 transcript 的裁判，正是 OpenAI 不走外部裁判的關鍵考量之一。</p>

<h3 id="但這條路是脆弱的">但這條路是脆弱的</h3>

<p>當然，把重心壓在模型層不是沒有代價，而且在訓練到位之前是脆弱的。有一篇關於自我歸因偏差 (self-attribution bias) 的研究發現: 當模型評估「出現在自己對話歷史裡」的行為時，判斷力會選擇性退化，而且在「錯誤的行為」上退化最嚴重，偏偏那正是最需要被抓出來的危險案例。Codex /goal 的 continuation prompt 正是把指令注入同一條 thread 的歷史，完全落在這個情境裡。OpenAI 自己的修補紀錄也透露這條路不好走: 他們改過好幾版 continuation prompt (因為早期模型會把目標偷偷縮小成更容易通過的子集)，甚至把「已經過了多少時間」的回報從 prompt 裡拿掉，因為模型看到時間一直在減少，反而會開始草率行事; 社群也有 issue 實測抓到 goal 已宣稱完成、使用者一句「再檢查一次」又冒出一堆高重要性的問題。值得注意的是，OpenAI 面對這些問題的回應始終不是「換成外部裁判」，而是把自評 prompt 寫得更硬、要求把判斷建立在測試與 build 這類客觀證據上，並把「要一個獨立裁判」的需求，交給 subagents 生態系 (例如唯讀的審查者子代理人，reviewer subagent) 去補。也就是說，<code class="language-plaintext highlighter-rouge">/goal</code> 這個內建迴圈本身，是明確選擇了自我反思 (self-reflection) 加紀律這條路。</p>

<h3 id="小結-兩家把重心放在不同的層">小結: 兩家把重心放在不同的層</h3>

<p>兩家不是對錯之分，是把重心放在不同層。Anthropic 在 harness 層用職責分離來把關，假設「便宜的外部模型加一段短診斷」就夠用，而且它自己同時做了 Outcome 那種完全獨立的 grader，等於光譜兩端都做了; OpenAI 則把重心放在模型層，假設模型的自我驗證可以靠 post-training 練起來，harness 只要薄薄一層紀律。OpenAI 這個方向若成立，長期會更省、更快、更接近「驗證是模型本能」那個前沿，但前提是模型真的被練到位，否則就會掉回自我歸因偏差那種選擇性失靈的問題。Anthropic 不一定全對，OpenAI 也還在補 prompt，這場「重心該放在 harness、還是放在模型訓練」的分歧，目前還沒有定論。</p>

<h2 id="如果是自行開發-agent該怎麼選">如果是自行開發 agent，該怎麼選</h2>

<p>回到這個系列的主軸 (自建 agent)，三種思路的調教難度和成本差很多:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">自我審計 (Codex 式)</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-bottom:8px;"><strong>調教難度最高。</strong>它高度依賴主模型「不對自己放鬆標準」的自律，而 GPT 系模型指令遵循一向是強項，又 (照前面的推測) 可能針對自我審計另外做過 post-training 強化，才撐得住。自建 agent 用一般模型要複製，難度應該會比較高，容易過早宣告完成。</div>
<div style="font-size:.85em;color:var(--text-secondary);">適合: 互動式開發的中短任務，開發者在場能即時檢查與修正，環境回饋 (測試、build、執行結果) 密集且可信，誤判完成的代價也低。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">Transcript 裁判 (Claude Code 式)</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-bottom:8px;"><strong>折衷做法。</strong>用一個小模型對著 transcript 判，相對簡單實作、也比較好評估 (輸入輸出都是文字，容易離線測)。成本與可靠性的平衡點。</div>
<div style="font-size:.85em;color:var(--text-secondary);">適合: 無人值守的中程任務，且完成條件能被指令輸出機械化證明 (測試全綠、佇列清空)。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">獨立 verify (Outcome 式)</div>
<div style="font-size:.9em;color:var(--text-secondary);margin-bottom:8px;"><strong>效果最好，但成本最高、延遲是最大弱點。</strong>每次驗收要把 artifact 跑起來操作，動輒數分鐘，還得花工夫把 rubric 和 grader 調對。</div>
<div style="font-size:.85em;color:var(--text-secondary);">適合: 長時間、以交付物為終點的任務，「完成」完全體現在可操作的 artifact 上，假性完成 (false success) 的代價高到值得花評估開銷。</div>
</div>
</div>

<p>ihower 的傾向是: 自建 agent 想複製 Codex 那種純自我審計，調教難度會很高，因為你手上的模型不見得有對應的 post-training; Claude Code 那種「小模型讀 transcript」是相對好上手、也好評估的折衷起點; 而完全獨立的驗證效果最好，但你要先接受它最貴、延遲最久這兩個前提。</p>

<p>說到底，這呼應這個系列一直強調的一句話: <strong>驗證強度是 harness 的一個可調參數，該隨任務風險和模型能力調整，而不是非黑即白的選擇。</strong> 任務風險低、模型夠強，就往便宜的自評靠; 風險高、交付物明確，就值得上獨立 grader。Anthropic 自己同時擁有光譜的兩端 (<code class="language-plaintext highlighter-rouge">/goal</code> 和 Outcome)，也是用任務型態來切分，而不是宣告誰一定比較好。</p>

<h2 id="綜合案例-一個使用者訪談-agent把兩家的招組起來用">綜合案例: 一個使用者訪談 Agent，把兩家的招組起來用</h2>

<p>前面三種實作都是大廠的產品，各自落在光譜上一個固定的點。但自建 agent 不必二選一，你可以挑不同 pattern 組起來用。小編手邊一個實際做過的使用者訪談 agent 拿來收尾剛好: 它在同一套流程裡同時用上了兩家的招，借 Claude Code 的 transcript 裁判把關「這題問夠了沒」，又借 Codex 每輪重新注入 prompt 的做法，推著訪談往前走。</p>

<p>場景是一個使用者訪談 agent，按訪綱逐題訪問受訪者。難題是: 什麼時候該換下一題? 這一題的資訊問夠了沒? 受訪者答非所問怎麼辦? 不能等訪談全部結束才發現漏問，得在當下就判斷。而且模型自己很愛「覺得差不多了就想往下走」，這跟第二篇講的「過早宣告完成」是同一個毛病，只是把時機③那個「整輪做完了沒」，縮小成「這一題做完了沒」。</p>

<h3 id="借-claude-code-的招-換題工具藏一個只讀-transcript-的-judge">借 Claude Code 的招: 換題工具藏一個只讀 transcript 的 Judge</h3>

<p>做法是: 當 agent 認為這題可以收、想跳下一題時，它呼叫一個 <code class="language-plaintext highlighter-rouge">switch_next_question_workflow</code> 工具來提出請求。但這個工具不直接換題，它先把請求交給一個獨立的 Judge 審核:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@function_tool</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">switch_next_question_workflow</span><span class="p">(</span><span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">user_requested_next</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">當你判斷目前這題已取得足夠答案、或使用者明確要求跳題時呼叫。
    reason: 你認為可以換題的理由
    user_requested_next: 使用者是否明確說了「下一題 / 跳過」
    </span><span class="sh">"""</span>
    <span class="c1"># 真正的判定在 server 端,工具只是把請求轉過去
</span>    <span class="bp">...</span>
</code></pre></div></div>

<p>關鍵在 system prompt 裡對這個工具的描述，直接把規則寫死: 「這個工具會交給獨立審核模型再次判斷; 工具回傳 Y/已切換時，才代表真的進到下一題」「若工具回傳 N/留在本題，你 MUST 繼續聚焦同一題，換一種問法追問，不要自己跳題」「不要自行宣稱已進到下一題，必須等工具結果」。也就是說，主 agent 以為自己在「切換題目」，但每次切換都得先通過這道審核，而且它被明確禁止自己宣布已經切換。這跟實作一 Codex 把 <code class="language-plaintext highlighter-rouge">update_goal</code> 的宣告權限制住是同個用意，只是這裡更進一步: 連宣告權都不留給主模型，直接交給一個獨立的 Judge，做法上更接近實作二的 Claude Code。</p>

<h3 id="judge-看什麼-每題各自的-rubric而且只讀逐字稿">Judge 看什麼: 每題各自的 rubric，而且只讀逐字稿</h3>

<p>審核不是看一個籠統的「問夠了沒」，而是每一題都配一份自己的完成標準 (rubric)。訪綱大致如下 (節錄):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QUESTION_WORKFLOW</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">title</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">最近一次使用情境</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">question</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">最近一次用產品的情境: 用什麼功能、什麼情境。</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">completion_criteria</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">回答需包含最近一次使用的具體情境,至少提到使用的功能,以及當時正在做的任務。</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="sh">"</span><span class="s">title</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">替代方案</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">question</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">不用這個產品你會用誰? 哪個最常用? 為什麼?</span><span class="sh">"</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">completion_criteria</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">回答需提到至少一個替代方案,最好能指出最常用者與原因。</span><span class="sh">"</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="c1"># …其餘題目
</span><span class="p">]</span>
</code></pre></div></div>

<p>Judge 是一次獨立的小模型呼叫 (用 gpt-5.4-mini、reasoning effort 設 none、<code class="language-plaintext highlighter-rouge">max_output_tokens=16</code>，因為它只需要回一個字)，拿到的是「當前題目 + 該題的完成標準 + 到目前為止的逐字稿」，prompt 大致是:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are a user interview quality reviewer.
Decide whether the current interview question has enough concrete
information to be considered complete.
Use only the transcript below. Do not infer facts that are not in the transcript.

Current question: {question}
Completion criteria: {completion_criteria}
Transcript (user and assistant only): {transcript}

Return exactly one character:
Y if the current question is complete. N if not.
</code></pre></div></div>

<p>有兩個地方剛好呼應這篇拆過的兩個重點。一是那句 <code class="language-plaintext highlighter-rouge">Use only the transcript below. Do not infer facts that are not in the transcript</code>，跟實作二 Claude Code /goal 的 <code class="language-plaintext highlighter-rouge">Answer based on transcript evidence only</code> 根本是同一句話: Judge 只認攤在逐字稿上的證據，不自己想像，沒印出來的對它就等於不存在。差別只在 Claude Code 一次判「整輪」，這裡一次判「一題」。二是每題各配一份 <code class="language-plaintext highlighter-rouge">completion_criteria</code>，不是用同一套標準衡量所有題目，正好就是前面比較三者時提到的「Rubric 分解」: 條件越具體，小模型越判得準。</p>

<h3 id="沒過時的回傳值-還是要可行動">沒過時的回傳值: 還是要可行動</h3>

<p>Judge 回 N 的時候，工具不是冷冰冰地回一個 false，而是回一段可行動的指令，順便夾帶這是第幾次被退:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>N: 仍停留在第 1 題「最近一次使用情境」。這是第一次追問。
請換一種問法追問目前這題,不要切換題目。
</code></pre></div></div>

<p>判定是透過工具回傳值夾帶的指令傳回主 agent 的，跟 Claude Code 把 Haiku 寫的 <code class="language-plaintext highlighter-rouge">reason</code> 包成 <code class="language-plaintext highlighter-rouge">Stop hook feedback:</code> 注入回主 thread，是同一個做法: 沒過不是終點，而是一段告訴它下一步怎麼做的訊息。</p>

<h3 id="語意檢查要有上限與退出機制">語意檢查要有上限與退出機制</h3>

<p>Judge 如果是一道沒有上限的檢查會出問題: 遇到答得很跳的受訪者，某題可能永遠湊不齊完成標準，agent 就卡在那題出不來，限時訪談裡這很糟。所以 server 端多維護一個「這題被退幾次」的計數器，被退 3 次、或使用者明確說要跳，就強制放行，不再問 Judge。實際行為就是: 同一題最多追問兩次，第三次無論判定如何都往下走。</p>

<p>這跟實作三 Outcome 的 <code class="language-plaintext highlighter-rouge">max_iterations</code> (預設 3、最多 20) 是同一種設計: 給審核一個退出機制。通則: <strong>語意檢查很好用，但它應該有界。</strong> 給一個重試上限、一個使用者能手動覆蓋的出口，品質把關跟「別讓流程停不下來」才能兼顧。</p>

<h3 id="借-codex-的招-time_control-每輪注入動態狀態">借 Codex 的招: time_control 每輪注入動態狀態</h3>

<p>光有 Judge 擋著還不夠，你還得在每一輪推著 agent 往對的方向走，尤其是時間。但模型自己不知道「現在過幾分鐘了」「進行到第幾題了」，這是 harness 才算得出來的狀態，得主動提供給它。</p>

<p>這裡用的正是實作一 Codex 那一招: 每一輪結束、agent 要繼續時，harness 往對話裡注入一段 prompt。差別在 Codex 注入的是固定的 <code class="language-plaintext highlighter-rouge">continuation.md</code>，這裡注入的是動態的當下狀態:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;time_control&gt;已過 7 分鐘 / 共 15 分鐘&lt;current_question&gt;第 2 / 5 題: …&lt;/current_question&gt;&lt;/time_control&gt;
</code></pre></div></div>

<p>幾個設計點: 它是內部指示，prompt 裡明確交代「DO NOT 對使用者念出、引用，或提及時間、進度」; 而且做了去重，只有「分鐘數變了」或「換題了」才重新插一則，不會每輪都重複注入 (Codex 靠 prompt cache 吸收每輪重播的模板成本，這裡改用去重來省)。然後 prompt 教 agent 怎麼用這個訊號調節奏: 時間充裕就從容追問; 過半還卡在前段就精簡、加速; 最後 1 到 2 分鐘讓每題拿到最小可用答案就趕快推進。</p>

<h3 id="一個推進度一個把關而且-pattern-不綁時機點">一個推進度、一個把關，而且 pattern 不綁時機點</h3>

<p>把兩半合起來看，正好是這篇兩種實作的縮影，一前一後配合: time_control 是 Codex 式的每輪注入 (前饋)，動態提供給模型它自己算不出來的當下狀態，影響它「什麼時候想呼叫換題工具」; Judge 是 Claude Code 式的 transcript 裁判 (回饋)，在它真的呼叫時把關「這次能不能換」。time_control 推進度、Judge 把關，合起來才讓一場限時訪談既問得夠深、又跑得完。</p>

<svg viewBox="0 0 700 400" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="使用者訪談 agent 組合 time_control 與 Judge 示意圖">
<defs>
<marker id="uiB" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="uiG" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#1a7f37" /></marker>
<marker id="uiR" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:#cf222e" /></marker>
</defs>
<rect x="160" y="14" width="380" height="54" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="350" y="38" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">time_control: 每輪注入動態狀態 (時間 / 題號)</text>
<text x="350" y="57" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">Codex 式 · 前饋: 給模型它自己算不出的當下狀態</text>
<rect x="235" y="96" width="230" height="58" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="350" y="120" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">訪談 agent 跑一輪</text>
<text x="350" y="139" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">逐題問答 · 判斷這題問夠了沒</text>
<rect x="205" y="192" width="290" height="62" rx="8" style="fill:#fff8c5;stroke:#9a6700;stroke-width:1.5" />
<text x="350" y="216" text-anchor="middle" style="fill:#9a6700;font-size:12.5px;font-weight:700">獨立 Judge · 只讀 transcript + 該題 rubric</text>
<text x="350" y="235" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px">Claude Code 式 · 回饋: 把關「這題能不能換」</text>
<rect x="395" y="298" width="215" height="48" rx="8" style="fill:#dafbe1;stroke:#1a7f37;stroke-width:1.5" />
<text x="502" y="320" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">進到下一題 ✅</text>
<text x="502" y="337" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">harness 推進到下一題</text>
<rect x="70" y="298" width="250" height="48" rx="8" style="fill:#ffebe9;stroke:#cf222e;stroke-width:1.5" />
<text x="195" y="320" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">留在本題，換問法追問</text>
<text x="195" y="337" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">回傳可行動指令 + 第幾次被退</text>
<line x1="350" y1="68" x2="350" y2="94" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#uiB)" />
<text x="358" y="86" style="fill:var(--text-secondary);font-size:10.5px">影響何時換題</text>
<line x1="350" y1="154" x2="350" y2="190" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#uiB)" />
<text x="360" y="177" style="fill:var(--text-secondary);font-size:10.5px">想換題: 呼叫 switch 工具</text>
<path d="M495,210 H575 V296" style="fill:none;stroke:#1a7f37;stroke-width:1.5" marker-end="url(#uiG)" />
<text x="537" y="203" text-anchor="middle" style="fill:#1a7f37;font-size:10.5px">Y: 切換</text>
<path d="M205,210 H150 V296" style="fill:none;stroke:#cf222e;stroke-width:1.5" marker-end="url(#uiR)" />
<text x="150" y="203" text-anchor="middle" style="fill:#cf222e;font-size:10.5px">N: 留本題</text>
<path d="M70,325 H32 V125 H233" style="fill:none;stroke:var(--accent);stroke-width:1.5" marker-end="url(#uiB)" />
<path d="M610,325 H665 V41 H542" style="fill:none;stroke:var(--accent);stroke-width:1.5" marker-end="url(#uiB)" />
<text x="640" y="200" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">下一輪</text>
<path d="M195,346 V362 H503 V348" style="fill:none;stroke:#1a7f37;stroke-width:1.3;stroke-dasharray:5 3" marker-end="url(#uiG)" />
<text x="349" y="359" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">退 3 次 / 使用者喊跳 → 強制放行</text>
<text x="350" y="382" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:600">time_control 推進度，Judge 把關: 一前一後讓限時訪談問得深又跑得完</text>
<text x="694" y="396" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>還有一點值得點出來: 這整套其實是做在工具層的 (時機①,Judge 藏在一次 tool call 背後)，不是 Stop hook 那種 turn-end (時機③)。但它借的兩個 pattern，正是這篇為時機③拆過的 transcript 裁判和每輪注入。這說明這些 pattern 並不綁死在某個時機點上，你可以在工具層、單輪結束、外層 loop 任何一個粒度上拿來組合，看你要把「做完了沒」這個判斷，放在多細的工作單位上。</p>

<h2 id="銜接下一篇-一個-context-裝不下的任務呢">銜接下一篇: 一個 context 裝不下的任務呢?</h2>

<p>時機③的價值，是在「每一輪結束」這個邊界上，把「做完了沒」交給一個夠獨立的判斷，而不是任憑模型自我感覺良好就算完成。</p>

<p>但它仍有個結構性的上限: 不管是哪一種實作，它們都在「同一個 session、同一個 (或續跑的) context」裡運作。當任務大到一個 context window 根本裝不下 (要跑幾百步、跨好幾天)，光靠單輪驗收收斂不了，失敗的推理還會在 context 裡越積越多、污染後面的判斷。</p>

<p>這就帶到時機④: 外層 loop。下一篇談怎麼把一個任務一輪輪交給全新的 agent，讓進度在 context 之外傳遞，也就是 Ralph、Symphony、Cron 這些 loop engineering 的做法。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Eval" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes (本篇) 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (6): 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-6-outer-loop/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (6): 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-6-outer-loop</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-6-outer-loop/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <strong style="color:var(--text);">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>上一篇談時機③，在「每一輪結束」這個邊界上，把「做完了沒」交給一個夠獨立的判斷。但它有個結構性的上限: 不管哪一種實作，都在同一個 session、同一份 (或續跑的) context 裡進行。當任務大到一個 context window 根本裝不下 (要跑幾百步、跨好幾天)，光靠單輪驗收收斂不了，失敗的推理還會在 context 裡越積越多，污染後面的判斷。</p>

<p>這一篇進到時機④: 外層 Loop。四個時機由內而外，這是最外、也最貴的一層。</p>

<p>要先說清楚一件事: 這一層在社群討論得很熱，<a href="https://x.com/steipete/status/2063697162748260627">Peter Steinberger</a> 那句「你不該再去提示 agent，你該設計提示 agent 的迴圈」被反覆討論了好幾個月，Boris Cherny、Andrej Karpathy 也都講過類似的話，還出現了 loop engineering、loopcraft 這些新詞。但這些講法指向的東西其實很具體，後面會一個一個拆開來談。</p>

<h2 id="為什麼需要外層迴圈">為什麼需要外層迴圈</h2>

<p>先講清楚這層在解什麼。前面幾個時機都假設「一個 context 裝得下整個任務」，在這個前提內把迴圈閉合。一旦離開這個前提，有三種情況會讓你需要把迴圈往外推一層:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 210px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">① 單一 context 能力有限</div>
<div style="font-size:.9em;color:var(--text-secondary);line-height:1.7;margin-bottom:8px;">大任務得拆段做，每段用乾淨 context 重新開始、表現不受前一段影響。一個 context 一路跑下去會遇到:</div>
<ul style="margin:0;padding-left:18px;font-size:.9em;color:var(--text-secondary);line-height:1.7;">
<li>context 塞滿: 工具輸出、中間推理把 context 佔滿，模型表現明顯變差</li>
<li>失敗推理污染: 前面出錯的推理留在 context 裡，持續影響後面的判斷</li>
<li>Goal 跨不過 session: 時機③的驗收只在一個 session 內有效</li>
</ul>
</div>
<div style="flex:1 1 210px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">② 是另一個不相關的新任務</div>
<div style="font-size:.9em;color:var(--text-secondary);line-height:1.7;">跟手上這件事無關的工作，本來就該各自開一份乾淨的 context，而不是全接在同一條後面。一旦同時有好幾件，就需要一個 context 之外的機制去管理多條平行的 agent: 誰在做什麼、哪些能一起跑。</div>
</div>
<div style="flex:1 1 210px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">③ 要由外部事件自動觸發</div>
<div style="font-size:.9em;color:var(--text-secondary);line-height:1.7;">有些工作不是人坐在終端機前手動下指令，而是外部訊息、webhook、排程、CI 失敗一來就該自己啟動。這個「自動把任務送出去跑」的動作，只能由一個在 session 之外、一直待命的機制來做。</div>
</div>
</div>

<p>這三種情況指向同一個東西: 一個在任何單一 context 之外、不會隨某一輪 agent 收掉就跟著消失的工程機制 (也就是外層 harness)。它負責決定什麼條件觸發、每件工作開哪一份乾淨 context、多條 agent 怎麼協調，以及最關鍵的，進度怎麼在一圈一圈之間傳下去。後面要談的三種實作剛好各自對應一種情況: Ralph 對著一個大任務一圈圈重跑 (①)、Symphony 同時管很多張 ticket、各配一個 agent (②)、Cron 靠排程或事件自動起跑 (③)。</p>

<p>這裡有個觀念要先確立，它貫穿後面三種實作: <strong>外層迴圈最關鍵的設計，是把「進度」這件事從 context 搬到磁碟。</strong> 模型在每一圈結束後 context 就清空了，所以能撐過多圈的不是模型自己記得，而是那份留在外面的狀態。Geoffrey Huntley 講 Ralph 時講得很直白: agent 會忘，但 repo 不會。</p>

<p>那這份外層迴圈具體怎麼搭? 這裡挑三個有代表性的案例來分析。</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">🔁 Ralph</div>
<div style="font-size:.9em;color:var(--text-secondary);">bash 蠻力重跑。每圈全新 context，同一份 prompt 一直跑到做完為止。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">🎼 Symphony</div>
<div style="font-size:.9em;color:var(--text-secondary);">看板協調器。把專案管理工具當控制平面，每張開啟的 ticket 配一個 agent。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">⏰ Cron 排程</div>
<div style="font-size:.9em;color:var(--text-secondary);">定時觸發 (如 <code>/loop</code>)。時間到就跑一輪，依 context 是否沿用再分兩種。</div>
</div>
</div>

<h2 id="ralph-最笨也最有名的-loop">Ralph: 最笨也最有名的 Loop</h2>

<p>第一種是 Geoffrey Huntley 在 2025 年中提出的 <a href="https://ghuntley.com/ralph/">Ralph</a>，做法簡單到第一眼會以為是在開玩笑:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> :<span class="p">;</span> <span class="k">do
  </span><span class="nb">cat </span>PROMPT.md | claude-code
<span class="k">done</span>
</code></pre></div></div>

<p>就是一個 bash 無窮迴圈，每一圈把同一份 prompt 檔交給 agent，跑完就再來一次。沒有花俏的編排，沒有狀態機。它真正的設計重點不在迴圈本身，而在「每一圈都是全新的 context」: 不讓對話一直長下去，而是每圈歸零，只從固定的幾份檔案重新讀起。靠這個做法，Huntley 用 Ralph 完成過一個完整的編譯器專案，也用大約 297 美元的 API 成本，做出一個原本報價 5 萬美元合約的 MVP。</p>

<p>那這個「無窮」迴圈怎麼停? 其實它是有完成條件的: 當 agent 認為 prd.json 上的任務都完成了，會輸出一個約定好的字串 <code class="language-plaintext highlighter-rouge">&lt;promise&gt;COMPLETE&lt;/promise&gt;</code>，外層 bash 每一圈用 <code class="language-plaintext highlighter-rouge">grep</code> 比對這個字串，一比中就跳出迴圈、不再重跑;另外再配一個 <code class="language-plaintext highlighter-rouge">max-iterations</code> 上限 (snarktank 範本預設 10 圈) 當保底，避免卡住時一直跑下去。也就是說，完成與否是讓模型自己宣告、再靠字串比對來認定，這也是後面批評會談到的點: 它是模型自己說了算，不是獨立驗證過的 Goal。</p>

<h3 id="跨圈記憶-進度全靠外部狀態">跨圈記憶: 進度全靠外部狀態</h3>

<p>既然 context 每圈歸零，進度就得放在 context 之外。Ralph 把這件事拆成三個檔案，這也正好是前面那句「進度搬到磁碟」最具體的示範:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">📜 git history</div>
<div style="font-size:.9em;color:var(--text-secondary);">已完成的工作就是 commit。新一圈開始，先看 git 歷史知道做到哪。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">📝 progress.txt</div>
<div style="font-size:.9em;color:var(--text-secondary);">每圈把學到的事追加上去: 踩過的陷阱、發現的慣例、做過的決策。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">☑️ prd.json</div>
<div style="font-size:.9em;color:var(--text-secondary);">任務清單與每項的 <code>passes</code> 狀態。每圈挑最高優先且還沒過的來做。</div>
</div>
</div>

<p><a href="https://github.com/snarktank/ralph">snarktank/ralph</a> 這個現成的範本把一圈的動作寫得很清楚: 讀 prd.json 和 progress.txt → 挑一個最高優先、<code class="language-plaintext highlighter-rouge">passes: false</code> 的 story → 只實作這一個 → 跑 typecheck 和測試 → 過了才 commit、把 <code class="language-plaintext highlighter-rouge">passes</code> 標成 true → 把這圈學到的東西追加進 progress.txt → 下一圈。它強調的成功要件，其實全是前幾篇講過的東西: story 要切小到一個 context 裝得下、要有 typecheck 和測試這種快速回饋、完成條件要寫清楚。換句話說，Ralph 的外層迴圈能不能跑得動，仍然取決於內層那兩個時機有沒有先做好。</p>

<svg viewBox="0 0 700 300" style="width:100%;max-width:660px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Ralph 運作原理示意圖">
<defs>
<marker id="rlpA" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="rlpS" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker>
</defs>
<path d="M524,108 V46 H86 V104" style="fill:none;stroke:var(--accent);stroke-width:1.5;stroke-dasharray:5 3" marker-end="url(#rlpA)" />
<text x="305" y="38" text-anchor="middle" style="fill:var(--accent);font-size:11.5px;font-weight:600">每圈結束 → context 整個丟掉，開全新一圈</text>
<rect x="22" y="108" width="130" height="64" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="87" y="135" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">prompt.md</text>
<text x="87" y="154" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">每圈用同一份</text>
<rect x="212" y="102" width="158" height="76" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="291" y="133" text-anchor="middle" style="fill:var(--text);font-size:12.5px;font-weight:700">全新 context 的 agent</text>
<text x="291" y="152" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">乾淨的 context，跑一圈</text>
<rect x="430" y="108" width="186" height="64" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="523" y="134" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:700">挑一個 story 做</text>
<text x="523" y="153" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">過 typecheck/測試才 commit</text>
<line x1="152" y1="140" x2="210" y2="140" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#rlpA)" />
<line x1="370" y1="140" x2="428" y2="140" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#rlpA)" />
<rect x="206" y="246" width="300" height="46" rx="8" style="fill:#fff8c5;stroke:#9a6700;stroke-width:1.5" />
<text x="356" y="266" text-anchor="middle" style="fill:#9a6700;font-size:11.5px;font-weight:700">磁碟狀態 (跨圈不丟)</text>
<text x="356" y="283" text-anchor="middle" style="fill:var(--text-secondary);font-size:10px">git history · progress.txt · prd.json</text>
<path d="M268,246 V182" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#rlpS)" />
<text x="260" y="214" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">① 開圈先讀</text>
<path d="M452,172 V246" style="fill:none;stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#rlpS)" />
<text x="460" y="208" style="fill:var(--text-secondary);font-size:10px">② commit、</text>
<text x="460" y="222" style="fill:var(--text-secondary);font-size:10px">記下學到的</text>
<text x="690" y="296" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<h3 id="ralph-wiggum-plugin-名字一樣機制不同">ralph-wiggum Plugin: 名字一樣，機制不同</h3>

<p>這裡要插一個容易混淆的點。Anthropic 官方在 Claude Code 出了一個叫 <a href="https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum">ralph-wiggum</a> 的 plugin，名字明顯是在致敬 Ralph，但它的機制跟原版相反:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:22px 0;">
<div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:8px;">原版 Ralph</div>
<div style="font-size:.92em;color:var(--text-secondary);">bash 外迴圈，<strong>每圈整個關掉重開</strong>。全新 context、靠檔案傳遞進度。是個「換新 context」的設計。</div>
</div>
<div style="flex:1 1 280px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">ralph-wiggum plugin</div>
<div style="font-size:.92em;color:var(--text-secondary);">用 <strong>Stop hook</strong> 實作。<strong>同一個 session</strong> 內，在 agent 想結束時攔住、把 prompt 重新送回去，配 <code>--max-iterations</code>、<code>--completion-promise</code> 控制何時停。是個「不讓它結束、要它繼續做」的設計。</div>
</div>
</div>

<p>一個換新 context，一個不讓它結束，context 處理剛好相反。這正好呼應系列第五篇那條教訓: <strong>別只看名字，要看實作。</strong> 上一篇講 Codex 的 goal mode 時，看過有流量不錯的文章把它說成「用獨立小模型每回合評分」，翻原始碼才發現根本沒有第二個模型。這裡也一樣: 兩個都叫 Ralph，但你要是照原版的心智模型去用 plugin、以為它每圈會清掉 context，就會踩雷。harness 的細節，看名字、看標題都不準，得自己讀一次 code。</p>

<p>而把 code 讀完，會看到一個比「同 session」更有意思的點。plugin 每一圈送回去的，是你最初下的那段 prompt 原封不動 (存在 <code class="language-plaintext highlighter-rouge">.claude/ralph-loop.local.md</code>，官方明說「prompt 不會在迭代之間改變」)，只多附一行系統訊息提醒這是第幾圈、以及「要停就輸出 <code class="language-plaintext highlighter-rouge">&lt;promise&gt;DONE&lt;/promise&gt;</code>，但只有陳述為真才准、別為了結束就說謊」。它判斷完成的方式也很陽春: 去比對最後一則回覆裡有沒有那個寫死的 <code class="language-plaintext highlighter-rouge">&lt;promise&gt;</code> 字串。把「同一條 thread 一路長下去」加上「完成與否由主模型自己判定」這兩件事擺在一起，你會發現它的機制其實更靠近上一篇的 Codex /goal，而不是原版 Ralph: 原版每圈換一份乾淨 context,plugin 跟 Codex 都是同一份 context 一路跑、自己宣告做完。差別只在 Codex 送回去的是一份專門寫過、含完成審計程序的接續模板(continuation)，宣告還得過 <code class="language-plaintext highlighter-rouge">update_goal</code> 那串嚴格條款; plugin 送的是原 prompt、停止只認一個字串相符，加一句口頭提醒，等於是這幾種做法裡最寬鬆的一種完成判定。<strong>所以「ralph-wiggum 是什麼」這題，光看名字會答成原版 Ralph，讀完 code 才知道它實際上更像 Codex /goal。</strong></p>

<h3 id="對-ralph-的批評">對 Ralph 的批評</h3>

<p>Ralph 有名，但 OpenClaw 作者 Peter Steinberger 對它的批評也很直接，大致三點: 一是弱模型的權宜之計(workaround)，模型不夠強，才需要在外層一直重跑來硬補; 二是靠耗掉大量 token 硬做，而不是聰明收斂; 三是把人從迴圈裡抽得太遠，產出沒人把關 (他用「slop in a loop」，迴圈裡的垃圾，來形容)。</p>

<div style="font-size:.82em;color:var(--text-secondary);margin:-6px 0 18px;">出處: Steinberger 從 2026 年 1 月起的多則推文，例如 <a href="https://x.com/steipete/status/2010205512303030525">「給 Opus 的權宜之計」</a>、<a href="https://x.com/steipete/status/2011422781137707112">「slop in a loop」</a>。</div>

<p>但他反對的不是迴圈本身，是用蠻力迴圈取代思考。他自己照樣大量用 loop，只是配上有收斂條件的 <code class="language-plaintext highlighter-rouge">/goal</code>，以及自己寫的審查、驗證 skills，人仍然在把關架構決策。</p>

<p>用這個系列的說法來講: Ralph 只做了外層的「排程」(一直重跑)，卻沒接好內層的「驗證」和「方向」。第二篇講過，只有回饋、沒有前饋，agent 常重跑好幾圈還是偏離目標。所以更精確的說法不是「Ralph 不好」，而是「外層迴圈不能取代內層的 harness，它是疊在上面、不是換掉它」。</p>

<h2 id="openai-symphony-把看板變成控制平面">OpenAI Symphony: 把看板變成控制平面</h2>

<p>第二種實作來自 OpenAI 的 <a href="https://openai.com/index/open-source-codex-orchestration-symphony/">Symphony</a>，它的出發點比 Ralph 高一階。</p>

<p>故事是這樣: OpenAI 有個團隊做過一件事，整個 repo 不准有人手寫程式碼，每一行都得由 Codex 生成 (這就是他們上一篇 harness engineering 文章的主題)。做成之後，下一個瓶頸就出現了，而且不是模型不夠強，是<strong>人的注意力不夠用</strong>: 一個工程師同時顧 3 到 5 個 Codex session 就到極限，再多就開始忘記哪個 session 在做什麼，得在終端機之間跳來跳去。他們等於是組了一隊很能幹的初級工程師，卻得把資深工程師全部拿去盯著這些 session 跑。</p>

<p>Symphony 的解法是換一個控制單位。不要再以「session」和「PR」為中心去管理，改以「任務」為中心: 把 Linear 這類專案管理看板當成 coding agent 的<strong>控制平面</strong>，每一張開啟中的 ticket 都保證有一個 agent 在自己的工作區裡跑，一直跑到進入下一個狀態為止。Agent 掛了就重啟，有新 ticket 就派工。OpenAI 說某些團隊導入後，三週內合併的 PR 增加了 500%。</p>

<h3 id="看板就是狀態機">看板就是狀態機</h3>

<p>Symphony 的迴圈，核心是把看板的 ticket 狀態當成一台狀態機在驅動:</p>

<ul>
  <li><strong>狀態機</strong>: Symphony 盯著看板，有 ticket 進到 Todo 就派 agent，移到 Rework 就讓 agent 帶著 review 意見重做，進到終態就收掉。</li>
  <li><strong>任務 DAG</strong>: 大型工作不是一張 ticket。先派一個 agent 讀 codebase、Slack、Notion 產出實作計畫，再展開成一棵帶依賴關係的任務樹，沒被 block 的任務自然平行跑。文章舉的例子是: React 升級被標成 block 在 Vite 遷移上，agent 就真的等遷移完才動 React。</li>
  <li><strong>Agent 自己開 ticket</strong>: 實作或 review 中發現的效能問題、重構機會，agent 直接開一張新 ticket 排程，而不是塞進當前任務。</li>
  <li><strong>交付的是工作成果證明 (proof of work)</strong>: agent 不只交 code，還附 CI 狀態、PR review 回饋、甚至一段操作示範影片。一句話總結這套設計的精神: agent 的目標不是「寫完 code」，而是「說服人類把這段程式碼合併」。</li>
</ul>

<p>值得補一句的是，Symphony 的「任務」不必然是寫 code。OpenAI 自己就說，有些 ticket 是純粹的調查或分析，從頭到尾不碰 codebase; 你也可以開一張「分析這個資料、產一份報告」的 ticket 交給它。看板這層抽象一旦確立，它管的就是「要完成的工作」，而不是「要寫的 code」。從這個角度看，Symphony 比較像一套組織級的外層 harness。</p>

<svg viewBox="0 0 700 252" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OpenAI Symphony 運作原理示意圖">
<defs>
<marker id="symA" markerWidth="11" markerHeight="9" refX="9" refY="3.2" orient="auto"><path d="M0,0 L9,3.2 L0,6.4 Z" style="fill:var(--text-secondary)" /></marker>
</defs>
<rect x="22" y="8" width="656" height="34" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="350" y="30" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:700">🎼 Symphony: 看板當控制平面，每張開啟的 ticket 都配一個 agent 在自己的 workspace 跑</text>
<rect x="24" y="54" width="150" height="24" rx="5" style="fill:var(--surface);stroke:var(--border);stroke-width:1" />
<text x="99" y="70" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px;font-weight:600">Todo</text>
<rect x="184" y="54" width="166" height="24" rx="5" style="fill:var(--surface);stroke:var(--border);stroke-width:1" />
<text x="267" y="70" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px;font-weight:600">In Progress</text>
<rect x="360" y="54" width="150" height="24" rx="5" style="fill:var(--surface);stroke:var(--border);stroke-width:1" />
<text x="435" y="70" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px;font-weight:600">Review</text>
<rect x="520" y="54" width="156" height="24" rx="5" style="fill:var(--surface);stroke:var(--border);stroke-width:1" />
<text x="598" y="70" text-anchor="middle" style="fill:var(--text-secondary);font-size:11px;font-weight:600">Done</text>
<rect x="32" y="86" width="134" height="40" rx="6" style="fill:var(--bg);stroke:var(--border);stroke-width:1.2" />
<text x="99" y="103" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-12</text>
<text x="99" y="118" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">待派工</text>
<rect x="32" y="132" width="134" height="40" rx="6" style="fill:var(--bg);stroke:var(--border);stroke-width:1.2" />
<text x="99" y="149" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-13</text>
<text x="99" y="164" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">待派工</text>
<rect x="192" y="86" width="150" height="40" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.4" />
<text x="267" y="103" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-9</text>
<text x="267" y="118" text-anchor="middle" style="fill:var(--accent);font-size:9.5px;font-weight:600">🤖 agent A 實作中</text>
<rect x="192" y="132" width="150" height="40" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.4" />
<text x="267" y="149" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-10</text>
<text x="267" y="164" text-anchor="middle" style="fill:var(--accent);font-size:9.5px;font-weight:600">🤖 agent B 實作中</text>
<rect x="368" y="86" width="134" height="40" rx="6" style="fill:#fff8c5;stroke:#9a6700;stroke-width:1.4" />
<text x="435" y="103" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-7</text>
<text x="435" y="118" text-anchor="middle" style="fill:#9a6700;font-size:9px">PR + 證明影片，等人審</text>
<rect x="528" y="86" width="140" height="40" rx="6" style="fill:#dafbe1;stroke:#1a7f37;stroke-width:1.4" />
<text x="598" y="103" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">TICKET-3</text>
<text x="598" y="118" text-anchor="middle" style="fill:#1a7f37;font-size:9.5px">✓ merged</text>
<line x1="40" y1="200" x2="660" y2="200" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#symA)" />
<text x="350" y="220" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">ticket 狀態由左往右流動 = 狀態機; 沒被 block 的各配一個 agent 平行跑</text>
<text x="690" y="244" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<blockquote>
  <p>編按: 跟外層 Loop 的主題稍微岔開，但 Symphony 有個細節值得一記: 它開源出來的核心是一份 <code class="language-plaintext highlighter-rouge">SPEC.md</code>，不是實作的程式碼。這份 spec 是從內部實作逆向整理出來的 (一開始只是 tmux 裡一個 Codex session 在輪詢 Linear),OpenAI 還為了讓這份 spec 更完整，讓 Codex 用 Elixir、TypeScript、Go、Rust、Java、Python 各實作一遍; 官方建議的用法第一條，就是把 spec 交給你自己的 agent、用你選的語言實作一個。當實作隨時能被 agent 重建，真正的資產就從 code 移到了那份定義「要解什麼問題、邊界在哪」的 spec。這個重點系列後面還會再談到。</p>
</blockquote>

<h2 id="cron-排程和-heartbeat">Cron 排程和 Heartbeat</h2>

<p>第三種實作最輕量、也最通用: 自動觸發。不靠你手動下指令，而是設一個觸發條件，條件一到就自動把一段 prompt 送出去跑一輪。它輕到不必另外建任何東西: Claude Code、Codex 這些 coding agent 都已經把它做成內建功能 (後面會講到的 <code class="language-plaintext highlighter-rouge">/loop</code>、Routines、Automations 就是)，設一行排程就能用，這也是三種裡最容易上手的一種。</p>

<p>觸發的節奏分兩種。一種是<strong>定時</strong>: cron 排程，時間到就跑; 這種通常還會避免重疊，上一輪還在跑就跳過這次、不疊著開。另一種是<strong>讓 agent 自己決定下一次何時跑</strong>: 沒事就把間隔拉長、有狀況就縮短。(也可以不看時間、改綁事件: webhook、新郵件、CI 失敗就觸發。)</p>

<p>而這層真正的設計重點在另一條軸: 「每次觸發要不要沿用上一次的 context」。這條分界把自動觸發分成差很大的兩種:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:22px 0;">
<div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:8px;">每次獨立 context</div>
<div style="font-size:.92em;color:var(--text-secondary);margin-bottom:8px;">時間到就開一個全新的 agent run (cron + headless、雲端排程)。乾淨的 context，進度靠外部狀態傳遞，跟 Ralph 同款。</div>
<div style="font-size:.85em;color:var(--text-secondary);">例: 定時產一份情報簡報的研究 agent、Symphony 的每個 worker。</div>
</div>
<div style="flex:1 1 280px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">沿用 context: Heartbeat</div>
<div style="font-size:.92em;color:var(--text-secondary);margin-bottom:8px;">定時把一段 prompt 送回同一個長駐 session。記憶連續、反應快，但 context 會一直變大。</div>
<div style="font-size:.85em;color:var(--text-secondary);">例: OpenClaw 那種 24/7 主動助理，定時 heartbeat 醒來看 inbox、行事曆、通知。</div>
</div>
</div>

<svg viewBox="0 0 700 300" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Cron 排程兩種模式: 獨立 context 與 heartbeat">
<defs>
<marker id="crnA" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
<marker id="crnS" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker>
</defs>
<circle cx="58" cy="150" r="26" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.6" />
<line x1="58" y1="150" x2="58" y2="134" style="stroke:var(--accent);stroke-width:1.6" />
<line x1="58" y1="150" x2="70" y2="150" style="stroke:var(--accent);stroke-width:1.6" />
<text x="58" y="196" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">cron 觸發</text>
<text x="58" y="210" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">時間到就跑</text>
<path d="M84,138 C122,116 144,94 180,86" style="fill:none;stroke:var(--accent);stroke-width:1.4;stroke-dasharray:4 3" marker-end="url(#crnA)" />
<text x="120" y="104" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">開新的</text>
<path d="M84,162 C122,186 144,208 180,216" style="fill:none;stroke:var(--accent);stroke-width:1.4;stroke-dasharray:4 3" marker-end="url(#crnA)" />
<text x="120" y="210" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">回同一條</text>
<text x="430" y="28" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:700">每次獨立 context (cron + headless / 雲端排程)</text>
<rect x="192" y="56" width="120" height="44" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="252" y="76" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">t1 全新 run</text>
<text x="252" y="92" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">乾淨的 context</text>
<rect x="362" y="56" width="120" height="44" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="422" y="76" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">t2 全新 run</text>
<text x="422" y="92" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">乾淨的 context</text>
<rect x="532" y="56" width="120" height="44" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.3" />
<text x="592" y="76" text-anchor="middle" style="fill:var(--text);font-size:10.5px;font-weight:600">t3 全新 run</text>
<text x="592" y="92" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">乾淨的 context</text>
<text x="430" y="120" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">各自了結，結果送出後 context 就丟掉; 進度靠外部狀態傳遞</text>
<line x1="150" y1="144" x2="668" y2="144" style="stroke:var(--border);stroke-width:1;stroke-dasharray:3 3" />
<text x="430" y="172" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:700">Heartbeat: 沿用 context (/loop · thread automation)</text>
<text x="252" y="192" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">t1</text>
<text x="430" y="192" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">t2</text>
<text x="608" y="192" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">t3</text>
<path d="M252,196 V215" style="fill:none;stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#crnS)" />
<path d="M430,196 V215" style="fill:none;stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#crnS)" />
<path d="M608,196 V215" style="fill:none;stroke:var(--text-secondary);stroke-width:1.4" marker-end="url(#crnS)" />
<rect x="190" y="216" width="478" height="60" rx="8" style="fill:var(--surface);stroke:var(--accent);stroke-width:1.5" />
<text x="210" y="237" style="fill:var(--text);font-size:10.5px;font-weight:600">同一條 session 一直活著</text>
<polygon points="206,268 206,258 648,248 648,268" style="fill:rgba(9,105,218,.15);stroke:var(--accent);stroke-width:0.8" />
<text x="427" y="264" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">context 每次都更長 →</text>
<text x="690" y="295" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>這條「每次獨立、還是沿用 context」的分界，Codex 的 <a href="https://developers.openai.com/codex/app/automations">Automations</a> 乾脆直接做成兩種類型。一種是 <strong>standalone automation</strong>: 照排程 (可填 cron 語法) 起一個全新的 run，結果進一個叫 Triage 的收件匣，有發現才進來、沒發現就自動封存 (這個「沒東西就封存」跟前面 Symphony 沒發現就歸檔是同個設計)。另一種是 <strong>thread automation</strong>，官方原話就是「heartbeat 式的定時喚醒，綁在當前這條 thread 上」: 定時回到同一段對話、保留 context，適合盯著一個長命令跑完、或定時回來推進一個 review 迴圈。同一個產品，把「要不要沿用 context」這個設定直接做成兩個選項。它還多給一個獨立的選擇: 在 git repo 裡，automation 可以跑在你的本地 checkout，也可以開一個獨立 worktree 隔離，免得動到你手上還沒寫完的東西。</p>

<p>Claude Code 則把這兩種拆在不同地方。heartbeat 式的是 session 內的 <code class="language-plaintext highlighter-rouge">/loop</code>: 它<a href="https://code.claude.com/docs/en/scheduled-tasks">綁在單一 session、只在空檔執行</a>，錯過的觸發不會補跑 (no catch-up)，適合「定時看一眼、有事才處理」的輕量定時檢查，不適合非得準時、不能漏的硬排程。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/loop 5m 檢查 deploy 狀態, 失敗就修       ← 固定間隔
/loop 盯著 CI, 紅了就處理                  ← 動態間隔: 模型自己決定下次何時跑
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">/loop</code> 剛好上面兩種節奏都給你: 給間隔 (<code class="language-plaintext highlighter-rouge">5m</code>) 就定時跑，不給就交給模型自己決定下次何時跑。要每次一份乾淨 context 的那種，則要跳出 session，改用跑在 Anthropic 雲端、每次一份全新 clone 的 <a href="https://code.claude.com/docs/en/routines">Routines</a> (用 <code class="language-plaintext highlighter-rouge">/schedule</code> 建)。對自建 agent 來說，選哪一種是你得自己決定的設計取捨: 每次給乾淨 context 的那種，進度全靠外部狀態; 長駐 session 則記憶連續、但 context 一直變長。</p>

<p>要強調的是，<strong>排程這層只管「什麼時候跑」，它完全不判斷「做到什麼程度才算完」。</strong> 一個 cron 就是時間到了把 prompt 送出去，至於這一輪該不該停、夠不夠好，排程一概不管。這就帶到外層 Loop 一個很重要、卻常被混為一談的觀念。</p>

<h2 id="goal-和-loop-可以一起用">Goal 和 Loop 可以一起用</h2>

<p>社群常把 <code class="language-plaintext highlighter-rouge">/goal</code> 和 <code class="language-plaintext highlighter-rouge">/loop</code> 混著講，好像是兩個競爭的東西。其實它們管的是兩件不同的事，彼此不衝突，可以分開用，也可以組合起來用:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>管什麼</th>
      <th>回答的問題</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Loop</strong> (Ralph／Symphony／cron)</td>
      <td>scheduling 排程</td>
      <td><strong>什麼時候</strong>跑、多久跑一次</td>
    </tr>
    <tr>
      <td><strong>Goal</strong> (上一篇)</td>
      <td>termination 終止</td>
      <td>做到<strong>什麼程度</strong>才算完、才能停</td>
    </tr>
  </tbody>
</table>

<p>看懂這個分工，就有兩種組合用法。第一種是<strong>疊起來</strong>: 這一篇講的三種外層 loop，每一個都可以掛上一個上一篇講的可驗證 Goal。Ralph 的每一圈、Symphony 的每張 ticket、cron 的每次觸發，都能配一個停止條件，變成「時間到就啟動、做到驗證過才停」的工作 (run-to-completion job)。loop 是外圈 (管排程),goal 是內圈 (管終止)。</p>

<p>第二種是<strong>串接</strong>: loop 偵測到事件、把工作推進某個 queue 就結束，另外派一個帶 goal 的任務去把它做完。前者管「發現工作、何時啟動」，後者管「把這件事做到通過驗證」。</p>

<p>關鍵是別把兩者混為一談。一個只有 loop 沒有 goal 的迴圈，就是前面講的 Ralph 那種問題 (一直重跑到圈數上限才停); 一個只有 goal 沒有 loop 的任務，則被綁在單一 session 裡，跨不過 context 的上限。兩個各自獨立的設定，可以分開調，也可以一起調。</p>

<h2 id="把自己移出迴圈">把自己移出迴圈</h2>

<p>三種實作講完，回到開頭那句廣為流傳的話。Peter Steinberger 說的是:</p>

<blockquote>
  <p>「這是你每月一次的提醒: 你不應該再去提示寫程式的代理人了。你應該要設計『讓你的代理人被提示』的迴圈。」</p>
</blockquote>

<p>Boris Cherny 講得更具體，說他現在的工作就是寫 loop: 不再自己提示 Claude，而是讓跑著的 loop 去提示 Claude、去決定要做什麼。他甚至給過數字: 過去 30 天他對 Claude Code 的貢獻，100% 是 Claude Code 自己寫的，合併了 259 個 PR (這些說法和數字，Matt Van Horn 在 <a href="https://x.com/mvanhorn/status/2063865685558903149">WTF Is a Loop</a> 裡整理得很完整)。他把這套做法濃縮成幾句: 把 loop 寫一次，給它可呼叫的 skills 和能自我檢查的回饋，設好上限讓它會停，然後放上 cron 跑。一個能直接照抄的起步範例長這樣:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/loop 幫我代管所有 PR。自動修 build 問題,
      等有留言進來時,用 worktree agent 來修正它們。
</code></pre></div></div>

<h3 id="實例-一個-maintainer-orchestrator">實例: 一個 Maintainer Orchestrator</h3>

<p>把這套講得最完整的實例，是 Steinberger 開源的 <a href="https://github.com/steipete/agent-scripts/blob/main/skills/maintainer-orchestrator/SKILL.md">maintainer-orchestrator</a> skill。他<a href="https://x.com/steipete/status/2064998499780084154">在推文上的描述</a>是: 告訴 codex 維護你的 repos，每 5 分鐘醒來，把工作派到各個 thread; 配上 triage、autoreview、computer use 幾個 skill，部分工作可以全自動合併。</p>

<p>拆開看，它正好把這篇三種實作疊在一起用:</p>

<ul>
  <li><strong>它是個 cron + orchestrator</strong>: orchestrator 本身只當控制平面，每 5 分鐘 heartbeat 醒來讀各 worker 現況，只負責定時檢查、派工、監控、需要時問人決策。實作全部交給平行跑的 worker thread，而且 worker 不准再往下委派。</li>
  <li><strong>先過一道 triage</strong>: 另一個 <a href="https://github.com/steipete/agent-scripts/blob/main/skills/github-project-triage/SKILL.md">github-project-triage</a> skill 把進來的 queue 分三類: 可重現、有界、可驗證的歸 <strong>Autonomous</strong> 直接派工; 需要產品決策、牽涉安全、或缺權限的歸 <strong>Needs owner</strong>; 過期、重複的歸 <strong>Defer/close</strong>，附證據處置。</li>
</ul>

<p>這套之所以可以讓「部分工作全自動合併」，不是因為大膽，而是靠 harness 設計。它用四道關卡限制自動合併:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">📋 Decision-Ready Rule</div>
<div style="font-size:.9em;color:var(--text-secondary);">不拿半成品問 owner。先做到可合併、CI 綠、live proof 完成，owner 只需回答合併還是刪除。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">🔬 Live Proof Gate</div>
<div style="font-size:.9em;color:var(--text-secondary);">合併前必須對真實環境驗證 (真帳號、真服務、真裝置)。mocks、fixtures、CI 都不能取代 live proof。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">🔑 權限分層</div>
<div style="font-size:.9em;color:var(--text-secondary);">triage、實作、push、merge、release 是分開的授權。沒拿到就停在邊界、回報確切的下一步。</div>
</div>
<div style="flex:1 1 240px;border:1px solid var(--border);border-radius:8px;padding:14px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--accent);margin-bottom:4px;">📜 跨 session 記憶</div>
<div style="font-size:.9em;color:var(--text-secondary);">orchestrator 獨佔一份 markdown，派工、決策、release 全留底。跟 Ralph 的 progress.txt 是同一個做法。</div>
</div>
</div>

<p>注意這四道關卡，沒有一個是這篇外層 Loop 才發明的: live proof 是時機③的獨立驗收 (上一篇 Outcome 那種「裁判真的去操作 artifact」)，權限分層和 decision-ready 是把「什麼能自動、什麼要回報」寫成前饋規則 (第二篇)，跨 session 記憶是 Ralph 那個進度搬到磁碟的做法。外層 Loop 真正的工作，是把前面幾篇拆過的這些設計，組成一個人不在現場也能安全跑的迴圈。</p>

<h2 id="外層-loop-不只用在寫程式">外層 Loop 不只用在寫程式</h2>

<p>前面這些例子，從 Ralph、Symphony、cron 到剛剛的 maintainer orchestrator，清一色都是建在 coding agent 上，因為那是工具生態最成熟的場景。但外層 Loop 的核心跟 coding 無關，而且它的變形非常多: 同樣是定時醒來的 agent，有人拿來整理收件匣、有人拿來監控競品、有人拿來每天產一份情報簡報、有人拿來跑一個全天候執行的主動助理。場景差很遠，做出來的樣子也差很遠，Ralph 的 bash 迴圈、Symphony 的看板、cron 排程都只是其中幾種。</p>

<p>但不管哪一種，核心其實就三個問題，你自建任何一個外層 Loop 都得回答:</p>

<ul>
  <li><strong>怎麼被觸發</strong>: 固定 cron、動態間隔、還是事件 (webhook、新郵件、CI 失敗、看板上多一張 ticket)?</li>
  <li><strong>醒來看什麼</strong>: 它一醒來，要讀哪些 context 之外的狀態，才知道進度到哪、有沒有新工作? (Ralph 讀 git 和 progress.txt,Symphony 讀看板，主動助理讀 inbox。)</li>
  <li><strong>做完把結果送去哪</strong>: 開一個 PR、寄一封簡報、送進 triage 收件匣，還是沒事就直接歸檔?</li>
</ul>

<p>把這三個問題對著你的場景回答一遍，就有一個外層 Loop 了，不管它做的是 Text-to-SQL 報表、知識庫監控，還是寫 code。這也是為什麼說它是更廣義的工程: 換掉場景，核心不變。</p>

<h2 id="收尾-loopcraft-的五層迴圈">收尾: loopcraft 的五層迴圈</h2>

<p>把這個系列走過的迴圈疊起來看，swyx 在 <a href="https://www.latent.space/p/ainews-loopcraft-the-art-of-stacking">loopcraft: the art of stacking loops</a> 用一張圖概括了「堆疊迴圈」這件事。由內到外疊了五層，每一層的跳出條件和時間尺度都不一樣:</p>

<p><img src="/assets/images/harness-engineering-6/loopcraft.png" alt="loopcraft: 由內到外五層巢狀迴圈，從 token 到開放探索" /></p>

<ul>
  <li><strong>① token loop</strong> (秒): 模型逐 token 取樣、接上、再取樣，出現 stop token 就停。這層在 harness 底下，是模型自己的解碼。</li>
  <li><strong>② agent turn</strong> (分): 呼叫工具、把結果讀回來，直到沒有工具要呼叫。就是第一篇講的那個「笨迴圈」，時機①的工具層回饋就發生在這一圈裡。</li>
  <li><strong>③ /goal loop</strong> (時): 跑、評、重試，直到判定目標達成。就是第五篇的時機③。</li>
  <li><strong>④ MetaLoop</strong> (天): swyx 給它的副標是「生迴圈的迴圈」，動作是開子代理、審查、汰除。這就是這一篇的時機④,Ralph、Symphony、cron、maintainer orchestrator 全都在這一層。</li>
  <li><strong>⑤ ???? loop</strong> (∞): 定目標、分配資源、汰除方向，沒有跳出條件，開放探索。swyx 自己都還沒給它名字。</li>
</ul>

<p>五層裡，①②是模型本身就給你的，③成熟的 coding agent 也幫你包好了。<strong>ihower 認為，loop engineering 這個詞最想強調的重點方向，就是第④層 MetaLoop，也就是這一篇講的外層 harness</strong>: 任務怎麼被觸發、跨 session 的進度怎麼傳、什麼時候算整件事做完，這些都得你自己設計。當然，如果你用的是 coding agent，要做這層最簡單的做法，就是善用它內建的 loop 排程和 goal 功能 (前面講過的 <code class="language-plaintext highlighter-rouge">/loop</code>、Routines、Automations 配上 <code class="language-plaintext highlighter-rouge">/goal</code>)，把排程和終止條件接起來，就不需要自己做了。</p>

<blockquote>
  <p>編按: LangChain 的 Sydney Runkle 在 <a href="https://x.com/sydneyrunkle/status/2066928783534289358">The Art of Loop Engineering</a> 給了一個不同的版本 (Agent loop、Verification loop、Event loop、Hill climbing loop)，前三層大致對應這個系列的四個時機，而她的第四層 Hill climbing (把生產環境的 trace 送回去修改 harness 本身的設定)，正好是下一篇要談的東西。</p>
</blockquote>

<blockquote>
  <p>編按: 寫這篇時本來想把 Claude Code 的 dynamic workflows 也一起講 (它一樣是開子代理、平行分流、審查、汰除這類外層編排)。一方面它自成一個大題目，小編之前也單獨寫過一篇了; 另外就是，站在「自行開發特定場景的 Agent」的角度，ihower 認為 dynamic workflows <strong>不實用</strong>: 它那套讓模型即時生成編排腳本、決定性 resume 的機制，是為了當「服務任何任務的通用 harness」才需要的，而自建垂直場景的 agent (vertical agent，例如 Text-to-SQL、訪談、RAG) 時，你本來就知道自己的場景，直接針對它手寫一套確定性的編排就好 (像本文 Symphony 那種貼合場景設計的控制流程)，不必靠模型每次即時生成，既便宜可靠又省 token。所以這篇就沒再展開，有興趣的可以看那篇: <a href="/2026/06/05/code-act-to-dynamic-workflows/">從 Code Act 到 Claude Code Dynamic Workflows</a>。</p>
</blockquote>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Workflow" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron (本篇) 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (7): 進階: 自我改進 Harness, Meta-Harness 與爬坡</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-7-self-improving/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (7): 進階: 自我改進 Harness, Meta-Harness 與爬坡" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-7-self-improving</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-7-self-improving/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <strong style="color:var(--text);">進階: 自我改進 Harness, Meta-Harness 與爬坡</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>前面六篇談了一整套用回饋約束 agent 的做法: 工具裡閉合的最小迴圈、使用者中途的 steering、單輪結束的驗收、跨 session 的外層 loop。但這些做法有個共通點一直沒挑明: 從頭到尾，動手改 harness 的都是同一個人，你。你寫 Ralph 的 prompt、定 Symphony 的看板規則、設 maintainer orchestrator 的四道關卡，agent 始終是在你定好的 harness 裡運作。</p>

<p>這一篇要問的，就是把這件事再往前推一步: <strong>如果連 harness 本身，都讓 agent 自己改呢?</strong> 也就是，讓 agent 根據自己跑出來的 trace 和 eval，回頭改自己的 harness。</p>

<p>LangChain 的 <a href="https://x.com/sydneyrunkle/status/2066928783534289358">Sydney Runkle</a> 把這層叫 hill climbing loop (爬坡迴圈)，擺在她那套四層迴圈的最外圈，意思就是把生產環境的 trace 拿回來，改 harness 本身的設定。它跟前三層 (對應這個系列的回饋時機) 的差別在於: 前三層是在一個既定的 harness 裡閉合迴圈，這一層是去動那個 harness。換個講法，一般的 loop engineering 問的是「agent 該不該繼續跑」，而自我改進的 harness 問的是「這一輪跑完之後，系統到底學到了什麼」。</p>

<p>先把一句話放在前面，這篇後面所有做法都以它為前提: <strong>自我改進不是讓 agent 想改什麼就改什麼。</strong> 真正能用的版本，都是在 eval、trace、版本控制和退步關卡 (regression gate) 的限制下，讓 agent 提出「可被驗證、可被回滾」的 harness 變更。少了這些限制，所謂的自我改進，跟讓 agent 自動把 harness 改壞，其實分不太出來。後面三條路講的花樣再多，底下這幾項缺一個，自我改進都不成立:</p>

<div style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:1.5em 0;font-size:.9em;line-height:1.95;">
<div style="font-weight:600;margin-bottom:8px;color:var(--accent);">📋 受控改版的必要條件</div>
<strong>固定評測集</strong> (拿來爬坡的題目) · <strong>Regression set</strong> (每個修好的失敗都變成永久測試) · <strong>Production failure trace</strong> (真實世界跑出來的新失敗) · <strong>版本化的 prompt / 工具 / schema</strong> (改動可追蹤、可比較) · <strong>晉級關卡 (promotion gate)</strong> (主任務沒變好、或舊案例退步就不收) · <strong>回滾機制</strong> (收錯了能退回去) · <strong>高風險改動的人工 review</strong>
</div>

<p>這也是為什麼在這一篇，Goodhart 問題要比前幾篇更早提防: 一旦 agent 會根據 eval 改 harness，它就有動機把 eval 本身當成目標，而不是把真實任務做好。後面每一條路都會再遇到這個問題。</p>

<h2 id="誰在改-harness">誰在改 Harness?</h2>

<p>先把「改 harness」這件事的層次分清楚。Harrison Chase 在 <a href="https://x.com/hwchase17/status/2040471961206214864">Continual learning for AI agents</a> 裡把一個 agent 系統拆成三層，而「持續學習」可以發生在任何一層:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:24px 0;">
<div style="flex:1 1 200px;border:1px solid #cf222e;border-radius:8px;padding:16px 18px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">🧠 Model 模型層</div>
<div style="font-size:.9em;color:var(--text);">改的是權重 (SFT、RL)。這是大廠的事，還卡著 catastrophic forgetting 這個未解的研究難題，自建 agent 多半碰不到。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">⚙️ Harness 層</div>
<div style="font-size:.9em;color:var(--text);">驅動 agent 的程式、固定的 prompt 與工具。本篇主角，agent 可以自己動它。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:6px;">📄 Context 層</div>
<div style="font-size:.9em;color:var(--text);">harness 之外、用來配置它的指示、skills、記憶。agent 也能自己更新 (OpenClaw 把這個叫「做夢 dreaming」)。</div>
</div>
</div>

<p>模型層動不了，但 harness 層和 context 層，agent 是有可能自己改的。為什麼這件事突然變得值得認真談? 因為前面講了五篇，harness 不再是模型的附屬品，它本身就是決定表現的一大關鍵 (第二篇那個「同一個模型換 harness、Terminal Bench 從三十幾名進到前五」的對照)。既然 harness 這麼重要、又是純文字和程式碼，那「誰來改它、能不能自動改」自然就成了下一個問題。</p>

<p>LangChain 的 Viv Trivedi 描述過一個更大的循環，叫 <a href="https://x.com/Vtrivedy10/status/2039872562662941118">Model-Harness Training Loop</a>: harness 裡被驗證有用的元件會被產品化，下一代模型帶著這些 harness 一起後訓練，模型變強之後 harness 再演化。模型和 harness 是互相塑造的。這個大循環裡，廠商那半 (把 harness 訓練進模型) 是最後一篇的題目; 這一篇先看靠近我們這半: 自建 agent 怎麼讓自己的 harness 自動變好。</p>

<h2 id="三條路一個共同前提">三條路，一個共同前提</h2>

<p>整理社群這半年的做法，讓 harness 自我改進大致有三條路:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">📡 路一: 讀 Production Traces</div>
<div style="font-size:.9em;color:var(--text-secondary);">線上的失敗案例做錯誤分析，沉澱成新的 Judge 與規則，插回前面的回饋時機。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">🔧 路二: Agent 當優化器</div>
<div style="font-size:.9em;color:var(--text-secondary);">任何可快速評估的指標，都能交給 coding agent 用「修改 → 評估」迴圈爬坡。</div>
</div>
<div style="flex:1 1 200px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">📚 路三: Self-Improving Skills</div>
<div style="font-size:.9em;color:var(--text-secondary);">把每輪學到的教訓寫回 skill，經驗沉澱成可重用的能力。</div>
</div>
</div>

<p>三條路長得不一樣，但站在同一個前提上: <strong>要有一個能自動打分的 eval。</strong> 這不是口號。小編先前寫的 <a href="https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer/">Coding Agent 作為軟體優化器</a> 整篇都在講同一件事: 當實驗能跑得比人快 100 倍，eval 就變成整條迴圈的瓶頸，指標一旦能被鑽漏洞或量錯東西，模型就會在評測數字上變強、實際上線卻變差。這篇的三條路，每一條的成敗最後都會回到這個前提上，所以後面會反覆提到它。先從第一條看起。</p>

<h2 id="路一-從-production-trace-到新的-judge">路一: 從 Production Trace 到新的 Judge</h2>

<p>第一條路最直接: 把線上跑出來的失敗，變成新的把關規則。</p>

<p>為什麼非得靠 production 不可? 因為離線 eval 蓋不住真實世界的無界輸入。你上線前準備的測試資料集，涵蓋的是你「想得到」的情況; 但使用者會用你完全想不到的方式使用產品，而那些案例，離線 dataset 裡一題都沒有。</p>

<p>這裡有個觀察小編覺得蠻到位，姑且叫它「請勿」標語定律: prompt 裡每一條「不可以做 X」，就像現實中的「請勿XXX」標語，一定都是因為有人真的做過，而且你一定想不到竟然有人會去做。換句話說，<strong>你的 system prompt 禁止清單，就是你的事故報告史。</strong> 而新的事故，只會發生在 production。這也呼應第二篇的 ratchet 心法: 你只在看到真實失敗時才加一條約束。差別只在，離線時代的「真實失敗」靠你自己測出來，上線後的「真實失敗」藏在 trace 裡。</p>

<p>於是 debug 的方式也變了，從「讀程式碼找邏輯錯誤」變成「分析 traces」。你要抓的不再是某一行寫錯，而是推理出錯、工具用得很沒效率、決策品質不好這類問題，這些在程式碼裡根本看不出來。</p>

<p>怎麼在生產規模下分析 trace、該看哪些、報告怎麼寫，小編之前在 <a href="https://blog.aihao.tw/2026/06/02/agent-trace-analysis/">如何用 AI 分析 Agent traces?</a> 整篇講過，這裡不重複。ihower 自己的做法是把這套用 <code class="language-plaintext highlighter-rouge">braintrust-cli</code> (或 <code class="language-plaintext highlighter-rouge">langfuse-cli</code>) 包成一個 skill，一句話啟動「review 最近 24 小時的 trace，產生一份品質報告」，裡面寫死了加權取樣規則和固定的報告格式。包成 skill 的好處之一，正好接回上一篇: 它可以排程定期跑，掛上 <code class="language-plaintext highlighter-rouge">/loop</code> 或 cron，讓「分析自己」這件事本身也納入外層 loop。</p>

<p>這篇要補的是後半段: 分析完之後，那份報告怎麼變成 harness 的永久改進。閉合迴圈分三步:</p>

<svg viewBox="0 0 700 250" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="從 trace 到新 Judge 的閉合迴圈">
<defs>
<marker id="j6A" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" style="fill:var(--accent)" /></marker>
</defs>
<rect x="20" y="40" width="180" height="76" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="110" y="68" text-anchor="middle" style="fill:var(--text);font-size:13px;font-weight:700">1️⃣ 發現</text>
<text x="110" y="89" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">production traces 裡</text>
<text x="110" y="104" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">找到新的失敗模式</text>
<rect x="260" y="40" width="180" height="76" rx="8" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="350" y="68" text-anchor="middle" style="fill:var(--text);font-size:13px;font-weight:700">2️⃣ 沉澱</text>
<text x="350" y="89" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">失敗案例加進 dataset</text>
<text x="350" y="104" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">人工標註，做出對齊的 Judge</text>
<rect x="500" y="40" width="180" height="76" rx="8" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="590" y="64" text-anchor="middle" style="fill:var(--accent);font-size:13px;font-weight:700">3️⃣ 部署</text>
<text x="590" y="84" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">新 Judge 插回三時機之一</text>
<text x="590" y="99" text-anchor="middle" style="fill:var(--text-secondary);font-size:10.5px">工具內 / 單輪 / 外層 loop</text>
<text x="590" y="111" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px"> </text>
<line x1="200" y1="78" x2="258" y2="78" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#j6A)" />
<line x1="440" y1="78" x2="498" y2="78" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#j6A)" />
<path d="M590,116 V170 H110 V118" style="fill:none;stroke:var(--accent);stroke-width:1.5;stroke-dasharray:5 3" marker-end="url(#j6A)" />
<text x="350" y="163" text-anchor="middle" style="fill:var(--accent);font-size:11px;font-weight:600">監控新 Judge → 又抓到新的失敗模式 → 回到 1️⃣</text>
<text x="350" y="210" text-anchor="middle" style="fill:var(--text);font-size:11.5px;font-weight:600">trace 是證據，Judge 是產出，harness 負責執行</text>
<text x="690" y="242" text-anchor="end" style="fill:var(--text-secondary);font-size:10px">✏️ 小編製圖</text>
</svg>

<p>第三步「部署」是這篇跟前幾篇接得最緊的地方: 沉澱出來的新 Judge，不是擺著看，而是插回前面講過的幾個自動時機。是一個確定性檢查，就放進工具層 (時機①); 是判「整輪做完了沒」的 rubric，就放進單輪驗收 (時機③); 是跨 session 的品質監控，就掛在外層 loop (時機④)。然後監控它、看它有沒有抓到新東西，再回到第一步。這就是線上跟離線接起來的完整閉合迴圈。</p>

<p>要特別強調第二步那個「對齊」。前面幾篇反覆講過，Judge 要先對齊才有意義: 一個沒跟你的判斷校準過的 Judge，只是把你不信任的東西自動化。所以這條迴圈裡，人最該花力氣的不是第一步的「找」(那可以交給 trace 分析 agent)，而是第二步把失敗歸納成分類、做出一個你信得過的 Judge。這一點留到後面再談。</p>

<h2 id="路二-agent-當優化器用-eval-爬坡">路二: Agent 當優化器，用 Eval 爬坡</h2>

<p>第二條路把 harness 當成一段可編輯的程式碼，讓 coding agent 反覆改它、爬分。</p>

<p>「改一段程式 → 跑 → 讀指標 → 留好的丟壞的」這個優化迴圈，小編先前在 <a href="https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer/">Coding Agent 作為軟體優化器</a> 已經從 Karpathy 的 autoresearch 拆得很細，基本原理這裡不重複，只借 Karpathy 一句話概括: 「任何可以快速評估的指標，都可以交給 agent 來最佳化。」這篇只換一個對象: 把優化目標從訓練腳本、搜尋排序，換成 harness 本身。前提一樣是回饋: <strong>回饋夠好、能自動產生，爬坡才成立</strong>，這也是為什麼 coding 類任務最先成功 (測試好寫，等於有可驗證的獎勵)，而 UI/UX 至今還難 (視覺評估弱，撐不起爬坡)。</p>

<h3 id="hill-climbing-的配方">Hill-Climbing 的配方</h3>

<p>直接拿 harness 來爬坡，Viv 在 <a href="https://x.com/Vtrivedy10/status/2041927488918413589">Better Harness: A Recipe for Harness Hill-Climbing with Evals</a> 給了一份很完整的配方。它的核心心智模型蠻好記: 把 eval 當成 agent 的訓練資料。</p>

<blockquote>
  <p>在傳統機器學習裡，是「模型 + 訓練資料 + 梯度下降 → 更好的模型」; 對 agent 來說，就是「harness + evals + harness engineering → 更好的 agent」。每一個 eval 案例都貢獻一個訊號 (這一步動作對不對、產出對不對)，這個訊號引導下一次該怎麼改 harness。</p>
</blockquote>

<p>既然 eval 是訓練資料，那 ML 訓練的紀律就整套搬得過來，而其中最關鍵的一條是切出 holdout set:</p>

<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;margin:1.5em 0;font-size:.92em;">
<div style="display:flex;background:var(--surface);font-weight:600;">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);">步驟</div>
<div style="flex:1 1 78%;padding:10px 12px;">在做什麼</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">蒐集並標記</div>
<div style="flex:1 1 78%;padding:10px 12px;">手寫 + 從 production trace 挖 + 改寫外部資料集; 每個 eval 標上行為分類 (tool selection、multi-step reasoning…)</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">切分資料</div>
<div style="flex:1 1 78%;padding:10px 12px;">分成最佳化集 (拿來爬坡) 和 holdout 集 (留著驗收)。<strong>這步最重要</strong>: 自動爬坡天生會過擬合，holdout 是「真泛化」的代理</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">跑 baseline</div>
<div style="flex:1 1 78%;padding:10px 12px;">改任何東西之前先測一次，所有改進都對著這個基準量</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">最佳化</div>
<div style="flex:1 1 78%;padding:10px 12px;">每輪診斷 trace、提出一個有針對性的 harness 改動 (一次只改一處，避免混淆變因)</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">驗證不退步</div>
<div style="flex:1 1 78%;padding:10px 12px;">確認新改動讓新案例通過、又沒讓原本通過的退步; 有退步就把退步清單留著，下一輪一起修</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1 1 22%;padding:10px 12px;border-right:1px solid var(--border);font-weight:600;">人工審查</div>
<div style="flex:1 1 78%;padding:10px 12px;">上線前人看一遍，抓指標看不到的邊角，例如那種「對 holdout 沒壞處、但其實是浪費 token 的過擬合指令」</div>
</div>
</div>

<p>NeoSigma 開源的 <a href="https://github.com/neosigmaai/auto-harness">auto-harness</a> 把同一套思路跑成了一個完整的閉合迴圈，而且補上了一個關鍵機制: regression gate (退步關卡)。他們在 Tau3 bench 上，固定底層模型 (GPT-5.4) 不動，只靠改 harness，把驗證集分數 (val score) 從 0.560 推到 0.780，跑了 18 個批次、96 個 harness 實驗。機制是: 從 trace 挖失敗 → 依「根因」分群 (這步很關鍵，逼你針對底層原因，而不是逐個症狀補) → 把失敗變成可重複的 eval 案例 → 提出 harness 改動 → <strong>只有同時滿足「不讓任何已修好的失敗退步 (≥80%)」和「整體分數不退步」才收下，否則 revert</strong>。</p>

<p>退步關卡為什麼是讓收益不斷累積的關鍵? 因為每個被修好的失敗都變成一個永久的測試案例，系統就再也回不到比它已經解掉的更差的狀態。沒有這道關卡，你只是在原地反覆試錯，同樣的失敗一再出現; 有了它，每個改進都是疊加的，門檻只會越來越高。這跟傳統軟體工程的 TDD、回歸測試是同一個道理，只是這裡的測試案例是從 production 失敗累積出來的。NeoSigma 的 regression set 從 0 增加到 17 條，越往後，能通過關卡的候選改動越少 (後段大多被擋下或 revert)，正好說明問題越來越難、改進越來越紮實。</p>

<h3 id="順帶提醒-goodhart-還在">順帶提醒: Goodhart 還在</h3>

<p>reward hacking 在 <a href="https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer/">優化器那篇</a> 講過一大段，改 harness 時一樣存在，而且因為影響面更大反而更危險。Langfuse 那個例子最典型: agent 為了刷分，直接移除「執行前先讓使用者確認」這道人工核准關卡，因為自動測試環境裡沒有真人會按確認，留著就永遠拿 0 分，它還順手刪掉測試沒涵蓋的真功能。結論很簡單: <strong>沒被量到的東西，就會被刪掉。</strong> 所以這條路的人工審查不是可有可無的禮貌，而是擋住 agent 把 harness 改成「eval 上看起來很強、真實使用者面前很爛」的那道關卡。</p>

<h3 id="gepa-對齊之後才自動化">GEPA: 對齊之後才自動化</h3>

<p>如果你已經有了一個對齊過的 Judge，那其中「改 prompt」這種最常見的改動，可以交給更系統化的工具。<a href="https://github.com/gepa-ai/gepa">GEPA</a> 就是一個: 它拿 dev set 的分數加上具體的錯誤回饋當訊號，反思 execution traces (執行軌跡)，再用 Pareto search 自動搜尋更好的寫法。GEPA README 講的最佳化對象是廣義的「文字型參數」(prompt、instructions 這類純文字設定)，所以同一套方法原則上也能延伸到 judge 的 prompt、schema 描述，不只是主 prompt。</p>

<p>但要再講一次前提: GEPA 的最佳化目標，就是 Judge 給的分數。<strong>Judge 不準，自動化只會讓你更快地往錯的方向走。</strong> 先把 Judge 對齊，再談自動化 prompt，順序不能反。</p>

<h3 id="meta-harness-讓-agent-重寫整個-harness">Meta-Harness: 讓 agent 重寫整個 harness</h3>

<p>把最佳化的對象從 prompt 一路升級到「整個 harness」，就是 Stanford 的 <a href="https://yoonholee.com/meta-harness/">Meta-Harness</a> (論文名字直接叫 End-to-End Optimization of Model Harnesses)。它要動的不只是 prompt，而是系統 prompt、工具定義、判斷完成的邏輯、context 管理整套外圍程式。</p>

<p>它跟一般 prompt 最佳化方法最大的不同，在「提案者能看到多少東西」。多數方法把歷史壓縮成一段摘要或一個分數; Meta-Harness 給提案者 (就是一個 Claude Code) 一個檔案系統，裡面放著所有先前候選 harness 的完整原始碼、分數、和 execution traces，讓它用 grep、cat 自己去讀。每一步最多送進 1000 萬個 token 的診斷脈絡 (對照它盤點的其他方法，最多只有 2.6 萬)。所以它不是看摘要猜，而是直接讀原始軌跡做「反事實診斷」: 精準定位是哪一個 harness 決策造成了失敗，再提出針對性的新版，評測、存檔、重複。</p>

<p>成績裡有個數字，正好接回第二篇講過的一點。第二篇講過「同一個模型，換一套沒在訓練時見過的 harness，排名能從三十幾名進到前五」，當時是想說明 harness 是能讓表現跨級距的關鍵。Meta-Harness 在 TerminalBench-2 上，讓 Haiku 4.5 用演化出來的 harness 跑到 37.6%，在所有 Haiku 4.5 的 agent 裡排第 1 (當時榜單狀態，會隨新 agent 加入變動)。一個小模型靠 harness 跨了級距; 而且這次更進一步: 不只是換 harness 能跨級距，連那套 harness 都不是人寫的，是 agent 自己演化出來的。</p>

<p>這條「讓 agent 改 agent」的研究線，往前可以追到 Sakana 的 <a href="https://sakana.ai/dgm/">Darwin Gödel Machine</a> (把自我改進當成演化搜尋，SWE-bench 從 20% 演化到 50%) 和 Meta 的 <a href="https://github.com/facebookresearch/hyperagents">HyperAgents</a> (連負責改善的 meta-agent 自己都在被改的範圍內)。這兩個都還是研究系統，看的是這條路能走到多遠，不是能直接搬上 production 的方案。它們的共同前提一樣: <strong>結果可驗證的問題域。</strong> 沒有一個可靠的打分機制，這套自我演化就無從爬起。</p>

<h2 id="路三-把學到的寫回-skill">路三: 把學到的寫回 Skill</h2>

<p>第三條路的單位不是 trace、也不是分數，而是 skill。創投 Garry Tan 把它講成一個動詞，叫 <a href="https://x.com/garrytan/status/2046876981711769720">skillify</a>: 每次 agent 犯了錯，就把這個失敗變成一個帶測試的 skill，讓同樣的錯再也不可能發生。</p>

<p>這其實就是第二篇 ratchet 心法的延伸 (「每當你發現 agent 犯了一個錯，就做一個工程上的解法，讓它再也不會犯同一個錯」)，只是這裡的「工程解法」不只是 AGENTS.md 裡的一行規則，而是一整包帶測試的能力。Garry 給的是一份 10 步檢查清單，一個失敗要走完這 10 步才算被 skillify:</p>

<div style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:1.5em 0;font-size:.9em;line-height:1.9;">
<div style="font-weight:600;margin-bottom:8px;color:var(--accent);">📋 skillify 的 10 步</div>
SKILL.md 契約 → 確定性程式碼 → 單元測試 → 整合測試 → LLM evals → resolver 觸發條目 → resolver eval (驗路由有沒有導對) → DRY 重複稽核 → 端到端煙霧測試 → 歸檔規則
</div>

<p>清單裡有兩件事正好呼應前面幾篇。第一，第 2 步「確定性程式碼」: skill 教 agent 先用它的判斷力寫出一段確定性的腳本，之後就用那段腳本取代判斷，而不是每次都重新推理。這跟第三篇工具層的第一原則「能確定就不用 LLM」是同一句話，Garry 的例子是查日曆: 與其讓模型每次推理日期，不如讓它寫一個 grep 腳本，然後規則強制它去跑腳本。第二，清單裡有一半都是各種測試 (單元、整合、LLM eval、resolver eval、煙霧)，這又回到那個共同前提: 沒有 eval 把關的 skill，只是程式碼剛好今天能跑。</p>

<p>不過 skillify 不是銀彈，這點 LangChain 的 Viv 在<a href="https://x.com/Vtrivedy10/status/2046979341427331522">回應 Garry 那串</a>時講得蠻誠實，值得一起看:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:22px 0;">
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">Skill 太多，context engineering 反而出問題</div>
<div style="font-size:.9em;color:var(--text-secondary);">如果 skill 一多、彼此難以區分何時該用，負責路由的 resolver 就會失準，你又回到第一篇講的 context rot。可能需要 skill search、把相似的 skill 合併。</div>
</div>
<div style="flex:1 1 300px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">別困在 skill learning 的局部最佳 (local minima)</div>
<div style="font-size:.9em;color:var(--text-secondary);">skill 適合修有界、有範圍的問題; 但要解真正困難的長程任務，得從目標往回設計整個 harness 架構，光靠一個個補 skill 容易陷進局部最佳。</div>
</div>
</div>

<p>而 <a href="https://x.com/aparnadhinak/status/2042022750135709882">Aparna Dhinakaran</a> 點出的問題更根本: skills 會腐爛。今天寫好的 skill，六週後上游 API 改了結構，它就靜默地回傳垃圾; agent 自動生的 skill 可能 trigger 太弱永遠不被觸發，變成沒人用、白佔 index token 的廢檔; 或者 agent 週一生了 <code class="language-plaintext highlighter-rouge">deploy-k8s</code>、週四又從另一段對話生了 <code class="language-plaintext highlighter-rouge">kubernetes-deploy</code>，兩個都在、觸發詞相近，路由就開始亂選。她講得直接: 「沒有測試，任何 codebase 都會腐爛」，這是軟體工程在 2005 年就解決的問題，agent 的 skill 沒有不同。Nous 的 Hermes Agent 能讓 agent 自己創建、修補、刪除 skill，設計很漂亮，但它不測自己的 skill，沒有 resolver eval 驗路由、沒有稽核抓重複。</p>

<p>所以這條路的結論跟前兩條一樣: skillify 的關鍵不在「會不會寫 skill」，而在那 10 步裡的測試。用 Aparna 的話: 評估是讓 harness 自動建構自己的鷹架。沒有評估把關，自我改寫的 skill 只是把幻覺寫進長期記憶。</p>

<h2 id="自我改進-harness-的十層堆疊">自我改進 Harness 的十層堆疊</h2>

<p>前面三條路是三種切入，散在不同做法裡。AlphaSignal 把這個題目下的 28 篇論文<a href="https://alphasignalai.substack.com/p/10-layers-of-self-improving-harness">歸納成一套十層堆疊</a>，正好把這三條路、加上開頭那張「受控改版的必要條件」清單，展開成一套從基準到量測的完整結構。它的定調跟這篇一致: 自我改進不是「讓 agent 自己重寫自己」。對照靜態 harness 看最清楚: 靜態版一失敗只能等人工修補，改完就結束，系統不會從這次失敗累積出任何改進; 自我改進版則是模型凍結，失敗證據 → 改 harness → 過關卡 → 版本化 → 再回頭量 worker 有沒有真的變好，形成一個閉合迴圈，比較像替 agent 行為做 CI (持續整合)。</p>

<svg viewBox="0 0 720 152" style="width:100%;max-width:680px;height:auto;display:block;margin:24px auto 4px" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="靜態 Harness 流程">
<defs>
<marker id="stm1" markerWidth="9" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker>
</defs>
<text x="360" y="24" text-anchor="middle" style="fill:var(--text);font-size:14px;font-weight:700">靜態 Harness</text>
<text x="360" y="43" text-anchor="middle" style="fill:#cf222e;font-size:10.5px;font-weight:600">失敗不會累積成改進，系統學不到東西</text>
<rect x="12" y="68" width="120" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="72" y="95" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">基礎模型</text>
<line x1="132" y1="91" x2="154" y2="91" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#stm1)" />
<rect x="156" y="68" width="120" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="216" y="95" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">靜態 Harness</text>
<text x="216" y="132" text-anchor="middle" style="fill:var(--text-secondary);font-size:9px">prompt · tools · 記憶規則 · validators</text>
<line x1="276" y1="91" x2="298" y2="91" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#stm1)" />
<rect x="300" y="68" width="120" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="360" y="95" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">跑任務</text>
<line x1="420" y1="91" x2="442" y2="91" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#stm1)" />
<rect x="444" y="68" width="120" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="504" y="95" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">失敗</text>
<line x1="564" y1="91" x2="586" y2="91" style="stroke:var(--text-secondary);stroke-width:1.5;stroke-dasharray:5 3" marker-end="url(#stm1)" />
<rect x="588" y="68" width="120" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5;stroke-dasharray:5 3" />
<text x="648" y="95" text-anchor="middle" style="fill:var(--text-secondary);font-size:12px;font-weight:600">人工修補</text>
</svg>

<svg viewBox="0 0 720 290" style="width:100%;max-width:700px;height:auto;display:block;margin:36px auto 24px" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="自我改進 Harness 閉合迴圈">
<defs>
<marker id="sfm1" markerWidth="9" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker>
<marker id="sfm2" markerWidth="9" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" style="fill:var(--accent)" /></marker>
</defs>
<text x="360" y="26" text-anchor="middle" style="fill:var(--accent);font-size:14px;font-weight:700">自我改進 Harness</text>
<rect x="55" y="52" width="130" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="120" y="79" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">模型凍結</text>
<line x1="185" y1="75" x2="213" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#sfm1)" />
<rect x="215" y="52" width="130" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="280" y="79" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">失敗證據</text>
<line x1="345" y1="75" x2="373" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#sfm1)" />
<rect x="375" y="52" width="130" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="440" y="79" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">改 Harness</text>
<line x1="505" y1="75" x2="533" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#sfm1)" />
<rect x="535" y="46" width="130" height="58" rx="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" />
<text x="600" y="71" text-anchor="middle" style="fill:var(--accent);font-size:12px;font-weight:700">關卡 Gate</text>
<text x="600" y="89" text-anchor="middle" style="fill:var(--text-secondary);font-size:8.5px">held-out · 退步 · 回滾</text>
<line x1="600" y1="104" x2="600" y2="186" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#sfm1)" />
<rect x="535" y="188" width="130" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="600" y="215" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">版本化 Harness</text>
<line x1="535" y1="211" x2="347" y2="211" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#sfm1)" />
<rect x="215" y="188" width="130" height="46" rx="7" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" />
<text x="280" y="215" text-anchor="middle" style="fill:var(--text);font-size:12px;font-weight:600">Worker 效益檢查</text>
<line x1="280" y1="188" x2="280" y2="100" style="stroke:var(--accent);stroke-width:1.5" marker-end="url(#sfm2)" />
<text x="296" y="147" text-anchor="start" style="fill:var(--accent);font-size:10px;font-weight:600">新證據</text>
<text x="360" y="262" text-anchor="middle" style="fill:var(--text-secondary);font-size:9.5px">Self-Harness 40.5→61.9% · AHE 69.7→77.0% · SkillOpt 52/52 cells</text>
<text x="710" y="282" text-anchor="end" style="fill:var(--text-secondary);font-size:8.5px">✏️ 小編重繪，仿 AlphaSignal</text>
</svg>

<p>把上面這個自我改進的閉合迴圈拆解開，從最底層的固定基準一路到最上面選配的權重更新，一共十層:</p>

<div style="display:flex;flex-direction:column;gap:7px;margin:24px 0;">
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">1</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">穩定基底</strong> <span style="color:var(--text-secondary);">Stable Substrate · 釘住改 harness 時不准動的基準</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">2</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">執行軌跡</strong> <span style="color:var(--text-secondary);">Trace Log · 記下每個 tool call、重試、verifier 輸出與成本</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">3</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">外部狀態</strong> <span style="color:var(--text-secondary);">External State · 學習放進 skills、記憶、policy 等可回滾的檔案</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">4</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">失敗挖掘</strong> <span style="color:var(--text-secondary);">Failure Mining · 過濾 trace，依根因把失敗分群</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">5</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">提案引擎</strong> <span style="color:var(--text-secondary);">Proposal Engine · 把證據轉成具體、可驗證的候選改動</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1.5px solid var(--accent);border-radius:6px;padding:9px 14px;background:var(--tag-bg);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--accent);color:#fff;font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">6</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--accent);">驗證關卡</strong> <span style="color:var(--text-secondary);">Validation Gate · 過 held-out + 不退步才准晉級。沒有關卡就沒有自我改進</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">7</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">版本與回滾</strong> <span style="color:var(--text-secondary);">Versioning &amp; Rollback · 把 harness 當 repo，連被否決的改動都存檔</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--tag-bg);color:var(--accent);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">8</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text);">路由與變體</strong> <span style="color:var(--text-secondary);">Routing &amp; Variants · 依任務類別分出多個 harness，避免修法互相矛盾</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1.5px solid var(--accent);border-radius:6px;padding:9px 14px;background:var(--tag-bg);">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--accent);color:#fff;font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">9</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--accent);">效益量測</strong> <span style="color:var(--text-secondary);">Benefit Measurement · 量 worker 換新 harness 後實際有沒有變好</span></div>
</div>
<div style="display:flex;align-items:center;gap:12px;border:1px solid var(--border);border-radius:6px;padding:9px 14px;background:var(--surface);opacity:.85;">
<div style="flex:0 0 24px;height:24px;border-radius:50%;background:var(--surface);border:1px solid var(--border);color:var(--text-secondary);font-weight:700;font-size:12.5px;display:flex;align-items:center;justify-content:center;">10</div>
<div style="flex:1 1 auto;font-size:.92em;line-height:1.5;"><strong style="color:var(--text-secondary);">選配的權重更新</strong> <span style="color:var(--text-secondary);">Optional Weight Update · 前九層都穩了，才考慮動模型權重</span></div>
</div>
</div>

<p><strong>第 1-2 層: 先讓改動可被歸因。</strong> 第 1 層先釘住改 harness 時什麼不准動: 底層模型、runtime、工具、評測器、benchmark 切分。沒有固定的基準，分數一變就無從歸因到底是哪一項造成的。第 2 層留下完整 trace，每個 tool call、檔案讀寫、重試、verifier 輸出、成本都記下來，根因分析才有依據，而不是只看最後結果對不對 (對應路一的 trace 分析)。</p>

<p><strong>第 3-5 層: 把學習外部化，再提煉成提案。</strong> 第 3 層把學到的東西放在模型之外、可檢視可測試可回滾的地方 (skills、記憶檔、自然語言 policy、工具 wrapper)，論文裡的 SkillOpt 就是這條路，在 52 個評測 cell 上都拿到改進 (對應路三)。第 4 層過濾 trace，只挑「能教出可重用教訓」的失敗，再依根因和機制分群，而不是每筆 trace 一視同仁 (對應路一的錯誤分析、NeoSigma 的依根因分群)。第 5 層把證據轉成具體候選改動，每個提案都要寫清楚: 改了哪個元件、依據什麼證據、預期修好什麼、可能帶來哪些退步。</p>

<p><strong>第 6 層: 關卡，整套堆疊的關鍵。</strong> 改動只有通過 held-out 驗證、又沒造成退步，才准晉級。這是全文講得最重的一句: <strong>「沒有關卡，就沒有自我改進。」</strong> 這層正是 NeoSigma 的 regression gate、路二配方裡「驗證不退步」那一步。</p>

<p><strong>第 7-9 層: 管理、分流、量真實效益。</strong> 第 7 層把 harness 當成一個 repo 來管: diff、commit 歷史、還原，連被否決的改動也存下來，當未來的證據。第 8 層依任務類別、難度、失敗模式分出多個 harness 變體，而不是維護單一一份全域 policy，避免互相矛盾的修法累積在同一份 harness 裡。第 9 層最關鍵、也最常被跳過: 量的是「worker agent 換上新 harness 之後實際有沒有變好」，而不是拿「更新器本身好不好」當代理指標。</p>

<p><strong>第 10 層: 選配的權重更新。</strong> 最後一層是選配的，把 harness 改動再疊上模型權重更新; 建議只有在前面九層都已經可量測、能運作之後，才考慮動模型。</p>

<p>第 9 層的提醒，來自一篇標題就很直白的論文《Harness Updating Is Not Harness Benefit》(更新 harness 不等於 harness 有效益): 它發現不同 harness 更新器之間，本身能力差距其實很小 (最多 3.1 分)，但換到下游、不同模型不同 benchmark 上，實際效益差很多。換句話說，<strong>改了 harness 不等於 agent 真的變好。</strong> 如果 worker agent 載不動、跟不上、用不了那套新 harness，那個改動就只是一個好看的 diff。</p>

<p>所以整套十層，真正把「自我改進」跟「自動作弊」分開的，就是第 6 層的關卡和第 9 層的效益量測。用 AlphaSignal 的原話: 關卡才是產品; 沒有它，這個迴圈只會自動走向過擬合。面對任何自我改進的 demo，第一個該問的不是「它改了什麼」，而是「哪一道關卡收下了這個改動、收下之後 worker 真的變好了嗎」。</p>

<h2 id="三條路十層堆疊最後都回到-eval">三條路、十層堆疊，最後都回到 Eval</h2>

<p>不管是前面三條路，還是剛拆完的十層堆疊，擺在一起看，最後都指向同一個點: 那個能自動打分的 eval。trace 沉澱成 Judge，要 Judge 對齊; 爬坡要對著 holdout 和 regression gate; skillify 要那 10 步裡的測試; 連十層裡最關鍵的第 6、9 兩層，把關的也是 eval。harness 怎麼自我改進的花樣可以很多，但能不能真的變好，全卡在這一件事上。</p>

<blockquote>
  <p>編按: Viv 把這個心智模型<a href="https://x.com/Vtrivedy10/status/2045230994656305485">寫成一條公式</a>很傳神: <code class="language-plaintext highlighter-rouge">agent = fit(model, harness, evals)</code>。就像 sklearn 的 <code class="language-plaintext highlighter-rouge">fit(model, data)</code>,evals 是 agent 的訓練資料，每一個 eval 都在投票決定 harness 該怎麼改。差別只在，前沿大廠花幾百萬在模型訓練的資料品質上，而做 agent 的團隊，該把同等的心力投在 eval 的策展與設計上。</p>
</blockquote>

<p>而這正是自我改進 agent 最大的風險所在。AlphaSignal <a href="https://alphasignalai.substack.com/p/when-ai-agents-learn-to-engineer">另一篇講自我改進 agent 風險的整理</a>，後半很誠實地點出風險: 最大的問題是 reward hacking,agent 會一味衝高單一指標、鑽評分函數的漏洞，達標卻沒真正解決問題; 還有卡在局部最佳的傾向 (autoresearch 社群就觀察到 agent 常常只會保守地微調超參數，不去做真正創新需要的大膽架構改動); 另外還有失控把算力預算耗光、寫出不安全程式碼這些風險。</p>

<p>這些風險反過來說明了一件事: <strong>自我改進 agent 越強，設計好 eval、控管好安全風險、引導整個流程的資深工程師，反而越不可或缺。</strong> 當 agent 開始自己改自己，你的 grader 設計得好不好，直接決定它是真的變強、還是學會作弊。所以前面講的那些自動化機制，幾乎都把人留在某個關卡上: Better-Harness 明確有一步人工審查，NeoSigma 也保留了引入人工審查的選項。這跟這個系列從第一篇講到現在的態度一致: 工程師被移出的是逐步操作那幾圈，留在最外圈做的，是定義「什麼叫做好」並守住它。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Eval" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 (本篇) 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (8): 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-8-model-harness-fit/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (8): 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-8-model-harness-fit</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-8-model-harness-fit/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <strong style="color:var(--text);">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div>9. <a href="/2026/06/26/harness-engineering-9-agent-frameworks/">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</a></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>這篇是系列觀念部分的收尾。前面七篇談的能力、回饋時機、自我改進，其實都在回答同一個問題: <strong>在一個固定的模型上，怎麼把 harness 搭好。</strong></p>

<p>但有一個維度一直沒談到: 時間。模型會升級，而模型一升級，你辛苦做好的 harness 不見得還繼續有效。這一篇補上那條時間軸，替前面的觀念收尾;最後第 9 篇會轉到實作，談自己開發 agent 時怎麼選框架。</p>

<p>模型一升級會發生的事，其實是一體兩面: <strong>harness 跟模型綁在一起，而且它會過期。</strong> 拆開來看:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 280px;border:1px solid var(--accent);border-radius:8px;padding:16px 20px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">🔗 harness 綁在特定模型上</div>
<div style="font-size:.92em;color:var(--text);">同一套 harness 換一個模型，效果不見得一樣好。模型是針對 harness 做 post-training 的，不只是針對 API。</div>
</div>
<div style="flex:1 1 280px;border:1px solid #9a6700;border-radius:8px;padding:16px 20px;background:#fff8c5;">
<div style="font-weight:700;color:#9a6700;margin-bottom:8px;">⏳ harness 會過期</div>
<div style="font-size:.92em;color:var(--text);">模型每次變強，某些當初補強它的元件就不再有用，該被移除。</div>
</div>
</div>

<p>其中「會過期」這面比較直覺: harness 裡的元件，大多是在補某一代模型的某個短處。它容易忘記，所以加 memory; 它還不會自己規劃，所以加一份 todo 清單; 它不會自己驗收，所以加 grader; 它不熟某種編輯格式，所以替它包一層工具 (這幾樣剛好分別是前面幾篇談過的重點)。模型一變強，這些補強有的仍然必要，有的就變成阻力，難的是事先分不出來是哪一種。</p>

<p>兩件事背後其實是同一個機制: 模型和 harness 在一個共演化的迴圈裡互相塑造 (這個迴圈第七篇借 Viv 的 Model-Harness Training Loop 提過)。先從第一面看起: 為什麼 harness 換個模型就不靈。</p>

<h2 id="一套-harness換個模型就不靈了">一套 harness，換個模型就不靈了</h2>

<p>先看一個經驗證據。Terminal-Bench 2.0 這個 benchmark 有意思的地方，是它的排行榜不是只排「模型」，而是排「harness + 模型」的配對。同一個模型配上不同 harness，分數可以差好幾個百分點，差距甚至大過一個模型世代的升級。第二篇引過那個對照 (同一個 Opus，放在原廠 harness 排三十幾名，換進另一套進到前五)，就是這個 benchmark 上的事。</p>

<p>這裡有個乍看很矛盾的現象，Saurabh Shah 公開問過一個很多人心裡的疑惑: 怎麼可能有「別的 harness」，在某個頂級能力上，比「模型當初 RL 訓練時就對著用的那個 harness」還能幫到模型? 模型不是應該對自家 harness 最順手嗎?</p>

<p>這個問題要分兩步回答。第一步先講清楚「為什麼模型對自家 harness 特別順手」，也就是 Model-Harness-Fit (講的是 harness 的工具格式、回饋節奏、上下文策略、驗收方式，貼不貼合當下這個模型的訓練分布與能力邊界); 第二步再回頭解這個看似矛盾的現象。</p>

<h3 id="模型是針對-harness-訓練的不只是針對-api">模型是針對 harness 訓練的，不只是針對 API</h3>

<p>OpenAI 工程師 nicbstme 在一篇深入比對 Codex、Claude Code、GitHub Copilot CLI 三家 harness 實作的<a href="https://x.com/nicbstme/status/2051131906327212298">長文</a>裡，把這件事講得最透。一句話總結:</p>

<blockquote>
  <p>模型是針對 harness 做 post-training 的，不只是針對 API。</p>
</blockquote>

<p>展開來說: tool 的名稱、它預期收到的 input schema、它包住記憶用的 citation 標籤、它呼叫 skill 的檔案結構、harness 要它「先做個計畫」時它遵循的規劃協定，這些都不是模型的通用能力。它們是 byte-level 的慣例，在 post-training 階段就內化成「某一個特定模型對上某一套特定 harness」的本能。把模型抽離它原本的 harness，你就放棄了一段補不回來的效能。</p>

<p>最直接的例子是工具格式。Cursor 的 harness 團隊在 <a href="https://cursor.com/en-US/blog/continually-improving-agent-harness">Continually improving our agent harness</a> 講得很白 (翻譯):</p>

<blockquote>
  <p>OpenAI 的模型被訓練成用 patch 格式來編輯檔案，Anthropic 的模型則被訓練成用字串取代。兩種模型其實都能用另一種工具，但給它不熟的那種，會多花推理 token、也會產生更多錯誤。所以在我們的 harness 裡，我們給每個模型它訓練時用的那種工具格式。</p>
</blockquote>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:22px 0;">
<div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">OpenAI / Codex 系</div>
<div style="font-size:.9em;color:var(--text-secondary);">訓練成生 <code>apply_patch</code> 的 diff 格式。</div>
</div>
<div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:6px;">Anthropic / Claude 系</div>
<div style="font-size:.9em;color:var(--text-secondary);">訓練成用 <code>edit_file</code> 的 <code>old_string</code> / <code>new_string</code> 字串取代。</div>
</div>
</div>

<p>這不是「偏好」問題，是 Cursor 在上百萬筆實際運作的 agent 軌跡上量到的、可測量的成本: 兩種模型其實都能用另一種工具，但給它不熟的格式 (讓習慣字串取代的 Claude 用 apply_patch、讓習慣 apply_patch 的 OpenAI 用字串取代)，它就會在格式細節上犯更多錯、多花推理 token。</p>

<blockquote>
  <p>編按: 這兩種格式實際長什麼樣、用起來差在哪，留到文末「<a href="#patch-vs-edit-detail">補充: apply_patch 與 edit_file 實際差在哪</a>」一節再展開，免得打斷主線。</p>
</blockquote>

<p>而工具格式只是最明顯的一個面，同一套邏輯散落在很多地方:</p>

<ul>
  <li><strong>citation 標籤</strong>: Codex 系的模型用完記憶，會在訊息結尾附上一個 <code class="language-plaintext highlighter-rouge">&lt;oai-mem-citation&gt;</code> 標籤，它的 harness 有個 parser 會解析這個標籤、去累加那條記憶的使用次數，當成記憶該不該淘汰的訊號。把這個模型放到一個不認得這個標籤的 harness 上，使用者會直接看到一串原始 XML，而那個淘汰訊號也永遠不會被觸發。</li>
  <li><strong>skill 的隱性契約</strong>: skill 看起來最像能跨 harness 共用的東西 (三家都用 <code class="language-plaintext highlighter-rouge">SKILL.md</code> 加 YAML frontmatter)，但這會誤導。<strong>格式相同，不代表契約相同。</strong> 一個 skill 的 body 裡會用祈使句點名特定工具:「用 TodoWrite 列三個子任務」「用 Agent 開 <code class="language-plaintext highlighter-rouge">subagent_type='Explore'</code> 的子代理」。這些工具名稱、參數形狀，是綁在寫出這個 skill 的那套 harness 上的。同一份 SKILL.md 搬到別家，TodoWrite 不存在、Agent 的形狀不一樣，skill 就靜默地失效、或跑出降級版本。</li>
</ul>

<h3 id="模型越強別家的工具格式反而錯更多">模型越強，別家的工具格式反而錯更多</h3>

<p>上面講的綁定，還有一個隨模型世代惡化的版本。Flask 作者 Armin Ronacher 在 <a href="https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/">Better models, worse tools</a> 記錄了一個具體案例。他自己維護一個 agent，它的編輯工具 schema 跟 Claude Code 長得不一樣: Claude Code 的編輯工具是扁平參數 (<code class="language-plaintext highlighter-rouge">old_string</code>、<code class="language-plaintext highlighter-rouge">new_string</code> 直接放在最上層)，他的工具則是把多個修改包成一個巢狀的 <code class="language-plaintext highlighter-rouge">edits[]</code> 陣列。</p>

<p>結果新一代的 Opus 4.8 和 Sonnet 5 呼叫他的工具時，常常在 JSON 結尾多加幾個 schema 裡根本沒有的欄位，像 <code class="language-plaintext highlighter-rouge">requireUnique</code>、<code class="language-plaintext highlighter-rouge">matchCase</code>、<code class="language-plaintext highlighter-rouge">oldText2</code>，而且每次編出來的名字還不一樣。一份實際的 transcript 裡，這種失敗率有 20%。奇怪的是，「要改哪段、改成什麼」的內容完全正確，錯的只有結尾那些多出來的欄位。更早的 Opus 4.5 用同一個工具反而沒這個問題: 模型變強了，遵循別家 schema 的能力卻退步了。</p>

<p>他的推測是，這不是隨機故障，是訓練留下的習慣。新一代模型的 post-training 是在 Claude Code (或類似的 harness) 裡做的，而 Claude Code 對格式錯誤非常寬容: 格式壞掉會自動重試、參數名寫錯有別名接住 (<code class="language-plaintext highlighter-rouge">old_str</code> / <code class="language-plaintext highlighter-rouge">old_string</code> 都認得)、不認識的欄位則<strong>靜默丟掉</strong>。在這種環境裡做 RL，tool call 格式就算有點不合規，任務照樣完成、reward 照樣拿到，模型於是學到「多加幾個欄位也沒差」。這個習慣在自家 harness 裡無害，搬到 schema 檢查嚴格的別家 harness 上，就變成一個個實際的錯誤。他的原話 (翻譯):</p>

<blockquote>
  <p>訓練得越好的模型，反而可能更用力跟你作對，因為它的先驗 (prior) 更強。替代的工具 schema 可能不只是模型不熟而已，它們可能是被 post-training 隱性懲罰過的。</p>
</blockquote>

<p>這給 Model-Harness-Fit 補上一個重要細節: <strong>工具 schema 不是中立的</strong>。每種 schema 離模型的訓練分布有近有遠，而且模型對自家 harness 練得越熟，離得遠的那些就錯得越多。所以「模型更會解任務」和「模型更不會遵循別家 schema」可以同時發生，前者甚至正是後者的原因。</p>

<p>那自建 harness 的人怎麼辦? Armin 驗證過一個解法: 開 Anthropic API 的 strict mode，讓 API 在採樣階段就強制輸出符合 schema (不合法的 token 根本生不出來)，問題就消失了。代價是 strict mode 對 schema 複雜度有限制，連 Claude Code 自己都沒開。所以務實上是二選一: 要嘛把自己工具的 schema 改得像 Claude Code 那套，往模型的訓練分布靠; 要嘛開 strict mode，用採樣層的強制保證換格式正確。兩條路背後跟前面 Cursor 的結論是同一件事: 別跟模型訓練時熟悉的格式作對。</p>

<h3 id="中途切換模型是最清楚的失敗案例">中途切換模型，是最清楚的失敗案例</h3>

<p>把這個綁定看得最清楚的，是「對話進行到一半切換模型」。Cursor 的研究文章把這個失敗面拆得很清楚，三件事同時壞掉:</p>

<div style="display:flex;flex-wrap:wrap;gap:14px;margin:22px 0;">
<div style="flex:1 1 200px;border:1px solid #cf222e;border-radius:8px;padding:14px 18px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">1️⃣ 對話歷史變 OOD</div>
<div style="font-size:.9em;color:var(--text);">前一個模型留下的 tool call (patch 格式、citation 標籤)，對接手的模型是訓練分布外的東西。</div>
</div>
<div style="flex:1 1 200px;border:1px solid #cf222e;border-radius:8px;padding:14px 18px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">2️⃣ prompt cache 失效</div>
<div style="font-size:.9em;color:var(--text);">cache 綁 provider 和模型，一切換就是保證 cache miss，第一輪要全價重算整段歷史。</div>
</div>
<div style="flex:1 1 200px;border:1px solid #cf222e;border-radius:8px;padding:14px 18px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:6px;">3️⃣ 工具形狀變了</div>
<div style="font-size:.9em;color:var(--text);">新模型載入自己的工具集，跟使用者前面用的那套不一樣，它得自己搞懂哪些還能用。</div>
</div>
</div>

<p>Cursor 的結論很務實 (翻譯):「我們通常建議在一整段對話裡固定用同一個模型，除非你有理由要換。」更好的做法是: 不要切換主對話的模型，而是開一個帶不同模型的 subagent，它從一個乾淨的 context 起步，沒有前一個模型留下的 transcript 偏差，也沒有 cache 會失效。這正好呼應第四、五篇反覆出現的做法: 要換模型、換視角，就開子代理，別動主線。</p>

<blockquote>
  <p>編按: 這條「切換模型 = 換 harness + 換工具 + 讓 cache 失效，三件事一起發生」也說明了一件事: 你以為只是在下拉選單換個名字，實際上是把整個契約換掉。</p>
</blockquote>

<p>講到這裡，模型跟 harness 綁得這麼緊，Saurabh 那個問題反而更尖銳了: 既然綁這麼緊，別人家的 harness 到底是怎麼贏過原廠的?</p>

<h2 id="harness這個詞被塞了太多意思">「harness」這個詞，被塞了太多意思</h2>

<p>解開這個矛盾的關鍵，在 Viv 一串<a href="https://x.com/Vtrivedy10/status/2051451869017584112">公開討論</a>底下，一位叫 Gangadhar 的人講得最好: 很多看似衝突的說法之所以衝突，是因為「harness」這個詞被塞了太多意思。一旦把它拆成四層，大部分矛盾就能同時成立。</p>

<div style="display:flex;flex-direction:column;gap:10px;margin:24px 0;">
<div style="border:1px solid var(--border);border-radius:8px;padding:12px 16px;background:var(--bg);display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;">
<div style="font-weight:700;color:var(--text);flex:0 0 120px;">任務分布</div>
<div style="font-size:.9em;color:var(--text-secondary);flex:1 1 220px;">你實際要解的那群任務長什麼樣 (Text-to-SQL、客服、訪談…)。</div>
<div style="font-size:.82em;color:var(--text-secondary);flex:0 0 auto;">← 你要對齊的目標</div>
</div>
<div style="border:1px solid var(--accent);border-radius:8px;padding:12px 16px;background:var(--tag-bg);display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;">
<div style="font-weight:700;color:var(--accent);flex:0 0 120px;">workflow 層</div>
<div style="font-size:.9em;color:var(--text);flex:1 1 220px;">任務怎麼拆、subagent 怎麼調度、routing、塞哪些 skill 和專用工具。</div>
<div style="font-size:.82em;color:var(--accent);font-weight:600;flex:0 0 auto;">← 自建贏在這層</div>
</div>
<div style="border:1px solid var(--border);border-radius:8px;padding:12px 16px;background:var(--surface);display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;">
<div style="font-weight:700;color:var(--text);flex:0 0 120px;">內層 tool loop</div>
<div style="font-size:.9em;color:var(--text-secondary);flex:1 1 220px;">怎麼提供 tool 描述、解析 tool call、把結果送回、context 滿了怎麼辦。</div>
<div style="font-size:.82em;color:var(--text-secondary);flex:0 0 auto;">← 原廠贏 (一起訓練)</div>
</div>
<div style="border:1px solid var(--border);border-radius:8px;padding:12px 16px;background:var(--surface);display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;">
<div style="font-weight:700;color:var(--text);flex:0 0 120px;">模型</div>
<div style="font-size:.9em;color:var(--text-secondary);flex:1 1 220px;">純權重。post-training 有沒有把工具放進迴圈，決定這層的預設傾向。</div>
<div style="font-size:.82em;color:var(--text-secondary);flex:0 0 auto;">← 原廠贏 (一起訓練)</div>
</div>
</div>

<p>拆開之後，矛盾就化解了: <strong>原廠贏在前兩層 (模型加內層 tool loop)，因為它們是一起訓練出來的; 自建或開放 harness 贏在 workflow 層，因為它能貼著一個特定的任務分布去優化。</strong> Model-Harness-Fit 講的是前兩層的契合，「自建打贏原廠」講的是後兩層的優化，兩者根本不在比同一件事，所以可以同時為真。Viv 之所以一度覺得這幾種說法互相矛盾，就是因為把這四層全用「harness」一個詞概括，自然會得出「不可能全部為真」的結論。</p>

<p>這個拆法還順手給了一條實作守則: <strong>內層留給原廠，workflow 層留給自己。</strong> 當你自建 agent、選用某個模型時，別去跟它訓練時的工具格式作對 (該給 <code class="language-plaintext highlighter-rouge">apply_patch</code> 就給，該給字串取代就給)，那是原廠用 post-training 訓練出來的契合，你動它只會掉分; 你的力氣要花在外層，針對你的任務分布去設計編排、context、skill。</p>

<h2 id="自建為什麼打得贏原廠-對齊你的任務分布">自建為什麼打得贏原廠: 對齊你的任務分布</h2>

<p>那「贏在 workflow 層」具體是贏在哪? Viv 給了一個三元組來回答: <strong>Model-Harness-Task fit</strong> (<a href="https://x.com/Vtrivedy10/status/2059712077925658717">出處</a>)。</p>

<p>他把 fit 拆成兩種。一種是大家常談的 Model-Harness fit: RL post-training 時把 harness 放進迴圈，模型自然跟特定工具形狀、提示風格產生契合 (就是前面講的那層)。另一種比較少人談、也比較少人實驗的，是 <strong>Harness-Task fit</strong>: 針對任務本身去調 harness，例如領域專屬的提示 (像驗證類的 coding 任務)，或刻意省略掉跟當前任務無關、只會造成混淆的 context。</p>

<p>Viv 點出一個很精準的觀察: Claude Code 這類原廠 harness 的 system prompt 裡塞了大量指令，因為它被迫服務一個非常通用的使用者，這個人基本上可能開口要任何東西。但如果你只針對一個窄任務，用一組高度聚焦、真正相關的 context 和工具，把其他雜訊全部拿掉，好處是很大的。他的原話 (翻譯):</p>

<blockquote>
  <p>harness 裡的每一個元件，存在的目的都是為了誘發模型的某種行為。如果這些元件針對任務調校過，模型就受益; 如果是噪音和好內容混在一起，模型可能還行，但也可能被搞混。</p>
</blockquote>

<p>這就是為什麼頂尖的垂直領域 AI 團隊，都替自家 agent 打造非常客製的 harness 和 eval。原廠是對著「通用分布」訓練的，你是對著「你使用者的真實任務分布」優化的，在那個窄分布上，你本來就該贏。</p>

<p>而對齊任務分布的方法，Viv 給的因果鏈很清楚:</p>

<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:center;margin:22px 0;font-weight:600;">
<span style="background:var(--tag-bg);border:1px solid var(--accent);color:var(--accent);border-radius:20px;padding:6px 16px;">Eval</span>
<span style="color:var(--text-secondary);">形塑 →</span>
<span style="background:var(--surface);border:1px solid var(--border);color:var(--text);border-radius:20px;padding:6px 16px;">Harness</span>
<span style="color:var(--text-secondary);">貼合 →</span>
<span style="background:var(--surface);border:1px solid var(--border);color:var(--text);border-radius:20px;padding:6px 16px;">Task</span>
</div>

<p>先把能代表你使用者的 eval 蒐集好，eval 形塑 harness,harness 貼合 task。這條鏈正好接回第七篇的結論 (harness 自我改進的核心，是那個能自動打分的 eval)，也跟 ihower 一直在講的 eval-driven 方法論是同一回事。</p>

<p>這不是紙上談兵，兩個實作可以佐證「為每個模型分別客製」真的有效:</p>

<ul>
  <li><strong>Cursor</strong>: 所有 harness 抽象都做成不綁定特定模型 (model-agnostic)，但每個模型都能深度客製: 給 OpenAI 模型 patch 格式、給 Anthropic 模型字串取代，連 prompt 都按 provider、甚至按模型版本分開寫 (他們觀察到 OpenAI 模型對指令比較字面、精確，Claude 比較能容忍模糊的指令)。他們的描述是:「持續性地堆疊一個個小優化」，而不是指望一步到位。</li>
  <li><strong>LangChain Deep Agents</strong>: 加了針對不同模型的設定檔 (<a href="https://www.langchain.com/blog/tuning-deep-agents-different-models">profiles</a>)，同一個 agent 換模型時自動套用對應的 prompt、工具、middleware，呼叫端完全不用改。他們在 tau2-bench 一個較難的子集上量到，光是套上對的 profile,GPT-5.3 Codex 從 33% 升到 53%,Opus 4.7 從 43% 升到 53%。改的不是模型，是 harness 那層按模型分流。</li>
</ul>

<p>所以「自建能不能打贏原廠」這題的答案是: 能，但不是靠複製原廠那套通用 harness，而是靠對齊你自己的任務分布。前提是你得先有能代表使用者的 eval，沒有 eval，你連「贏了沒」都量不出來，更別說往哪裡調。</p>

<h2 id="harness-會過期">harness 會過期</h2>

<p>講完「綁模型」，再看一體兩面的另一面: harness 會過期。</p>

<p>Anthropic 在 <a href="https://claude.com/blog/harnessing-claudes-intelligence">Harnessing Claude’s intelligence</a> 裡，把這件事講成一個該反覆問自己的問題:「有什麼是我可以停止做的?」(What can I stop doing?) 他們的論點是:</p>

<blockquote>
  <p>harness 裡的每個元件，都編碼了一項「模型自己做不到什麼」的假設。當模型在某件事上變強，那個假設就過時了，那個元件也該被移除。</p>
</blockquote>

<h3 id="案例一-context-reset三個世代拆掉三層">案例一: context reset，三個世代拆掉三層</h3>

<p>Anthropic 自己給的例子很具體: 做過一個跑長任務的 agent，在 Sonnet 4.5 上，模型一感覺到 context 快滿，就開始草草收尾、推說任務太大 (他們叫它 context anxiety，上下文焦慮)。為了補這個，他們在 harness 裡加了「定時清空 context、用結構化交接檔把狀態帶過去」的機制。結果 Opus 4.5 一出，reset 就變得多餘; Opus 4.6 再一出，連更上層的任務拆解 (sprint decomposition) 都整個拿掉，效果反而更好。三個模型世代，拆掉三層 harness。Portkey 的<a href="https://portkey.ai/blog/the-harness-tax/">整理</a>下了一句很到位的註腳: 這些元件「一月還在承重，三月就成了死碼」。</p>

<h3 id="案例二-todowrite-退役">案例二: TodoWrite 退役</h3>

<p>更能說明「補強措施怎麼從幫手變成累贅」的，是 Claude Code 的待辦清單。Claude Code 團隊的工程師在 <a href="https://x.com/trq212/status/2027463795355095314">Lessons from Building Claude Code</a> 裡完整講了這條演化:</p>

<ul>
  <li><strong>一開始</strong>: Claude Code 剛推出時，模型撐不住長任務，需要一份待辦清單讓它不偏離任務，於是給了 <code class="language-plaintext highlighter-rouge">TodoWrite</code> 工具: 開工先寫一份 todo，做完一項勾掉一項。</li>
  <li><strong>還是會忘</strong>: 光有清單不夠，模型常常忘記自己要幹嘛，於是 harness 每 5 輪插一次 system reminder，提醒它原本的目標。</li>
  <li><strong>模型變強之後，清單反而綁手綁腳</strong>: 新一代模型不只不需要被提醒，還會被那份提醒限制住: 一直被提醒這份 todo list，反而讓模型以為自己必須照著清單做，而不是動態修改它。再加上 Opus 4.5 變得很會用子代理，但多個子代理要怎麼協作在同一份 todo list 上?</li>
  <li><strong>於是 TodoWrite 退役，換成新的 Tasks 系統</strong>: 這裡的 Tasks 是一組新工具 (<code class="language-plaintext highlighter-rouge">TaskCreate</code>、<code class="language-plaintext highlighter-rouge">TaskUpdate</code>、<code class="language-plaintext highlighter-rouge">TaskList</code> 等)，不是那個會開子代理、後來改名叫 <code class="language-plaintext highlighter-rouge">Agent</code> 的舊 Task 工具，兩者只是名字像。Todos 和 Tasks 管的事根本不同: Todos 是「讓單一模型在一個 session 內不偏離任務」，Tasks 是「讓跨 session、跨多個子代理的長專案能協調起來」。</li>
</ul>

<p>兩者具體差在三個地方，而且最關鍵的第一點 (持久化) 常被忽略:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 280px;border:1px solid var(--border);border-radius:8px;padding:16px 20px;background:var(--surface);">
<div style="font-weight:700;color:var(--text);margin-bottom:8px;">TodoWrite (舊)</div>
<div style="font-size:.9em;color:var(--text-secondary);line-height:1.75;"><strong>存在 context 裡</strong>,session 結束或 compaction 一來就消失。扁平清單，沒有依賴關係。單一模型私有，子代理看不到。</div>
</div>
<div style="flex:1 1 280px;border:1px solid var(--accent);border-radius:8px;padding:16px 20px;background:var(--tag-bg);">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">Tasks (新)</div>
<div style="font-size:.9em;color:var(--text);line-height:1.75;"><strong>寫到磁碟 <code>~/.claude/tasks/</code></strong>，跨 session、跨 compaction 都還在。任務間有依賴 (A 擋著 B,A 完成 B 自動解鎖)。多個 session 和子代理共用同一份，更新即時廣播。</div>
</div>
</div>

<p>把這三點接回前面幾篇就更清楚: 持久化對應第六篇外層 loop 裡 Ralph 那種「拿外部 plan 檔手動追進度」的做法，Tasks 等於把它變成內建; 依賴與跨代理共享，則對應 Opus 4.5 之後模型很會開子代理、但多個子代理沒辦法協作在同一份扁平 todo list 上的那個缺口。</p>

<p>那篇收尾的一句話正是這一篇的主軸 (翻譯):「隨著模型能力提升，你的模型曾經需要的工具，現在可能正在限制它。不斷回頭檢視『到底需要哪些工具』的舊假設，很重要。」</p>

<p>這裡有個比「變成死重」更精準的觀察，Shane Deconinck 把它叫 scaffolding trap (腳手架陷阱): 當模型變強，腳手架不只是變成死重而已，它會反過來跟模型新獲得的能力衝突。你當初為了補一個短處寫的 workaround，現在正擋著模型去用它學會的更好做法。TodoWrite 就是教科書級的例子: 那些每 5 輪一次的提醒，本來是怕模型忘記，後來卻變成逼模型死守一份它其實該動態修改的清單。</p>

<blockquote>
  <p>編按: 同一篇文章還有一個例子。Claude Code 早期用 RAG 向量資料庫給模型 context，後來發現模型夠強了，直接給它一個 <code class="language-plaintext highlighter-rouge">Grep</code> 工具讓它自己搜、自己建 context，效果更好也更不易壞。補強措施退場，是個反覆出現的模式，不是單一個案。</p>
</blockquote>

<h3 id="不是變少是該做的會變">不是變少，是該做的會變</h3>

<p>不過這裡要補一個容易誤會的地方:「模型變強 → 拆掉 harness」不等於「harness 會一路縮到零」。Addy Osmani 講得好: 隨著模型變強，值得做的 harness 不會變少，只是該做哪些會跟著變。Opus 4.6 淘汰掉 context anxiety 那一類腳手架，但能做到的上限也跟著提高了: 原本做不到的任務 (跨好幾天的記憶、協調三個專職 agent、替生成的 UI 設計品質評估器) 現在變得可行，而它們有自己的新失敗模式，需要新的腳手架去補。拆掉舊的、補上新的，假設在變，腳手架也跟著變。</p>

<h3 id="沒有一體適用的-harness">沒有一體適用的 harness</h3>

<p>同樣的道理不只適用在時間軸上（同一個模型越變越強），在同一個時間點、不同大小的模型之間也成立: harness 該做得多精巧，跟你用多強的模型有關，沒有一套適合所有模型。小模型一次做不對、也撐不住長任務，更需要靠外層的 harness 去補: 把工作流拆成多階段、分給不同模型、一段段把關。夠強的大模型常常一次就做完，還能全程待在同一份 context 上，prompt cache 一路有效。</p>

<p>harness 的精巧程度和模型能力綁在一起: 小模型得配上一套比較複雜、設計精巧的 harness，表現才會好; 同一套複雜 harness 換上大模型卻多半是浪費，多出來的那幾層都是多餘。所以 harness 的架構選擇，沒辦法脫離模型來談。</p>

<h3 id="廠商會主動把-harness-訓練進模型">廠商會主動把 harness 訓練進模型</h3>

<p>為什麼這些補強措施退場得越來越快? 因為廠商會主動把 harness 的做法訓練進模型。這就是開頭那個共演化迴圈運轉的方向: 一個 harness primitive 先上線，幾個月後它出現在幾百萬筆 agent 軌跡裡，這些軌跡變成下一代模型的訓練資料，再過一代，這個 primitive 就被訓練進模型的本能，harness 那層就能拿掉。幾個看得到的例子:</p>

<ul>
  <li>OpenAI 把 <code class="language-plaintext highlighter-rouge">apply_patch</code> 這個編輯格式直接後訓練進模型，模型本身就會生成這個格式 (這跟前面「綁模型」那段是同一件事的兩面)。</li>
  <li>第五篇提過一點留到這篇講: Fable 5 這代模型「擅長在迴圈中自我修正」已經寫進官方賣點。把這句話翻成 harness 的語言就是: 第五篇那些「逼模型寫完要驗證」的外層腳手架 (Stop hook 退件、催它再檢查一遍的 prompt)，有一部分正在被模型內建的自我修正取代。Anthropic 給的另一個更直白的數據是: 早期 Claude 連玩寶可夢 FireRed 都要一整套含地圖、導航的輔助 harness,Fable 5 只靠純視覺、極簡 harness 就破關了。模型越強，需要的腳手架越少。</li>
</ul>

<p>OpenAI 那邊也一樣，prompt 和 harness 會跟著一代代模型不斷調整 (Codex CLI 是開源的，這些演進在它的 commit 歷史裡都看得到)。所以「拆 harness、改做法」不是哪一家的專利，是整個產業共通的動作。</p>

<p>這正好把第二篇那個 ratchet (棘輪) 原則補完整。第二篇說，你只在看到真實失敗時才加一條約束; 但棘輪是雙向的，你也只在某天模型強到讓那條約束變多餘時，才把它拆掉。一份好的 AGENTS.md，每一行都該追得回一個具體出過的包; 而當那個包再也不會發生，那一行就該刪掉。加約束和拆約束，是同一套紀律的兩個方向。</p>

<p>第一篇也先提過一點: filesystem 和 sandbox 這些能力「有效，但不代表永遠」。放在這條時間軸上就清楚了: 今天每一個看起來理所當然的 harness 元件，都是某一代模型某個短處的對應物。哪些會被吸收回模型、哪些會留下來、價值持續累積，得看模型往哪裡變強，沒有哪個元件保證能一直留著。</p>

<h3 id="每次模型升級問一次有什麼可以停止做">每次模型升級，問一次「有什麼可以停止做?」</h3>

<p>前面這些拆 harness 的案例，可以歸結成一條給自建 agent 的例行檢查: 每次模型大升級，都重新審一遍 harness，問一次「有什麼是我現在可以停止做的?」把已經派不上用場的腳手架拆掉。具體可以對著手上的 harness 逐項問:</p>

<ul>
  <li>這條 prompt 約束，還在提高成功率嗎，還是只是慣性留著?</li>
  <li>這個強制的規劃步驟 (開工先寫 todo、先列計畫)，還在防止模型偏離任務，還是只是拖慢它?</li>
  <li>這個 context reset 或交接，還在避免錯誤，還是已經在打斷模型的長程推理?</li>
  <li>這個 subagent，是真的提高了品質，還是只多了合併成本?</li>
  <li>這個工具格式，還是這個模型最熟的那一種嗎?</li>
</ul>

<p>而且這不限於大版本: Anthropic 在 Opus 4.7 的發布說明就提醒，4.7 對指令的遵循變嚴格了，「為早期模型寫的 prompt 可能產生非預期結果，使用者該重新調校自己的 prompt 和 harness」。連小版本升級都可能要回頭重調。這件事不做，你的 harness 會越積越臃腫，反而拖累一個已經更強的模型。</p>

<h2 id="幾個不同看法">幾個不同看法</h2>

<p>Model-Harness-Fit 這套說法聽起來很順，但也有一些不同看法，而且講這些的就包括最積極推廣這套框架的 Viv 自己。把不同角度都擺出來才公道:</p>

<ul>
  <li><strong>聰明的模型本來就該能輕鬆切換工具格式。</strong> Viv 自己講過 (翻譯):「一個真正聰明的模型，要在不同的 patch 方法之間切換應該毫無困難; 是『把 harness 放進迴圈一起訓練』這件事，造成了這種過度擬合 (overfitting)。」換句話說，模型對自家工具格式的偏好，某種程度上是被訓練慣出來的，不是一種本質的好。前面 Armin 那個「新模型在別家工具上多加欄位」的案例，就是這種過度擬合實際發生的樣子。他還點出一個長期隱憂 (翻譯):「post-training 越是集中在單一個主流 harness 裡發生，其他所有 harness 就越得繼承它的怪癖。」</li>
  <li><strong>長期的解法，也許是用更多樣的 harness 去訓練，讓模型泛化。</strong> 如果偏好來自過度擬合，那對著更多種 harness 訓練、讓模型在任何工具格式上都一樣行，就是更健康的方向。</li>
  <li><strong>成對推出 = 用可攜性換排行榜。</strong> 廠商把模型和 harness 當成一個產品一起出，確實能在 benchmark 上拿到最好的數字，但代價是模型變得更不可替換。這筆交易划不划算，要看客戶在不在乎可攜性，而現在客戶大多只在乎排行榜。</li>
</ul>

<p>另外，「harness 會過期」也有它的反面: 不是所有 primitive 都會被吸收回模型，有些會留下來，而且價值會持續累積。哪些被吸收、哪些留下，目前沒有人能事先說準，只能讀軌跡、看數據。</p>

<p>把這些不同看法擺出來不是要否定前面，而是想說: Model-Harness-Fit 是個有用的工程框架，但它描述的是「現在」這個時間點的狀態，不是一條永恆定律。它本身也會隨著時間改變。</p>

<h2 id="會過期的與不會過期的">會過期的，與不會過期的</h2>

<p>把整個系列從頭收起來。這七篇講的是一套讓 agent 做得對、做得完的工程方法，但每一個做法、每一個元件，都會隨剛剛那條時間軸改變。那到底有沒有什麼是穿越模型世代、不會過期的?</p>

<p>有，而且這正是整個系列真正的主軸。把東西分成兩堆:</p>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 280px;border:1px solid #cf222e;border-radius:8px;padding:18px 22px;background:#ffebe9;">
<div style="font-weight:700;color:#cf222e;margin-bottom:8px;">⏳ 會過期</div>
<div style="font-size:1.02em;font-weight:600;color:var(--text);margin-bottom:6px;">各種 harness 做法與補強措施</div>
<div style="font-size:.9em;color:var(--text-secondary);">對應 Bitter Lesson: 能隨算力擴大的通用方法，終究勝過手工規則。很多會隨更強的模型逐步不再需要。</div>
</div>
<div style="flex:1 1 280px;border:1px solid #1a7f37;border-radius:8px;padding:18px 22px;background:#dafbe1;">
<div style="font-weight:700;color:#1a7f37;margin-bottom:8px;">♾️ 不會過期</div>
<div style="font-size:1.02em;font-weight:600;color:var(--text);margin-bottom:6px;">定義「什麼叫做好」+ 驗證它做到了</div>
<div style="font-size:.9em;color:var(--text-secondary);">這就是 Eval 與 Judge。穿越模型世代的基本功。</div>
</div>
</div>

<p>⏳ 會過期的，是各種具體的 harness 做法。這對應的是 <a href="https://blog.aihao.tw/2026/02/17/bitter-lesson-agent-harness/">Bitter Lesson</a>: 能隨算力擴大的通用方法，終究會勝過手工堆砌的規則。但 Bitter Lesson 在這裡不是叫你「別設計 harness」，而是「別把今天這個模型的限制，寫成永遠不動的架構」。工程上你還是需要 harness，只是它該被設計得更精簡、更容易替換、也更容易量出到底有沒有用，這樣模型一變強，你才拆得掉。你今天精心寫的工具、清單、reset、催驗證的 prompt，很多會隨著更強的模型逐步不再需要。用 nicbstme 那句話收尾最貼切: 想待在前沿，你得在每次新模型發布時，刪掉你大半的程式碼。</p>

<p>♾️ 不會過期的，是無論模型變多強，你永遠得回答的兩個問題: 定義「什麼叫做好」、以及驗證它真的做到了。這就是 Eval 和 Judge。</p>

<p>這兩堆的分界，其實貫穿了整個系列。第三篇工具層的確定性檢查和語意 judge、第五篇單輪結束的停止條件和獨立 grader、第六篇外層 loop 的完成判定、第七篇自我改進的核心，講的全是同一件事的不同粒度: 你得有東西能判斷「對不對、完了沒」。</p>

<p>而這件事為什麼不會過期? 因為就算模型強到能完美自我驗證 (第五篇講過，OpenAI 正把驗證往模型層訓練)，它驗證的，仍然是一個「由你定義的目標」。模型可以幫你檢查，但「什麼算達標」這個標準，是你給的。一個會自我修正的模型，也得有個目標可以對齊。所以定義「好」這件事，是那個無論模型多強，都得由你自己來的核心。</p>

<p>這也是為什麼整個系列從第一篇到現在，反覆把工程師的角色描述成同一個轉變: 你被移出的，是那些逐步操作的迴圈 (瓦特調速器那個比喻裡，顧著轉閥門的那一圈); 你留下來做的，是設計回饋、定義目標、把關「什麼叫做好」。模型會一代代把腳手架吸收掉，但它吸收不了那個替它定義成功、並驗證成功的角色。</p>

<p>harness 會過期; 替 harness 定義「對不對、完了沒」的那套 eval 與 judge，不會。這七篇談了這麼多，最後就收在這一句上。</p>

<h2 id="patch-vs-edit-detail">補充: apply_patch 與 edit_file 實際差在哪</h2>

<p>前面「綁模型」那節提到，OpenAI 系的模型被訓練成生 <code class="language-plaintext highlighter-rouge">apply_patch</code>、Anthropic 系被訓練成用 <code class="language-plaintext highlighter-rouge">edit_file</code> 的字串取代。這兩種編輯工具實際長什麼樣、用起來差在哪，這裡補充一下。</p>

<p>同樣是「把 <code class="language-plaintext highlighter-rouge">return None</code> 改成 <code class="language-plaintext highlighter-rouge">return result</code>」這一個改動，兩種工具的呼叫長這樣:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code># apply_patch (Codex / OpenAI)
*** Begin Patch
*** Update File: src/handler.py
@@ def handle(req):
-    return None
+    return result
*** End Patch
</code></pre></div></div>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code># edit_file / str_replace (Claude / Anthropic)
old_string: "    return None"
new_string: "    return result"
</code></pre></div></div>

<p>apply_patch 是一份 diff: 模型用 <code class="language-plaintext highlighter-rouge">@@</code> 標出附近的程式碼當定位的參考點，再用 <code class="language-plaintext highlighter-rouge">-</code> / <code class="language-plaintext highlighter-rouge">+</code> 標出要刪哪行、加哪行，一份 patch 裡可以同時改好幾個檔案、好幾個位置。str_replace 則是「找字串、換字串」: 模型給一段必須逐字元完全相符、而且在檔案裡唯一的 <code class="language-plaintext highlighter-rouge">old_string</code>，加上要換成的 <code class="language-plaintext highlighter-rouge">new_string</code>，一次呼叫改一個地方。實際用起來，差別落在三處:</p>

<ul>
  <li><strong>批次粒度</strong>: 一次大改動，apply_patch 可以一份 patch 解決多檔多處; str_replace 通常一次改一個點，多處就要呼叫好幾次。</li>
  <li><strong>怎麼指出位置</strong>: apply_patch 靠上下文行定位 (像讀 git diff); str_replace 要把目標那段原文一字不差地貼出來，還得保證它在檔案裡只出現一次。</li>
  <li><strong>失敗的方式</strong>: apply_patch 的上下文對不上 (檔案漂移、縮排差一格) 就套不上去; str_replace 的 old_string 差一個空白就找不到，在檔案裡出現兩次就不知道該改哪個。</li>
</ul>

<p>但放回這篇的主題: 重點不是哪種比較好，而是模型被訓練成習慣哪一種。把 apply_patch 給習慣字串取代的 Claude 模型，它容易把 diff 的上下文行抓錯、hunk 格式生壞; 把 str_replace 給習慣 apply_patch 的 OpenAI 模型，它會在「逐字複製、保證唯一」這件事上犯更多錯。兩種模型其實都能用另一種工具，只是給它不熟的那種，它會多花推理 token、也錯得更多。這就是 Model-Harness-Fit 最具體的一個例子。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="LLM" /><category term="Eval" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson (本篇) 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">給 Agent 開發者的駕馭工程 (9): 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</title><link href="https://blog.aihao.tw/2026/06/26/harness-engineering-9-agent-frameworks/" rel="alternate" type="text/html" title="給 Agent 開發者的駕馭工程 (9): 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/26/harness-engineering-9-agent-frameworks</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/26/harness-engineering-9-agent-frameworks/"><![CDATA[<div class="series-toc" style="border:1px solid var(--border);border-radius:8px;background:var(--surface);padding:14px 18px;margin:24px 0;font-size:.92em;line-height:1.85;">
<div style="font-weight:700;color:var(--accent);margin-bottom:8px;">📚 給 Agent 開發者的駕馭工程 (全 9 篇)</div>
<div>1. <a href="/2026/06/26/harness-engineering-1-deep-agent-capabilities/">基礎: Deep Agent 的六項內建能力</a></div>
<div>2. <a href="/2026/06/26/harness-engineering-2-what-is-harness-engineering/">核心: Agent 要的是回饋迴路，不是完美提示</a></div>
<div>3. <a href="/2026/06/26/harness-engineering-3-tool-execution-feedback/">回饋時機一: 工具回傳值，是寫給 agent 的回饋</a></div>
<div>4. <a href="/2026/06/26/harness-engineering-4-mid-run-injection/">回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent</a></div>
<div>5. <a href="/2026/06/26/harness-engineering-5-goal-and-outcomes/">回饋時機三: 單輪結束的驗收，Goal 與 Outcomes</a></div>
<div>6. <a href="/2026/06/26/harness-engineering-6-outer-loop/">回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron</a></div>
<div>7. <a href="/2026/06/26/harness-engineering-7-self-improving/">進階: 自我改進 Harness, Meta-Harness 與爬坡</a></div>
<div>8. <a href="/2026/06/26/harness-engineering-8-model-harness-fit/">收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson</a></div>
<div>9. <strong style="color:var(--text);">自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建?</strong> <span style="color:var(--text-secondary);">(本篇)</span></div>
<div style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border);">🎞️ 搭配演講投影片: <a href="https://ihower.tw/presentation/harness.html">給 Agent 開發者的 Harness+Loop Engineering</a></div>
</div>

<p>前面八篇談的都是 harness engineering 的觀念: 回饋迴路、工具層的檢查、使用者 steering、單輪驗收、外層 loop、自我改進、model-harness-fit。觀念講完，真要自己動手開發一個 agent 時，會先碰到一個很實際的問題: 該從哪個開發框架開始?</p>

<p>選擇一大堆: OpenAI 的 Codex SDK 和 Agents SDK、Anthropic 的 Claude Agent SDK、GitHub 的 Copilot SDK、LangChain 的 LangGraph 跟 deepagents、Google 的 ADK、Pydantic AI、Vercel 的 AI SDK、AWS 的 Strands Agents、微軟的 Agent Framework、Earendil 的 Pi。每一家都說自己是做 agent 的好工具，而且還不只這些。這篇從裡面挑了小編覺得比較成熟、有實際產品或社群在用的幾個框架，用系列第 1 篇的「六項能力」當對照標準，分成兩條路線比較，最後給一點選型建議。</p>

<p>照例提醒系列定位: 這裡談的是「自己開發一個 agent (可能是 Text-to-SQL、知識庫 RAG、訪談 agent，未必跟寫程式有關)」要用哪個框架當基礎，不是「怎麼把 Claude Code、Codex 這類 coding agent 用得更順」，也不是叫你挑一個拿來做軟體開發的 coding agent。下面有些框架 (像 Codex SDK、Claude Agent SDK、GitHub Copilot SDK) 雖然源自 coding agent，但這裡是拿它們當基礎去打造你自己的 agent，用途可以跟寫程式完全無關。</p>

<h2 id="兩條開發路線">兩條開發路線</h2>

<p>先快速複習第 1 篇那六項 Deep Agent 能力: ① Plan &amp; Todos、② Filesystem &amp; Bash、③ Sub-Agent、④ Memory、⑤ Skills、⑥ 更多工具 (MCP、Browser、Computer Use)。小編就拿這個分類當對照標準，看每個框架幫你內建了幾項、哪些要自己接。用這個角度看，市面上的框架大致分成兩條路線:</p>

<ul>
  <li>一條是從<strong>全套 Deep Agent</strong> 開始: 框架直接給你一個跑得動的 agent，六項能力大多已經接好，你拿來改、加工具、改 system prompt 就能用。</li>
  <li>另一條是<strong>從基礎構建</strong>: 框架給的是 agent、工具、handoff、workflow、session 這些基礎元件，agent 迴圈跟那六項能力大多要你自己決定要不要加、怎麼加。</li>
</ul>

<h2 id="路線一-從全套-deep-agent-開始">路線一: 從全套 Deep Agent 開始</h2>

<p>這條路線的共同點: 一上手就是一個會規劃、會讀寫檔案、會開子代理人的 agent，六項能力大多已經內建。你的工作是在它之上做客製，而不是從頭組裝。</p>

<p>還有一點容易被忽略但很重要: 除了那六項能力 (那些主要是工具)，全套 Deep Agent 還附帶一份原廠調校過的 system / developer prompt 跟 agent loop，裡面已經寫好大量預設行為: 怎麼規劃、什麼時候用哪個工具、出錯了怎麼自我修正、怎麼維護待辦清單。第 1 篇編按引用過 LangChain 對 Deep Agent 的四特徵定義，排第一個的就是「詳細的 system prompt」。這套預設行為，是 deep agent 開箱就比較會做事的一個重要原因。</p>

<p><strong><a href="https://developers.openai.com/codex/sdk/">Codex SDK</a> &amp; <a href="https://developers.openai.com/codex/app-server">Codex App Server</a> (OpenAI)</strong>: 核心是 Rust 寫的 Codex CLI，完全開源 (Apache 2.0)，連 agent loop 本身都在開源 repo 裡，可以讀、可以改。對外有兩種嵌入方式，差別在於你要用什麼語言串接: 一種是 TypeScript / Python SDK 把 CLI 包成程式介面 (用這個方式你得寫 TS 或 Python)，另一種是 <code class="language-plaintext highlighter-rouge">app-server</code> 用 JSON-RPC 雙向溝通 (設計上刻意參考 MCP)，因為是協議介面，你的程式用什麼語言都能接。六項能力幾乎都有，sandbox 還是作業系統核心層級的隔離。一個實務上的差別: Codex 可以用 ChatGPT 訂閱帳號登入，不一定要用 API key，這點後面選型會再提。</p>

<p><strong><a href="https://github.com/github/copilot-sdk">GitHub Copilot SDK</a> (Node / Python / Go / .NET / Java / Rust)</strong>: 它開放給你用程式呼叫的，是 <a href="https://github.com/features/copilot/cli">Copilot CLI</a> 背後同一個、已在生產環境驗證過的 agent runtime。架構跟 Codex 很像: Node / Python / .NET 會自動把 Copilot CLI 當依賴一起裝進來，其餘語言的 SDK 則都透過 JSON-RPC 跟 Copilot CLI 的 server mode 溝通，所以 Go、Java、Rust 也能接，第一方支援的語言是這條路線裡最多的。六項能力大多內建: 規劃、檔案編輯與第一方工具 (等同 <code class="language-plaintext highlighter-rouge">--allow-all</code> 那組)、custom agents、skills、MCP 都有。核心則比較像 Claude Agent SDK: repo 裡各語言的外層套件是 MIT 開源，但真正的 agent loop 跑在那個一起裝進來的 Copilot CLI 執行檔裡，而它是 GitHub 的閉源產品，你能用它調好的迴圈，但看不到也改不了內部。</p>

<p><strong><a href="https://code.claude.com/docs/en/agent-sdk/overview">Claude Agent SDK</a> (Anthropic, Python / TypeScript)</strong>: 前身是 Claude Code SDK。這裡有一個要特別留意的點: SDK 的外層包裝 (Python / TypeScript) 是 MIT 開源的，但真正的 agent loop 跟 harness 核心，是跑在一個閉源的 Claude Code 原生執行檔裡，SDK 只是去啟動那個執行檔、用 stdio 跟它溝通。換句話說，你能用 Anthropic 那套調好的 agent loop，但看不到、也改不了它的內部。再加上它受 Anthropic 商業條款規範、第三方產品不允許用 claude.ai 帳號登入 (只能用 API key)，可控性是這條路線裡最受限的。六項能力一樣大多內建。</p>

<p><strong><a href="https://github.com/langchain-ai/deepagents">LangChain deepagents</a> (Python / TypeScript)</strong>: MIT 開源，建在 LangGraph 之上，等於把 Claude Code 那套 deep agent 的做法重新實作成一個開源套件。六項能力都有對應工具，不過預設的檔案系統是虛擬的 (記在記憶體裡、不會跨 session 留存)，bash / shell 要自己開啟。最大的好處是每一部分 (檔案系統、sandbox、記憶) 都是可替換的 backend，要改不用 fork 整包。</p>

<p>這條路線內部其實也有差異: 前面四個預設就把六項能力跟大量預設行為都準備好，但也有走相反方向、把核心刻意做到最精簡的。</p>

<p><strong><a href="https://pi.dev/">Pi</a> (Earendil, TypeScript)</strong>: 這條路線裡的極簡派。它跟前面幾個一樣是 coding agent 出身，開箱就有一個能跑的 agent、TUI，以及一份預設的 system prompt。差別在預設啟用的東西很少: 只留 read / write / edit / bash 四個工具、system prompt 刻意做到最精簡 (不到 1000 token，有時約 200)。但 plan mode、子代理 (附 planner / reviewer / scout / worker)、todo、權限、sandbox 這些，官方都用範例 extension 提供，要哪個自己裝上就好 (MCP 比較例外，repo 裡沒有，得自己做或裝第三方)。背後假設是: 用 RL 訓練過的前沿模型，本來就不太需要太多預設行為，核心保持最精簡、能力按需要再加上去。MIT 全開源，連 agent loop 都讀得到、改得動，可控性是這條路線裡最高的。Pi 會受到注意，很大一部分是因為 <a href="https://github.com/openclaw/openclaw">OpenClaw</a> 就建在這四個 Pi 套件之上: 一個能在 WhatsApp、Telegram、Discord、Slack、iMessage 等多種通訊軟體上跑、共用記憶跟持久化 session 的個人 AI 助理。OpenClaw 在 GitHub 上累積了數十萬 star，大家回頭才發現底下那層 harness 原來這麼精簡。</p>

<blockquote>
  <p>編按: 這條路線還有一個重要差別值得補充: 核心到底開不開源。三者其實差很多。Codex 連 agent loop 都放在開源 repo 裡 (Apache 2.0)，可讀可改; Claude Agent SDK 跟 GitHub Copilot SDK 則是「對外的各語言套件開源、真正的核心閉源」: 你能呼叫它調好的 agent loop，但看不到、也改不了。Claude 的核心是閉源的 Claude Code 原生執行檔; Copilot 的核心是 Copilot CLI，它雖然有一個 <a href="https://github.com/github/copilot-cli">github/copilot-cli</a> 的 repo，但點進去會發現裡面只有安裝腳本 (install.sh)、README、changelog 和一份授權檔，沒有任何實作原始碼，而那份授權還是專有商業授權、明文禁止修改。換句話說那個 repo 是用來發佈安裝、收 issue 的，不是放原始碼的地方。如果你要做的是對外 production 服務、需要看得到原始碼才好 debug，這個差別要先想清楚。</p>
</blockquote>

<h2 id="路線二-從基礎構建">路線二: 從基礎構建</h2>

<p>這條路線的框架，給的是控制流、編排、型別、持久化這些基礎元件。六項 Deep Agent 能力不會預設都幫你啟用，要自己設定、添加。而且連那份預設行為很強的 system prompt 也要自己寫: 框架只給你一個近乎空白的起點，agent 該怎麼規劃、怎麼自我修正，都得你自己設計。換來的是完全的可控性。這條路線內部其實差異不小，與其硬分風格，不如照框架有多 opinionated (幫你預設了多少、規定了多少) 來排序: 排在前面的比較 opinionated，內建跟預設都比較多; 排在後面的比較低階，更多事情要你自己決定。順序大致如下 (每家支援的程式語言一併標在後面):</p>

<ul>
  <li><strong><a href="https://strandsagents.com/">Strands Agents</a> (AWS, Python / TypeScript)</strong>: Apache 2.0 開源，主打 model-driven 的迴圈: 不要開發者去畫複雜的流程圖，而是把規劃、串接、呼叫工具、反思交給模型自己在迴圈裡決定。這個取向讓 Strands 在這條路線裡相當 opinionated、也最接近 deep agent: 直接內建了讀寫檔案、shell、code interpreter、子代理人、browser、computer use 這些工具。AWS 內部的 Amazon Q Developer、AWS Glue 都用它。</li>
  <li><strong><a href="https://adk.dev/">Google ADK</a> (Python、Java 為主，另有 TypeScript / Go / Kotlin)</strong>: Apache 2.0 開源，功能同樣齊全，但重點在多代理人編排而不是單一迴圈: 內建 planner (讓模型先產出一份規劃再行動，但不是逐項打勾的待辦清單)、Sequential / Parallel / Loop 的多代理人編排、以及分得很清楚的 session (對話內) 跟 memory service (跨 session 可搜尋的長期記憶)，還原生支援 A2A 協議跟 Computer Use。</li>
  <li><strong><a href="https://github.com/microsoft/agent-framework">Microsoft Agent Framework</a> (.NET 跟 Python)</strong>: MIT 開源，是微軟把 Semantic Kernel 跟 AutoGen 合併後的接班框架。核心是 AI Agent 加上 graph-based 的 workflow 編排，多代理人跟記憶內建，主打企業級需求: 持久化 checkpoint、OpenTelemetry 可觀測性、middleware、A2A、人為介入。檔案系統這類則要自己接。</li>
  <li><strong><a href="https://openai.github.io/openai-agents-python/">OpenAI Agents SDK</a> (Python 跟 JS / TypeScript)</strong>: MIT 開源，比較不 opinionated: 核心就是 Agent、Handoff、Guardrail、Session、Tool 幾個概念，內建一個預設的 agent 迴圈，但六項能力多半要自己補。多代理人協作靠 Handoff 跟「agent 當成工具」用一般 Python 串起來，不是用一張圖去定義。</li>
  <li><strong><a href="https://ai.pydantic.dev/">Pydantic AI</a> (只有 Python)</strong>: MIT 開源，由 Pydantic 團隊出品，是型別優先的框架，強項是型別安全: 用 Pydantic model 定義結構化輸出、用依賴注入管理 agent 的相依資源，model 無關。MCP 是 client / server 雙向內建，還官方支援 Temporal、DBOS、Prefect、Restate 四種 durable execution (持久化執行) 引擎。</li>
  <li><strong><a href="https://ai-sdk.dev/">Vercel AI SDK</a> (只有 TypeScript / JavaScript)</strong>: Apache 2.0 開源，TypeScript 優先的工具包。核心是呼叫 LLM 的統一介面加上 Zod schema 做型別安全的結構化輸出，v7 開始有正式的 agent 迴圈 (<code class="language-plaintext highlighter-rouge">ToolLoopAgent</code>) 和 sandbox 執行環境，但六項能力的工具大多要自己接或從社群套件補。跟其他框架最大的差別是內建前端 UI 整合: React、Svelte、Vue、Solid 都有現成的 chat hooks，如果你的 agent 需要即時串流到前端介面，這是其他框架都沒直接提供的。Model 無關，支援 30+ 個 provider。</li>
  <li><strong><a href="https://github.com/langchain-ai/langgraph">LangGraph</a> (LangChain, Python 跟 JS / TypeScript)</strong>: MIT 開源，是這裡最不 opinionated、最低階的一個: 本質是用圖 (節點、邊、共享 state) 描述的狀態機，連 agent 迴圈都要你自己用節點接出來。它的強項是持久化: checkpointer 把每一輪 state 存起來，支援人為介入、時光回溯 (time-travel) 除錯、斷點續跑，跨 session 長期記憶則用 Store。前面提的 deepagents 就是建在它上面，等於有人幫你把那層 deep agent 接好了。</li>
</ul>

<h2 id="各框架的六項能力對照">各框架的六項能力對照</h2>

<p>用第 1 篇那六項能力逐一對照這些框架，差別就很清楚了。</p>

<div style="font-size:.85em;color:var(--text-secondary);margin:18px 0 6px;line-height:1.6;">開發語言 = 你串接這個框架時要寫的程式語言 (不是框架本身的實作語言)。能力欄對應第 1 篇六項: ① 規劃 (Plan &amp; Todos) · ② 檔案/Bash (Filesystem &amp; Bash) · ③ 子代理 (Sub-Agent) · ④ 記憶 (Memory) · ⑤ Skills · ⑥ 額外工具 (MCP 等)。標示: ✅ 內建 · 🟡 部分或要自行開啟 · 🔧 自己接。</div>

<div style="overflow-x:auto;margin:6px 0 24px;">
<table style="border-collapse:collapse;width:100%;font-size:.85em;min-width:720px;">
<thead>
<tr style="background:var(--surface);">
<th style="text-align:left;padding:8px 10px;border:1px solid var(--border);">框架</th>
<th style="text-align:left;padding:8px 10px;border:1px solid var(--border);">開發語言</th>
<th style="padding:8px;border:1px solid var(--border);">① 規劃</th>
<th style="padding:8px;border:1px solid var(--border);">② 檔案/Bash</th>
<th style="padding:8px;border:1px solid var(--border);">③ 子代理</th>
<th style="padding:8px;border:1px solid var(--border);">④ 記憶</th>
<th style="padding:8px;border:1px solid var(--border);">⑤ Skills</th>
<th style="padding:8px;border:1px solid var(--border);">⑥ 額外工具</th>
</tr>
</thead>
<tbody>
<tr style="background:var(--tag-bg);"><td colspan="8" style="padding:6px 10px;border:1px solid var(--border);font-weight:700;color:var(--accent);">路線一: 全套 Deep Agent</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Codex SDK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">TS · Py</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Codex App Server</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">不限 (JSON-RPC)</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">GitHub Copilot SDK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Node · Py · Go · .NET · Java · Rust</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Claude Agent SDK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · TS</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">LangChain deepagents</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · TS</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Pi (極簡)</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">TS</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td></tr>
<tr style="background:var(--tag-bg);"><td colspan="8" style="padding:6px 10px;border:1px solid var(--border);font-weight:700;color:var(--accent);">路線二: 從基礎構建 (opinionated 由高到低)</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Strands Agents</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · TS</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Google ADK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · Java · TS · Go · Kotlin</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Microsoft Agent Framework</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">.NET · Py</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">OpenAI Agents SDK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · TS</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">✅</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Pydantic AI</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">只有 Py</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">Vercel AI SDK</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">只有 TS / JS</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td></tr>
<tr><td style="padding:8px 10px;border:1px solid var(--border);">LangGraph (核心)</td><td style="padding:8px 10px;border:1px solid var(--border);white-space:nowrap;">Py · TS</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td><td style="text-align:center;border:1px solid var(--border);">✅</td><td style="text-align:center;border:1px solid var(--border);">🔧</td><td style="text-align:center;border:1px solid var(--border);">🟡</td></tr>
</tbody>
</table>
</div>

<p>從開發語言看，幾乎每家都支援 Python 跟 TypeScript，這兩個是目前 agent 開發的主流語言 (ADK 另外還有 Java、Go、Kotlin，Pydantic AI 則只有 Python、Vercel AI SDK 只有 TypeScript / JavaScript，GitHub Copilot SDK 第一方支援到六種、是最多的)。比較特別的是 Codex App Server: 它做成 JSON-RPC server，所以不限定你的程式用什麼語言 (Copilot SDK 同樣靠 JSON-RPC 跟 CLI 溝通)。</p>

<h2 id="兩條路線的優缺點">兩條路線的優缺點</h2>

<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0;">
<div style="flex:1 1 300px;border:1px solid var(--border);border-top:3px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;margin-bottom:10px;">全套 Deep Agent</div>
<div style="color:#1a7f37;font-weight:600;font-size:.92em;">優點</div>
<div style="font-size:.9em;color:var(--text);margin:4px 0 10px;">馬上有能跑的 agent; 六項能力、加上一份調校過、預設行為很強的 system prompt 都已內建，不用從頭做; 這套 harness 是原廠針對自家模型調過的。</div>
<div style="color:#cf222e;font-weight:600;font-size:.92em;">缺點</div>
<div style="font-size:.9em;color:var(--text);margin-top:4px;">是給通用任務用的，會帶你的場景用不到的 context 跟工具; 可控性、可改性、可維護性受限 (Claude Agent SDK 閉源最明顯); 通常和特定模型供應商綁定。</div>
</div>
<div style="flex:1 1 300px;border:1px solid var(--border);border-top:3px solid var(--accent);border-radius:8px;padding:16px 18px;background:var(--surface);">
<div style="font-weight:700;margin-bottom:10px;">從基礎構建</div>
<div style="color:#1a7f37;font-weight:600;font-size:.92em;">優點</div>
<div style="font-size:.9em;color:var(--text);margin:4px 0 10px;">完全可控，可以貼著自己的任務分布，把無關的 context 跟工具拿掉; model 無關，可換供應商、好控制成本; 適合要精準掌握 context / 延遲 / 成本的產品場景。</div>
<div style="color:#cf222e;font-weight:600;font-size:.92em;">缺點</div>
<div style="font-size:.9em;color:var(--text);margin-top:4px;">要自己設計、組裝需要用到的能力，花的時間跟工程量會多一些。</div>
</div>
</div>

<p>還有一個容易被忽略、但對 production 服務很重要的維度: 維護跟 debug。全套 Deep Agent 路線有不少底層不在你的控制範圍內，一旦出問題比較難處理; 核心閉源的更麻煩 (Claude Agent SDK、GitHub Copilot SDK 都是這型): 底層出狀況你改不了、只能等官方。以 Claude Agent SDK 為例，它 GitHub 上其實累積了不少還沒解決的 issue (官方不一定有空處理)。如果你要做的是對外的 production 服務，這個底層夠不夠單純、好不好維護 (至少要能看到原始碼)，會是很實際的考量。從基礎構建雖然一開始要自己接，但底層都在你自己這邊，要 debug、要改都比較有把握。</p>

<h2 id="那到底該選哪條">那到底該選哪條?</h2>

<p>沒有一體適用的答案，要看你做的是什麼。小編整理三個比較清楚的情境:</p>

<p>🔹 <strong>特定情境應用，或是 B2C 使用者規模比較大的產品 → 建議從基礎構建</strong>。從基礎構建可以更有效率: 貼著單一任務分布做的精簡版本，token 成本比較低、延遲也比較低。使用者一多，這些成本、延遲、可控性的差異會更明顯，你會希望把通用 deep agent 那些用不到的 context 跟工具都拿掉 (也就是貼著單一用途的 vertical agent)。能換模型供應商、能精算每一項成本，這時候很重要。</p>

<p>🔹 <strong>自用、或企業內部軟體開發、要打造自己團隊用的 harness 跟 outer loop → 建議從現成 deep agent 開始</strong>。這種場景你要的本來就是一個強的通用開發 agent，原廠已經幫你把內層調好了，沒必要重做。使用者數量沒那麼大，每個 session 多帶一點通用能力的成本不是重點，先有個能跑的東西、把你的 outer loop 疊上去比較划算。</p>

<p>🔹 <strong>如果你想做的是讓使用者用自己的 ChatGPT 訂閱帳號 → 可以看看 Codex SDK / app server</strong>。Codex 支援用 ChatGPT 訂閱帳號登入，不一定要用 API key 計費。對比之下 Claude Agent SDK 第三方使用只能用 API key、不能用 claude.ai 帳號登入。如果你的產品設計是「使用者帶自己的帳號來用」，這個差別會直接影響你做不做得出來。</p>

<h2 id="最後-框架只是起點">最後: 框架只是起點</h2>

<p>框架是起點，不是終點。選全套 Deep Agent，起點比較高，一開始就有個能跑的 agent，但相對不好改; 選從基礎構建，起點低、要自己接的多，但彈性跟可控性比較好，可以一步步朝你想要的設計前進。</p>

<p>不管選哪邊，框架幫你接好的都還是第 1 篇講的「能不能做」。真正讓 agent 做得對、做得完的那套 harness (工具層檢查、單輪驗收、外層 loop、eval gate)，不管哪個框架都得自己建。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Coding" /><summary type="html"><![CDATA[📚 給 Agent 開發者的駕馭工程 (全 9 篇) 1. 基礎: Deep Agent 的六項內建能力 2. 核心: Agent 要的是回饋迴路，不是完美提示 3. 回饋時機一: 工具回傳值，是寫給 agent 的回饋 4. 回饋時機二: 兩次 model request 之間，把訊息注入執行中的 agent 5. 回饋時機三: 單輪結束的驗收，Goal 與 Outcomes 6. 回饋時機四: 外層 Loop, Ralph、Symphony 與 Cron 7. 進階: 自我改進 Harness, Meta-Harness 與爬坡 8. 收尾: 會過期的 Harness, Model-Harness-Fit 與 Bitter Lesson 9. 自建 Agent 的框架選型: 全套 Deep Agent 還是從基礎構建? (本篇) 🎞️ 搭配演講投影片: 給 Agent 開發者的 Harness+Loop Engineering]]></summary></entry><entry><title type="html">當模型表現取決於推論算力: 評測分數正在失去意義，LLM 能力上限也量不出來</title><link href="https://blog.aihao.tw/2026/06/11/test-time-compute-evals/" rel="alternate" type="text/html" title="當模型表現取決於推論算力: 評測分數正在失去意義，LLM 能力上限也量不出來" /><published>2026-06-11T00:00:00+00:00</published><updated>2026-06-11T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/11/test-time-compute-evals</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/11/test-time-compute-evals/"><![CDATA[<p>OpenAI 研究員 Noam Brown (推理模型 o1 背後的關鍵人物) 發了一篇長文 <a href="https://x.com/polynoamial/status/2064210146558136827">Implications of Large-Scale Test-Time Compute</a>，蠻有料的。核心論點一句話就能講完: 隨著 LLM 越來越強，benchmark 分數越來越取決於模型在推論時用掉多少算力，也就是「測試時算力」(test-time compute)。我們很可能不知道現代 LLM 的能力上限在哪裡，因為要實際量測它太昂貴了。所以評估方式該改了: 不要只報一個分數，而是畫出一條曲線，呈現模型在不同 token 數、成本或時間下的表現。</p>

<p>以下整理重點，並補充幾篇相關研究。</p>

<h2 id="同一個模型換個-x-軸就是另一個故事">同一個模型，換個 x 軸就是另一個故事</h2>

<p>GPT-5.5 剛發布時，第一波反應是質疑: benchmark 數字有進步，但不多。幾小時後大家實際上手，才發現它比 GPT-5.4 強了一個檔次。經典的「benchmark 成績表格」顯然沒有反映出全貌。</p>

<p>原因是 GPT-5.5 並不是在跟 5.4 相同的 token 預算(或美元預算)下評估的。本文封面圖就是 Noam 給的對比: 左邊的長條圖上，兩個模型只差 2.8 個百分點，看起來進步不大。但右邊把 x 軸換成輸出 token 數之後，故事完全不同: 在相同的 token 預算下，5.5 明顯強一截，5.4 要花將近 3 倍的 token 才能追到接近的分數。把測試時算力這個變因控制住，兩個模型的真正差距才顯現出來。</p>

<h2 id="為什麼不直接把算力開到飽和再評估">為什麼不直接把算力開到飽和再評估?</h2>

<p>最直覺的反問是: 那就讓模型一直算下去，算到分數不再進步為止，量到的不就是上限了? Noam 說問題在於: 實際經驗上，「分數不再進步」這個停滯點(plateau)非常遠，在合理的預算內甚至可能根本看不到。</p>

<p>他舉了兩個例子。一個是 Karpathy 的 autoresearch 實驗，跑了數百次實驗後，表現仍在繼續進步。另一個是英國 AI Security Institute 的資安攻防評測，跑到 1 億 tokens，Mythos 和 GPT-5.5 的表現還在快速上升:</p>

<p><img src="/assets/images/test-time-compute-evals/aisi-cyber-eval.jpg" alt="" /></p>

<p>而且注意看這張圖: 越強的模型，分數隨算力成長的幅度也越大。模型越強，就越能在長時間跨度(long horizon)的任務上持續有效運作，停滯點被推得更遠，甚至可能消失。</p>

<blockquote>
  <p>編按: 「量測上限太昂貴」不是修辭。X 上最近流傳一篇據稱是 Anthropic 未發布模型 Mythos 的企業試點測試心得(<a href="https://x.com/Tz_2022/status/2064355852539019546">中文翻譯</a>)，作者說光這一輪測試就花了超過 100 萬美元的推論費用，而他們公司全員上個月的推論算力總開銷也才 200 萬美元。內容真偽無法驗證，但這個量級跟上面那條「跑到 1 億 tokens 還在爬升」的曲線是一致的: 想知道前沿模型的上限在哪，先準備好足夠的預算。</p>
</blockquote>

<h2 id="該怎麼評估-把曲線畫出來">該怎麼評估: 把曲線畫出來</h2>

<p>Noam 認為正確的評估方式，是畫出「表現 vs 測試時算力」的曲線，x 軸可以用 token 數、成本或時間。已經有 benchmark 這樣做了，例如 ARC-AGI 的排行榜，直接畫出「分數 vs 每題成本」:</p>

<p><img src="/assets/images/test-time-compute-evals/arc-agi-2-leaderboard.jpg" alt="" /></p>

<p>另一個合理的做法是設定明確的 token、時間或成本預算，並且事先告知模型，就像人類考 SAT 或數學奧林匹亞也是限時的。</p>

<p>三種 x 軸各有取捨:</p>

<ul>
  <li><strong>Token 數</strong>: 不同模型之間不能直接比較，因為每家的 tokenizer、生成速度、單價都不同</li>
  <li><strong>美元成本</strong>: 受 batching、硬體利用率等實作細節影響，而且成本和延遲之間會互相取捨</li>
  <li><strong>實際耗時</strong>: 像 best-of-N (平行跑 N 次取最好的結果)這類技巧，可以在幾乎不增加耗時的情況下用掉更多算力，所以時間軸會低估算力用量</li>
</ul>

<p>但 Noam 的重點是: 不管選哪一個，任何一條曲線都比單一分數有資訊量。</p>

<h2 id="對-ai-安全的影響-安全評估該用多大的預算跑">對 AI 安全的影響: 安全評估該用多大的預算跑?</h2>

<p>前沿模型發布前，實驗室通常會評估資安攻擊、生物武器等濫用風險，超過能力門檻就要先做好緩解措施才能發布。但如果模型能力取決於用了多少推論算力，那安全評估該用多大的預算來跑? 實務上，多數安全評估根本沒有考慮這件事。</p>

<p>Gemini 3 Deep Think 發布時，benchmark 分數比之前的模型高出一截，卻沒有附上說明風險評估的 model card，引發 AI 安全社群的不滿。但 Noam 認為這個批評沒打到點上: Deep Think 很可能是拿其他「有」做過安全評估的模型，外面再包一層鷹架(scaffold)搭出來的。任何人只要願意付出 Deep Think 等級的推論費用，自己把多次模型呼叫串起來，大概也能重現同樣的能力。Deep Think 只是讓一般使用者更方便取得而已。</p>

<p>真正該檢討的是: Gemini 3 和其他模型發布時，安全報告都沒有把能力表示成測試時算力的函數。一個有決心的國家級行為者，可以對單一任務投入超過 1000 萬美元的推論算力; 但評估模型通常要跑數千甚至數百萬次任務，每一次都用這麼高的預算並不切實際。好消息是，表現隨算力擴展的走勢還算可預測，所以可以在較低預算下實際量測，再(帶著不確定性)外推高預算下的能力。Noam 理想中的模型評估長這樣:</p>

<p><img src="/assets/images/test-time-compute-evals/ideal-eval.jpg" alt="" /></p>

<p>文章還點出一個之後會更麻煩的問題: 要確認一個 agent 連續運作一年都不會出現對齊問題(misalignment)，可能唯一的辦法就是真的讓它跑一年。當 agent 的運作時間超過新模型的開發週期，實驗室可能根本來不及在發布前完成完整評估。</p>

<h2 id="noam-brown-給-ai-社群的三點具體建議">Noam Brown 給 AI 社群的三點具體建議</h2>

<ol>
  <li>實驗室發布新模型時，應該公布以 token 數、成本或時間為 x 軸的 benchmark 表現。至少也要報告達成那個分數用掉了多少推論預算</li>
  <li>Benchmark 排行榜應該一併追蹤推論用量，或是設定明確的 token、成本、時間預算</li>
  <li>各家實驗室的安全政策(如 OpenAI 的 Preparedness Framework、Anthropic 的 Responsible Scaling Policy)在判定模型是否跨過安全門檻時，應該把推論算力明確納入考量，並在多個預算下評估，包含從小預算外推的估計(附上不確定性)</li>
</ol>

<h2 id="不只-noam-在講-忽略成本的比較正在失效">不只 Noam 在講: 忽略成本的比較正在失效</h2>

<p>小編順手查了一下這個主題的其他研究，發現同樣的觀點已經累積不少證據:</p>

<h3 id="arc-prize-額外的準確率是可以用錢買的">ARC Prize: 額外的準確率是可以用錢買的</h3>

<p>ARC Prize 共同創辦人 Mike Knoop 在<a href="https://arcprize.org/blog/which-ai-reasoning-model-is-best">測評各家推理系統</a>時講得更直接:「所有 benchmark 和 model card 的報告都必須沿著兩個軸來做，因為額外的準確率是可以用錢買的。光禿禿的準確率分數是行銷，不是科學。」(原文: Naked accuracy scores are marketing, not science.) 他們的測試結論也是沒有單一贏家: 要最高準確率和要性價比，最佳選擇完全不同。</p>

<h3 id="artificial-analysis-的-claude-fable-5-評測">Artificial Analysis 的 Claude Fable 5 評測</h3>

<p>Artificial Analysis 這週發布的 <a href="https://x.com/ArtificialAnlys/status/2064500152430383489">Claude Fable 5 評測</a>就是一個現成的例子。下圖上半部是 Humanity’s Last Exam 的分數排行，Claude Fable 5 以 53.3% 居首; 下半部則是跑完整輪評測的總成本，Fable 5 要 $2,174，而 GPT-5.5 (xhigh) 拿 44.3% 花 $820、GPT-5.5 (high) 拿 43.0% 只花 $489。上下兩半對照著看，結論就從「Claude 領先 9 個百分點」變成「Claude 多花 2.7 倍的成本，買到 9 個百分點」。哪個划算，取決於你的任務值多少錢。若你只看分數排行，不看總成本，那你就無法判斷是否划算:</p>

<p><img src="/assets/images/test-time-compute-evals/aa-hle-cost.jpg" alt="" /></p>

<p>順帶一提，Artificial Analysis 網站上也有「<a href="https://artificialanalysis.ai/models#intelligence-index-tokens-cost">Intelligence Index vs 評測成本</a>」的散點圖，x 軸是跑完整套評測的成本(對數刻度)，這是目前業界做模型選型時最常引用的圖表之一。下圖左上角的綠色區塊標示「最划算象限」，GPT-5.5 (xhigh) 和 Claude Opus 4.8 (max) 這些最強模型則都落在右側最貴的那一區:</p>

<p><img src="/assets/images/test-time-compute-evals/aa-intelligence-vs-cost.png" alt="" /></p>

<h3 id="為什麼散點圖上沒有-claude-fable-5">為什麼散點圖上沒有 Claude Fable 5?</h3>

<p>不知為何，上面這張圖還沒有標出剛拿下 Intelligence Index 第一名 (64.9 分) 的 Claude Fable 5。AA 的模型頁面上 Fable 5 跑評測的 token 用量標示為 Unknown，小編猜可能是因為它的成本特別難算。Fable 5 有好幾個會在執行時動態改變行為和計費的機制:</p>

<ol>
  <li><strong>Fallback 機制</strong>: 約 9% 的任務會轉給 Opus 4.8 跑、並按 Opus 的單價計費，總成本取決於有多少任務被轉走</li>
  <li><strong>Adaptive thinking 預設全程開啟</strong>: 模型自行決定每一題要思考多深，token 用量無法事先固定</li>
  <li><strong>靜默降級</strong>: 根據 <a href="https://www-cdn.anthropic.com/d00db56fa754a1b115b6dd7cb2e3c342ee809620.pdf">system card</a> 第 12-13 頁，偵測到「前沿 LLM 開發」用途時(例如 pretraining pipeline、分散式訓練基礎設施、ML 加速器設計，約佔 0.03% 流量)會靜默限制模型能力: 不換模型、照 Fable 5 原價計費，也不通知使用者，從 API response 完全看不出來。模型甚至不會拒絕，仍會照常配合回應，只是這類任務的輸出效果被刻意壓低。至於怎麼壓低，Anthropic 沒有明講細節，只舉例說作法有: prompt 修改(在使用者看不到的地方改寫或附加指令)、steering vectors(推論時在模型內部的激活值上加一個方向向量，把行為往特定方向推)、參數高效微調(PEFT，掛上讓特定能力變弱的少量微調參數)。<a href="https://x.com/giffmana/status/2064446918541881734">Lucas Beyer 等研究員這幾天在 X 上嘲諷的就是這個機制</a></li>
</ol>

<blockquote>
  <p>小編補充 (2026/6/12 更新): 靜默降級有後續發展。在研究社群一片批評聲後，Anthropic 於 6/11 <a href="https://x.com/claudedevs/status/2064949876463645026">宣布</a>把它改成可見: 被標記的請求會跟 cyber/bio 防護一樣，明顯地 fallback 到 Opus 4.8，每次發生都看得到，API 也會回傳拒絕原因。官方承認當初選擇不可見的防護「是錯誤的取捨」並道歉。代價是防護變可見後更容易被探測繞過，為了維持對 jailbreak 的強健性，改進 classifier 期間誤判會變多。</p>
</blockquote>

<p>前兩個機制影響的是成本，第三個影響的是同一筆錢買到的能力是否一致。這些機制從外部都看不到也控制不了，同一套評測跑出來的 token 數、計費單價、甚至模型行為都可能不同，要報告一個可重現的成本數字就難了。</p>

<p>小編用已公布的數據回推: HLE 單項 Fable 5 花了 $2,174，是 Opus 4.8 的 1.24 倍，但這是它最省的場景; 第三方在文字生成和 agentic 評測上實測的成本是 Opus 4.8 的 2~3 倍。合起來粗估，Fable 5 跑完整套評測約要 $9,000~$11,000，約是 Opus 4.8 (max) 的 2 倍以上，已經靠近圖上 x 軸 $10k 的最右邊了。</p>

<h3 id="帕雷托前緣-pareto-frontier">帕雷托前緣 (Pareto frontier)</h3>

<p>這類「智慧 vs 成本」的散點圖通稱帕雷托前緣(Pareto frontier): 把「沒有其他模型同時比它更便宜又更聰明」的模型連成一條外緣線，選模型就沿著這條線挑，線內側的模型都存在又便宜又強的替代品。下圖是 <a href="https://x.com/AaronBergman18/status/2060248776929947703">Aaron Bergman</a> 上個月用 Artificial Analysis 數據畫的版本，虛線就是前緣。可以注意到前緣的中低價位段幾乎全是開放權重模型(藍點: Qwen、DeepSeek、MiMo)，閉源模型(紅點)只守住右上角的高智慧高價端:</p>

<p><img src="/assets/images/test-time-compute-evals/pareto-frontier-open-weights.jpg" alt="" /></p>

<p>swyx 的 Latent Space 從 2024 年就開始<a href="https://www.latent.space/p/lmarena">追蹤這條前緣</a>隨時間推移的速度，而且它移動得非常快: 根據 <a href="https://epoch.ai/trends">Epoch AI 的統計</a>，達到固定表現水準的推論成本大約每兩個月就砍半。也就是說，今天落在前緣上的模型，幾個月後就可能被更便宜的新模型蓋過，「哪個模型最划算」的結論有效期很短，需要定期重新檢視。</p>

<h3 id="拉齊預算後推理技巧的優勢會縮水">拉齊預算後，推理技巧的優勢會縮水</h3>

<p>EMNLP 2024 的 <a href="https://aclanthology.org/2024.emnlp-main.1112.pdf">Reasoning in Token Economies</a> 把各種推理策略放在相同的推論預算下重新評估，發現 Multi-Agent Debate、Reflexion 這些方法的優勢大幅縮水，多數情況下反而輸給簡單的基準做法 self-consistency (對同一題多次取樣再投票)。很多「新方法帶來的進步」，其實只是用了更多預算。這對評估 prompt 技巧和 agent 架構是同樣的提醒: 沒有控制預算的 A/B 比較，結論可能是錯的。</p>

<h3 id="看牌價選模型也會被誤導">看牌價選模型也會被誤導</h3>

<p><a href="https://arxiv.org/abs/2511.05722">OckBench</a> 發現每 token 單價只有一半的 7B 模型，因為產出 3 倍的 token 數量，實際每次查詢的成本反而貴 57%，他們稱之為「過度思考稅」(overthinking tax)。<a href="https://arxiv.org/abs/2603.23971">另一篇研究</a>系統性測了 8 個推理模型在 12 種任務上的表現，發現模型兩兩比較時，有 32% 的組合牌價排序跟實際總成本排序是相反的。</p>

<h2 id="小結">小結</h2>

<p>Noam 自己也說，這篇文章對長期追蹤的人來說沒什麼新東西: 從 2024 年 9 月 o1 發布那天起，大家就知道推理模型的表現會隨推論算力擴展。但快兩年過去，前沿實驗室發布新模型還是只報告單一數字，安全機構還是會對「鷹架架構(scaffold)用 100 倍預算打出更高分」感到意外。</p>

<p>對做模型選型的工程師來說，這篇的實際意義很具體: 下次比較模型時，需要考慮「在我的預算下哪一個模型最強」，而不是「誰的分數最高」。同一條曲線上的不同點，其實是不同的產品。</p>]]></content><author><name>ihower</name></author><category term="Eval" /><category term="Benchmark" /><category term="LLM" /><summary type="html"><![CDATA[OpenAI 研究員 Noam Brown (推理模型 o1 背後的關鍵人物) 發了一篇長文 Implications of Large-Scale Test-Time Compute，蠻有料的。核心論點一句話就能講完: 隨著 LLM 越來越強，benchmark 分數越來越取決於模型在推論時用掉多少算力，也就是「測試時算力」(test-time compute)。我們很可能不知道現代 LLM 的能力上限在哪裡，因為要實際量測它太昂貴了。所以評估方式該改了: 不要只報一個分數，而是畫出一條曲線，呈現模型在不同 token 數、成本或時間下的表現。]]></summary></entry><entry><title type="html">Microsoft AI: 從零練起的 MAI 模型和平台佈局</title><link href="https://blog.aihao.tw/2026/06/08/microsoft-mai-models/" rel="alternate" type="text/html" title="Microsoft AI: 從零練起的 MAI 模型和平台佈局" /><published>2026-06-08T00:00:00+00:00</published><updated>2026-06-08T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/08/microsoft-mai-models</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/08/microsoft-mai-models/"><![CDATA[<p>你可能知道 Microsoft 跟 OpenAI 合作很深，但比較少人注意到: Microsoft 其實在兩年前就默默開始自己練模型了。</p>

<p><a href="https://microsoft.ai/">MAI (Microsoft AI)</a> 是 2024 年 Microsoft 收購 DeepMind 共同創辦人 <a href="https://x.com/mustafasuleyman">Mustafa Suleyman</a> 的 Inflection AI 後成立的內部前沿模型實驗室。定位上跟 OpenAI 的合作是並行的: OpenAI 繼續提供 GPT 系列，MAI 則讓 Microsoft 擁有完全自主掌控的模型線。</p>

<p>你可能會問: Microsoft 不是已經有 <a href="https://azure.microsoft.com/en-us/blog/one-year-of-phi-small-language-models-making-big-leaps-in-ai/">Phi 系列</a>了嗎? Phi 是 Microsoft Research 做的開源小模型(最新的 <a href="https://www.microsoft.com/en-us/research/blog/phi-4-reasoning-vision-and-the-lessons-of-training-a-multimodal-reasoning-model/">Phi-4-reasoning</a> 是 15B 參數)，定位在研究貢獻和邊緣裝置部署。MAI 則完全不同: 閉源、前沿規模(1T 參數)、目標是跟 OpenAI、Anthropic、Google DeepMind 同級。兩個是不同團隊、不同目標的產品線。小編之前也沒特別關注這個團隊，直到六月初的 <a href="https://build.microsoft.com/">Build 2026</a> 上他們一口氣端出<a href="https://microsoft.ai/news/building-a-hillclimbing-machine-launching-seven-new-mai-models/">七個模型</a>，才發現值得關注一下。</p>

<p>這七個模型涵蓋推理、程式碼、圖片生成、語音合成和語音辨識，從組建團隊算起只花了約兩年。對做 LLM 應用的開發者來說，多了一個選擇，但更值得關注的是背後的平台策略和技術決策思路。</p>

<h2 id="七個模型哪些跟你有關">七個模型，哪些跟你有關</h2>

<table>
  <thead>
    <tr>
      <th>模型</th>
      <th>定位</th>
      <th>狀態</th>
      <th>取用方式</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://microsoft.ai/news/introducing-mai-thinking-1/">MAI-Thinking-1</a></td>
      <td>旗艦推理，35B 活躍參數的 MoE 架構，256K context</td>
      <td>內部預覽</td>
      <td>Microsoft Foundry，即將開放公開預覽</td>
    </tr>
    <tr>
      <td><a href="https://microsoft.ai/news/introducingmai-code-1-flash/">MAI-Code-1-Flash</a></td>
      <td>5B 輕量程式碼模型</td>
      <td>已上線</td>
      <td>VS Code GitHub Copilot</td>
    </tr>
    <tr>
      <td><a href="https://microsoft.ai/news/introducing-mai-image-2-5/">MAI-Image-2.5</a></td>
      <td>圖片生成/編輯</td>
      <td>已上線</td>
      <td>Foundry、OpenRouter API、PowerPoint</td>
    </tr>
    <tr>
      <td>MAI-Image-2.5-Flash</td>
      <td>上者的低成本版</td>
      <td>已上線</td>
      <td>同上</td>
    </tr>
    <tr>
      <td><a href="https://microsoft.ai/news/mai-transcribe-1-5more-accurate-context-aware-and-built-for-production/">MAI-Transcribe-1.5</a></td>
      <td>語音轉文字，支援 43 語言</td>
      <td>已上線</td>
      <td>Foundry、Teams</td>
    </tr>
    <tr>
      <td><a href="https://microsoft.ai/news/mai-voice-2/">MAI-Voice-2</a></td>
      <td>文字轉語音，可用少量錄音複製聲紋</td>
      <td>已上線</td>
      <td>Foundry、VS Code</td>
    </tr>
  </tbody>
</table>

<p>部分模型也上了 <a href="https://openrouter.ai/">OpenRouter</a>、Fireworks、Baseten 等第三方推理平台。目前沒有開源權重的計畫，走的是 API 和平台模式。</p>

<p><strong>Image 2.5 定價參考</strong>(每百萬 token): 文字輸入 $5 / 圖片輸入 $8 / 圖片輸出 $47。Flash 版約便宜 3-4 倍。</p>

<h2 id="跟現有選擇比如何">跟現有選擇比如何</h2>

<p>先看 MAI-Thinking-1 的 benchmark 數字:</p>

<table>
  <thead>
    <tr>
      <th>Benchmark</th>
      <th>MAI-Thinking-1</th>
      <th>Sonnet 4.6</th>
      <th>Opus 4.6</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AIME 2025 (數學推理)</td>
      <td>97.0%</td>
      <td>95.6%</td>
      <td>99.8%</td>
    </tr>
    <tr>
      <td>SWE-Bench Pro (程式碼)</td>
      <td>52.8%</td>
      <td>–</td>
      <td>53.4%</td>
    </tr>
    <tr>
      <td>SWE-Bench Verified (程式碼)</td>
      <td>73.5%</td>
      <td>79.6%</td>
      <td>80.8%</td>
    </tr>
    <tr>
      <td>GPQA Diamond (科學問答)</td>
      <td>84.2%</td>
      <td>89.9%</td>
      <td>91.3%</td>
    </tr>
    <tr>
      <td>IF Bench (指令遵循)</td>
      <td>69</td>
      <td>86</td>
      <td>–</td>
    </tr>
  </tbody>
</table>

<p>人類盲測對比 Sonnet 4.6: 49% 贏、45% 輸、6% 平手。對比 Opus 4.6: 43% 贏、52% 輸。</p>

<p><a href="https://microsoft.ai/pdf/mai-thinking-1.pdf">論文</a>自己的定位很克制: 「不是領域最強，但在廣泛任務上表現穩定一致。」</p>

<p>社群也注意到幾點:</p>

<ol>
  <li><strong>比較對象的選擇</strong>: Anthropic 在 Build 2026 前幾天才發布了 Opus 4.8，但 MAI 選擇跟較早的 Sonnet 4.6 做比較。</li>
  <li><strong>數字尚未獨立驗證</strong>: 截至目前，<a href="https://news.ycombinator.com/item?id=48374362">第三方評測聚合器</a>上還沒有 MAI-Thinking-1 的獨立測試結果。</li>
  <li><strong>明顯弱項</strong>: 指令遵循能力和終端操作(Terminal-Bench)跟競品差距不小，如果你的應用重度依賴複雜指令，這點要留意。</li>
</ol>

<p>小編的判斷: 作為第一個版本，MAI-Thinking-1 大致在 Sonnet 4.6 同級。如果你已經在用 Claude 或 GPT，目前沒有強烈理由切換。但如果你本來就深度使用 Microsoft 生態系(Azure、GitHub、M365)，整合度是加分項。</p>

<h2 id="mai-code-1-flash-copilot-裡的新選項">MAI-Code-1-Flash: Copilot 裡的新選項</h2>

<p>對每天在寫程式的人來說，<a href="https://microsoft.ai/news/introducingmai-code-1-flash/">MAI-Code-1-Flash</a> 可能是最直接相關的:</p>

<ul>
  <li>只有 5B 參數，但 SWE-Bench Pro 拿到 51.2% (Claude Haiku 4.5 是 35.2%)</li>
  <li>解決困難問題時，token 用量比同級模型少 60%</li>
  <li>有「自適應回應長度」機制: 簡單問題快速回答，複雜問題才展開長思考</li>
  <li>直接在 VS Code 的 Copilot 模型選擇器裡選用，不需額外設定</li>
</ul>

<p>這個模型是直接用 GitHub Copilot 的正式環境訓練的，不是單純對 benchmark 最佳化。對日常寫程式來說，回應速度和 token 效率可能比 benchmark 分數更重要。</p>

<h2 id="零蒸餾-對開發者意味著什麼">零蒸餾: 對開發者意味著什麼</h2>

<p>MAI-Thinking-1 有一個特別的設計選擇: 完全不使用其他模型的蒸餾(也就是不拿 GPT、Claude 等模型的輸出當訓練資料)，推理能力純粹靠自己的強化學習訓練學出來。也不使用合成資料，30T tokens 預訓練資料全部來自人類產出的內容。</p>

<p>這對下游開發者有什麼意義?</p>

<p>🔹 <strong>企業法務面</strong>: 如同 <a href="https://x.com/eliebakouch/status/2061965825037254947">@eliebakouch 的分析</a>，乾淨的資料來源讓企業法務更容易簽字放行。如果你的客戶是大企業或受監管產業，「這個模型沒有用到競爭對手的輸出當訓練資料」是一個可以寫進合約裡的保證。</p>

<p>🔹 <strong>供應鏈獨立性</strong>: 不依賴其他實驗室的模型輸出，意味著 Microsoft 的模型改進不會被上游的 API 政策變動影響。對長期使用 Microsoft 生態系的開發者來說，這是穩定性的保證。</p>

<p>不過社群對「乾淨資料」的說法也有質疑。<a href="https://simonwillison.net/2026/Jun/2/microsofts-new-models/">Simon Willison</a> 指出訓練資料包含 1.2 兆頁公開網頁爬蟲和 GitHub 程式碼。<a href="https://news.ycombinator.com/item?id=48374362">Hacker News</a> 上的討論認為，GitHub 改了使用條款允許用使用者資料訓練 AI，這大概就是所謂「合規授權資料」的意思，跟其他實驗室的做法沒有本質差異。所以「乾淨」更多是指「沒用別家模型的輸出」，不是「完全沒有版權爭議」。</p>

<h2 id="frontier-tuning-讓模型變成你的">Frontier Tuning: 讓模型變成你的</h2>

<p>這次發布中對開發者最有戰略意義的可能是 <a href="https://x.com/mustafasuleyman/status/2062275417378041957">Frontier Tuning</a>。核心概念:</p>

<p>🔹 <strong>強化學習環境(RLE)</strong>: 你建立自己的訓練環境，讓 MAI 模型在你的工作流程中持續學習。不只是 prompt 調整或 LoRA 微調，是真的在你的場景裡做強化學習。</p>

<p>🔹 <strong>實際效果</strong>: Microsoft 內部用 RLE 針對 Excel 的 agent 功能調校，結果跟 GPT-5.4 同等水準但效率高 10 倍。幫 McKinsey 調校後，品質勝過 GPT-5.5，成本低 10 倍。</p>

<p>🔹 <strong>商業定位</strong>: Mustafa 描述為「從租用 AI 到掌控 AI」。調校後的模型權重是你的，別人拿不到。</p>

<p>不過 <a href="https://news.ycombinator.com/item?id=48374362">Hacker News 上也有人吐槽</a>: 實際體驗是一個資料標註介面，需要你提供指令和回饋，每步之間要等很久。離「模型自動觀察你的工作流然後學會」還有段距離。</p>

<h2 id="satya-的觀點-對架構決策的啟發">Satya 的觀點: 對架構決策的啟發</h2>

<p>Satya 在 <a href="https://www.latent.space/p/satya-2026">Latent Space 訪談</a>中分享的幾個觀點，對做 LLM 應用架構決策的人蠻有參考價值:</p>

<p>🔹 <strong>模型只是起點，harness 才是產品</strong>: 每個 Microsoft 產品(GitHub Copilot、Defender)現在都是 multi-model harness，定義了「模型 + 資料 + 工具」的迴圈。上下文層的準備工作是過去兩年最難學到的一課。</p>

<p>🔹 <strong>私有評估集是最大的護城河</strong>: 如果你有自己的 eval，能在不同模型間切換並持續進步，你就掌握主動權。如果你的系統綁死一個模型、沒辦法換，你就沒有議價能力。</p>

<p>🔹 <strong>Token 資產</strong>: 企業累積的執行軌跡(traces)、評估集、上下文是新型態的智慧財產。這個觀點對正在建 AI 產品的團隊很重要: 你的護城河不在於你用哪個模型，而在於你累積了什麼資料和評估能力。</p>

<p>🔹 <strong>小模型 + 好的 harness 一樣能有效爬坡</strong>: 不一定要用最大最貴的模型。5B 參數的 MAI-Code-1-Flash 在正確的 harness 下表現超越大很多的模型。這呼應了「用小模型 + 好的 harness」可能比「直接用最大模型」更划算的實務經驗。</p>

<h2 id="技術報告-有趣的訓練細節">技術報告: 有趣的訓練細節</h2>

<p>這份 <a href="https://microsoft.ai/pdf/mai-thinking-1.pdf">109 頁的技術報告</a>是這次發布中讓社群最驚喜的部分。<a href="https://x.com/nrehiew_/status/2062013300196700395">@nrehiew_</a> 稱它「幾乎可以當成今天 LLM 訓練的教科書」，<a href="https://www.latent.space/p/ainews-microsoft-build-mai-thinking">Latent Space</a> 則評價 MAI 目前是「不錯的第二梯隊新實驗室，在特定領域微調上有明確優勢」。以下挑幾個有意思的點:</p>

<p><strong>架構: 大容量但省推理成本</strong></p>

<p>MAI-Thinking-1 總參數量約 1T，但每次推理只啟動 35B(512 個專家模組裡挑 8 個)。好處是模型知識容量大但推理成本可控。另一個設計是注意力機制大部分層只看附近的文字(局部注意力)，每隔幾層才做一次全文注意力，讓 256K 的長上下文不會讓推理成本暴增。</p>

<p><strong>推理能力是強化學習從零練出來的</strong></p>

<p>跟很多模型先拿 GPT/Claude 的思考過程做蒸餾不同，MAI-Thinking-1 的強化學習起點是一個完全沒見過「思考過程」的基底模型。訓練分成三條路線同時進行: 數學/科學推理、程式碼/工具使用、對話品質與安全性，各自練完再合併成一個模型。</p>

<p>論文展示了數學能力(AIME 2025)從約 20% 爬到 97% 的完整過程，花了約 5000 步。中間有好幾次訓練崩潰，靠的是「自我蒸餾」恢復: 把模型之前產出的好答案收集起來，先微調回穩定狀態，然後繼續強化學習。這種「崩了就從自己的好輸出重來」的做法蠻實務的。</p>

<p><strong>程式碼能力的訓練資料怎麼來的</strong></p>

<p>他們從 GitHub 上 1.02 億個 PR 出發，自動篩選出 26.5 萬個「可以驗證對錯」的程式修改環境(覆蓋 9.4 萬個 repo)，拿來當強化學習的訓練場。模型要實際讀程式碼、改程式碼、跑測試，答對才有獎勵。這個規模和方法對做 coding agent 評估的團隊蠻有參考價值。</p>

<p><strong>小規模實驗的結論不一定能放大</strong></p>

<p>論文揭示了一個有趣的陷阱: 用小模型測試出「資料配比 A 比 B 好」，放大到完整規模後結論可能反轉。實際案例是程式碼比重高的配比在大模型上勝出，但在小模型上反而輸。這對所有在做規模擴展決策或評估設計的人都是個提醒: 小實驗的結論要謹慎外推。</p>

<p><strong>訓練規模</strong></p>

<p>預訓練用了 30T tokens、8,192 張 GB200 GPU。強化學習階段最大的一次訓練動用了 4,864 張 GB300 晶片。</p>

<h2 id="平台佈局-跟-openai-的關係怎麼了">平台佈局: 跟 OpenAI 的關係怎麼了</h2>

<p>要理解 MAI 的戰略意義，得先知道背景: 2026 年 4 月，Microsoft 跟 OpenAI <a href="https://www.theverge.com/ai-artificial-intelligence/942242/microsoft-build-ai-agents-openai-competition">重新談判了合約</a>。OpenAI 解除了只能透過 Azure 發行的限制，可以到其他雲端上架；同時 Microsoft 也正式獲得自行訓練前沿模型的自由。<a href="https://venturebeat.com/technology/microsoft-ai-chief-says-company-was-set-free-from-openai-to-pursue-superintelligence">Suleyman 在受訪時說</a>: 「我們大約在六個月前才從 OpenAI 合約中解放出來，可以正式追求超智慧。所以這還是非常早期的階段。」</p>

<p>他也很坦白地定位 MAI 的現況: 「目標是證明我們能成為全球前四的實驗室。目前重要的三家是 Google DeepMind、OpenAI、Anthropic，我們還不算在其中。」</p>

<p>這讓 Azure 上的模型供給格局從「幾乎只有 OpenAI」變成三條路線並存:</p>

<table>
  <thead>
    <tr>
      <th>路線</th>
      <th>適合場景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>MAI (自家模型)</strong></td>
      <td>企業合規、成本敏感的日常工作負載、Azure 深度整合</td>
    </tr>
    <tr>
      <td><strong>OpenAI on Azure</strong></td>
      <td>最難的推理任務、需要最強模型能力時</td>
    </tr>
    <tr>
      <td><strong>開源/合作夥伴模型</strong> (Llama, Mistral 等)</td>
      <td>需要微調、資料駐留要求、特定任務</td>
    </tr>
  </tbody>
</table>

<p><a href="https://www.digitalapplied.com/blog/microsoft-mai-model-family-build-2026-strategy-analysis">Digital Applied 的分析</a>認為，Azure 開發者現在應該根據任務需求在三條路線之間挑選，而不是像以前一樣預設什麼都用 OpenAI。</p>

<h2 id="microsoft-foundry-開發者實際接觸的界面">Microsoft Foundry: 開發者實際接觸的界面</h2>

<p>對開發者來說，這些模型都是透過 <a href="https://devblogs.microsoft.com/foundry/build-2026-foundry-models/">Microsoft Foundry</a> (原 Azure AI Foundry) 來使用的。幾個跟開發者直接相關的功能:</p>

<p>🔹 <strong>模型目錄</strong>: 超過 12,000 個模型，包含 MAI、OpenAI、Claude、Grok、Llama、Mistral、DeepSeek 等。80% 的 Fortune 500 企業在使用。</p>

<p>🔹 <strong>Model Router</strong>: 根據工作負載特性、成本目標、延遲要求，自動把每個請求路由到最合適的模型。不需要自己寫 routing 邏輯。</p>

<p>🔹 <strong>API 相容性</strong>: REST API 走 <code class="language-plaintext highlighter-rouge">/openai/v1/</code> 路由(chat/completions, embeddings, fine-tuning 等)，SDK 支援 Python、.NET、JS/TS、Java。如果你已經在用 OpenAI 格式的 API，切換成本很低。</p>

<p>🔹 <strong>Agent Service</strong>: 託管式 agent 運行環境，有沙箱隔離、狀態管理、檔案系統存取。</p>

<p><strong>MAIA 200 晶片對開發者的影響</strong>: 開發者不會直接碰到這顆晶片(目前沒有 Azure VM 實例可以租)。它是在 Foundry API 背後默默跑的，好處是 MAI 模型的 token 定價會比較低。<a href="https://x.com/mustafasuleyman/status/2061880164498428188">Mustafa 表示</a>在 MAIA 200 上跑 MAI 模型比 NVIDIA GB200 每美元效能高 30%、每瓦效能高 1.4 倍。</p>

<h2 id="產品整合和垂直領域">產品整合和垂直領域</h2>

<p>🔹 <strong>跨產品整合</strong>: MAI 模型已經嵌入 GitHub Copilot (Code-1-Flash)、Microsoft Teams (Transcribe)、PowerPoint (Image 2.5)、Dynamics 365 (Voice 2)。這種深度整合是第三方模型做不到的。</p>

<p>🔹 <strong>Mayo Clinic 合作</strong>: Microsoft <a href="https://microsoft.ai/news/building-a-hillclimbing-machine-launching-seven-new-mai-models/">宣布</a>與 Mayo Clinic 合作，用去識別化的臨床資料共同訓練醫療領域的前沿模型。這是 Frontier Tuning 在垂直領域的第一個公開案例。</p>

<p>🔹 <strong>Satya 的第三幕定位</strong>: 在 <a href="https://www.latent.space/p/satya-2026">Latent Space 訪談</a>中，Satya 把 Microsoft 的演進描述為「作業系統公司 → 雲端公司 → 智慧平台公司」。MAI 模型是這個「第三幕」的基礎設施層。</p>

<p><a href="https://news.ycombinator.com/item?id=48374362">WindowsForum 的評論</a>則比較冷靜: 「MAI 讓 Microsoft 對自己的 AI 命運有更多掌控權，但不代表使用者會因此更信任 Windows、Office 或 GitHub 裡的 AI 功能。信任要一個功能一個功能地贏回來。」</p>

<h2 id="結論-多了一個選項重點在生態系">結論: 多了一個選項，重點在生態系</h2>

<p>對 LLM 應用開發者來說，MAI 這次發布的意義不在於「又多了一個跟 Sonnet 4.6 差不多的模型」，而在於:</p>

<ol>
  <li><strong>Microsoft 生態系有了自己的模型</strong>: 如果你的產品建在 Azure / GitHub / M365 上，現在有原生整合度更高的選項。</li>
  <li><strong>Frontier Tuning 提供了深度客製化的新路線</strong>: 比一般的微調 API 更深入，但也更重(需要建立訓練環境、提供回饋)。適合有明確領域需求且願意投入的團隊。</li>
  <li><strong>強化了「模型可替換、評估集是護城河」的觀點</strong>: 不管你用不用 MAI，Satya 講的「私有 eval + multi-model harness」是值得認真思考的架構方向。</li>
</ol>

<p>最後想提一點: Microsoft MAI 和 <a href="https://blog.aihao.tw/2026/05/31/alex-wang-rebuilding-meta-ai/">Meta MSL</a> 不約而同都選擇了「從頭練、不蒸餾」的路線。這條路更慢、更貴、更容易失敗，但兩家都認為只有這樣才能建立真正可持續往上爬的能力，而不是靠蒸餾別人的輸出拿到一個無法超越來源模型的天花板。在 AI 快速迭代的時代，願意花兩年從零開始是令人敬佩的。</p>

<p>對照之下，不是每家大公司都做同樣的選擇。回顧今年以來:<a href="https://blog.google/company-news/inside-google/company-announcements/joint-statement-google-apple/">Apple 今年一月宣布</a>下一代基礎模型將改用 Google Gemini，等於放棄自己練前沿模型。<a href="https://www.aboutamazon.com/news/aws/aws-agentic-ai-amazon-bedrock-nova-models">Amazon 的 Nova 系列</a>持續發展，但定位偏向性價比而非前沿智能(<a href="https://www.neowin.net/news/amazon-unveils-nova-premier-its-most-advanced-yet-underwhelming-model/">Neowin 評 Nova Premier</a> 為「最先進但令人失望」，Nova 2 的評測對標是輕量級模型而非頂尖模型)，Amazon 真正的重心在自研晶片 Trainium 和 Bedrock 平台(模型 API 服務)，大手筆<a href="https://www.cnbc.com/2026/04/20/amazon-invest-up-to-25-billion-in-anthropic-part-of-ai-infrastructure.html">投資 Anthropic</a>，也把 <a href="https://www.theregister.com/2026/04/28/openai_climbs_into_amazons_bedrock/">OpenAI 模型上架到 Bedrock</a>。<a href="https://techcrunch.com/2026/05/06/is-xai-a-neocloud-now/">xAI 併入 SpaceX</a> 後重心轉向基礎設施，把資料中心的算力分別租給 <a href="https://512pixels.net/2026/06/google-leasing-spacex-xai/">Anthropic</a> 和 <a href="https://www.cnbc.com/2026/06/05/google-to-pay-spacex-920-million-a-month-for-xai-compute-capacity.html">Google</a>，光租算力就穩賺，不用自己承擔模型研發的風險。</p>

<p>小編整理了三大雲平台目前的前沿模型支援現況:</p>

<table>
<thead>
<tr><th>雲平台</th><th>自家模型</th><th>第三方前沿模型</th><th>開源模型</th></tr>
</thead>
<tbody>
<tr><td>Gemini Enterprise Agent Platform (原 Vertex AI)</td><td><strong>Gemini</strong></td><td>Claude</td><td rowspan="3">Llama、Mistral、DeepSeek、Qwen<br />三家皆有上架</td></tr>
<tr><td>Microsoft Foundry (原 Azure AI Studio)</td><td>MAI</td><td><strong>OpenAI</strong>、Claude</td></tr>
<tr><td>Amazon Bedrock</td><td>Nova</td><td><strong>Claude</strong>、OpenAI</td></tr>
</tbody>
</table>

<hr />

<p>📎 資料來源:</p>
<ul>
  <li><a href="https://microsoft.ai/news/building-a-hillclimbing-machine-launching-seven-new-mai-models/">Building a Hill-Climbing Machine</a> (Microsoft AI Blog)</li>
  <li><a href="https://microsoft.ai/pdf/mai-thinking-1.pdf">MAI-Thinking-1 Technical Report</a> (109 頁完整論文)</li>
  <li><a href="https://www.latent.space/p/satya-2026">Satya Nadella on Latent Space</a> (Build 2026 訪談)</li>
  <li><a href="https://x.com/mustafasuleyman/status/2061880164498428188">Mustafa Suleyman 公告推文</a></li>
</ul>]]></content><author><name>ihower</name></author><category term="LLM" /><category term="Industry" /><summary type="html"><![CDATA[你可能知道 Microsoft 跟 OpenAI 合作很深，但比較少人注意到: Microsoft 其實在兩年前就默默開始自己練模型了。]]></summary></entry><entry><title type="html">從 Code Act 到 Claude Code Dynamic Workflows 深度技術解析</title><link href="https://blog.aihao.tw/2026/06/05/code-act-to-dynamic-workflows/" rel="alternate" type="text/html" title="從 Code Act 到 Claude Code Dynamic Workflows 深度技術解析" /><published>2026-06-05T00:00:00+00:00</published><updated>2026-06-05T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/05/code-act-to-dynamic-workflows</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/05/code-act-to-dynamic-workflows/"><![CDATA[<p>Claude Code 上週發表 <a href="https://claude.com/blog/introducing-dynamic-workflows-in-claude-code">dynamic workflows</a> (<a href="https://code.claude.com/docs/en/workflows">官方文件</a>)，大家第一反應大多是「終於能一次跑幾百個 sub-agent 了」。這當然很猛，但小編覺得更值得講的，是它背後那條技術脈絡: 從 2024 年初的 Code Act 一路長出來的。</p>

<p>把這條線拉開來看，dynamic workflows 不是憑空冒出來的新玩意，而是「用程式碼當 agent 的行動」這個老想法，走到成熟、產品化的一步。技術上小編覺得設計得非常漂亮，這篇就來把這條路從頭走一遍。</p>

<h2 id="1-code-act-的核心洞見-用程式碼當行動空間">1. Code Act 的核心洞見: 用程式碼當行動空間</h2>

<p>故事從 <a href="https://arxiv.org/abs/2402.01030">“Executable Code Actions Elicit Better LLM Agents”</a> (2024/2) 這篇講起。傳統 agent 的做法是 JSON function calling: 模型吐一個 JSON 物件，描述要呼叫哪個工具、帶哪些參數，再交給後端的處理常式執行，把結果塞回去。</p>

<p>CodeAct 提出的轉向是: 不要用預先定義好的 JSON 格式當行動空間，而是直接讓 LLM 寫一段「可執行的 Python」當作它的行動，把規劃跟工具呼叫合併進同一份程式碼裡一起跑。</p>

<p>為什麼程式碼會比 JSON 好? 兩個關鍵理由:</p>

<p>🔹 <strong>模型本來就很會寫程式碼。</strong> LLM 在訓練時看過幾百萬個開源專案的真實程式碼，但工具呼叫用的那些特殊 token，是靠合成資料硬訓出來的，模型在真實世界裡根本沒見過。</p>

<p>🔹 <strong>程式碼天生支援組合。</strong> 迴圈、條件判斷、變數傳遞、把好幾個工具的輸出串起來，這些在 JSON schema 裡很難表達，在 Python 裡卻幾乎不費力。</p>

<p>論文 <a href="https://arxiv.org/html/2402.01030v4">Figure 1</a> 那個例子舉得很漂亮。任務是在美國、日本、德國、印度裡，找出買「CodeAct」這支手機最划算的國家。每個國家都得查匯率、稅率、當地售價、運費，再算出最終美元價格。用到的工具有 <code class="language-plaintext highlighter-rouge">lookup_rate</code>、<code class="language-plaintext highlighter-rouge">lookup_phone_price</code>、<code class="language-plaintext highlighter-rouge">convert_and_tax</code>、<code class="language-plaintext highlighter-rouge">lookup_shipping_cost</code>、<code class="language-plaintext highlighter-rouge">estimate_final_price</code>。</p>

<p>兩種做法的差異:</p>

<div style="display:flex;flex-wrap:wrap;gap:1rem;margin:1.2rem 0 1.6rem;">
<div style="flex:1 1 300px;min-width:0;">
<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;">
<div style="padding:.5rem .75rem;font-weight:600;background:#ffebe9;border-bottom:1px solid var(--border);font-size:.9rem;">JSON Function Calling</div>
<pre style="margin:0;padding:.6rem .75rem;font-size:.78rem;line-height:1.55;overflow-x:auto;background:var(--bg);"><code>→ {"tool":"lookup_rate","country":"Germany"}
← exchange_rate=1.1, tax_rate=0.19
→ {"tool":"lookup_phone_price","country":"Germany"}
← 700
→ {"tool":"convert_and_tax","price":700,...}
← 833
→ {"tool":"lookup_shipping_cost","country":"Germany"}
← ...
→ {"tool":"estimate_final_price",...}
← ...</code></pre>
<div style="padding:.45rem .75rem;font-size:.8rem;color:var(--text-secondary);background:var(--surface);border-top:1px solid var(--border);">⬆ 以上才跑完「德國」一國，四國要重複四遍。<br />每個 → ← 都是一次完整的模型來回，<strong style="color:#cf222e;">10+ 次起跳</strong></div>
</div>
</div>
<div style="flex:1 1 300px;min-width:0;">
<div style="border:1px solid var(--accent);border-radius:8px;overflow:hidden;">
<div style="padding:.5rem .75rem;font-weight:600;background:var(--tag-bg);border-bottom:1px solid var(--accent);font-size:.9rem;color:var(--accent);">CodeAct</div>
<pre style="margin:0;padding:.6rem .75rem;font-size:.78rem;line-height:1.55;overflow-x:auto;background:var(--bg);"><code>countries = ["USA","Japan","Germany","India"]
final_prices = {}
for country in countries:
    rate, tax = lookup_rate(country)
    price = lookup_phone_price("CodeAct", country)
    converted = convert_and_tax(price, rate, tax)
    shipping = lookup_shipping_cost(country)
    final_prices[country] = estimate_final_price(
        converted, shipping)
cheapest = min(final_prices, key=final_prices.get)</code></pre>
<div style="padding:.45rem .75rem;font-size:.8rem;color:var(--text-secondary);background:var(--tag-bg);border-top:1px solid var(--accent);">迴圈跑完四國，工具輸出直接傳給下一個工具。<br /><strong style="color:#1a7f37;">1 段程式碼、1 個 action</strong></div>
</div>
</div>
</div>

<p>左邊每呼叫一個工具，結果都得塞回模型，模型再決定下一步，一路來回。右邊一個 for 迴圈跑完四國，<code class="language-plaintext highlighter-rouge">lookup_rate</code> 的輸出直接傳給 <code class="language-plaintext highlighter-rouge">convert_and_tax</code> (資料流)，最後用 Python 內建的 <code class="language-plaintext highlighter-rouge">min()</code> 挑出最便宜的。模型只需要產生這一段程式碼、看一次最終答案。</p>

<p>CodeAct 的一個具體實作是 Hugging Face 的 <a href="https://github.com/huggingface/smolagents">smolagents</a>，裡面的 <code class="language-plaintext highlighter-rouge">CodeAgent</code> 就是讓模型寫 Python 來呼叫工具，而不是走 JSON function calling。</p>

<p>這裡有個容易混淆的點: CodeAct 跟 <a href="https://developers.openai.com/api/docs/guides/tools-code-interpreter">Code Interpreter</a> 不一樣。Code Interpreter 只是把「跑程式碼」當成眾多工具裡的「一個工具」來用，程式碼本身碰不到 agent 的其他工具。</p>

<p>CodeAct 則是把整個決策過程，連同函式呼叫，都寫進同一份程式碼裡一起執行。少了中間來回，效率自然好。</p>

<h2 id="2-同一招用到-mcp-上-cloudflare-自家的-code-mode">2. 同一招用到 MCP 上: Cloudflare 自家的 Code Mode</h2>

<p>時間快轉到 MCP 生態爆炸之後，出現一個很現實的問題: 工具太多了。每個操作都註冊成一個工具，幾千個工具定義直接塞爆上下文。</p>

<p>Cloudflare 的 <a href="https://blog.cloudflare.com/code-mode/">Code Mode</a> 參考了 CodeAct 的思路: 與其把每個操作描述成獨立的工具，不如把 MCP 工具轉成一份有型別的 TypeScript SDK，叫模型寫程式碼去呼叫。他們<a href="https://blog.cloudflare.com/code-mode-mcp/">新的 Cloudflare MCP server</a> 涵蓋整個 Cloudflare API (數千個端點)，只定義兩個 agent 工具: <code class="language-plaintext highlighter-rouge">search()</code> 跟 <code class="language-plaintext highlighter-rouge">execute()</code>。</p>

<p>這樣大約只吃 1000 個 token，同樣的東西若用傳統「每個端點一個工具」的做法，要 117 萬 token，直接超過大多數模型的 context window。<code class="language-plaintext highlighter-rouge">search()</code> 讓模型去查 OpenAPI 規格、把幾千個端點收斂到需要的那幾個，<code class="language-plaintext highlighter-rouge">execute()</code> 在沙箱裡跑實際呼叫。</p>

<p>這裡有個概念叫「composition tax」(組合稅): 每一次工具呼叫的結果，都得先塞回模型的神經網路，再被原封不動抄到下一個呼叫的輸入，白白浪費 token 跟延遲，還多出一次推理。動作越多，這個稅越重。寫成程式碼就能在執行環境裡直接串接，跳過這道稅。</p>

<h2 id="3-程式碼可呼叫-agent-tools-ptc">3. 程式碼可呼叫 agent tools: PTC</h2>

<p>接著是 Claude API 這邊的 <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling">Programmatic Tool Calling (PTC)</a>，它建在 <code class="language-plaintext highlighter-rouge">code_execution</code> 工具之上 (這就是 Claude 的 Code Interpreter)。作法很單純: 你照常定義自訂工具，只要在工具定義裡加一個 <code class="language-plaintext highlighter-rouge">allowed_callers: ["code_execution_20260120"]</code>，Claude 就不會一個一個直接呼叫它們，而是寫一段 Python，把這些工具當成函式，在程式碼執行容器裡組合、執行。</p>

<p>一句話抓住定位: PTC 就是「<strong>一段在容器裡執行的程式碼，而且這段程式碼能呼叫你原本要給 agent 的那些工具</strong>」。對照前面: Code Interpreter 的程式碼只能在沙箱裡自己算，碰不到 agent 的工具; CodeAct 在研究層面做到了讓程式碼呼叫 agent 工具。PTC 的升級是把這件事變成正式上線的 API，附帶的好處是: <strong>工具的執行留在你那邊，工具結果不進 Claude 的上下文</strong>。</p>

<p>機制拆開來看是這樣:</p>

<ol>
  <li>Claude 在 Anthropic 的容器裡寫一段程式碼，裡面可能有迴圈、條件判斷、好幾個工具呼叫。</li>
  <li>程式碼跑到 <code class="language-plaintext highlighter-rouge">result = await query_db(sql)</code> 時，<strong>容器暫停</strong>，API 把這個呼叫當成一個 <code class="language-plaintext highlighter-rouge">tool_use</code> 事件回傳給「你」。</li>
  <li><strong>工具還是跑在你那邊</strong>: 你的伺服器執行它、把結果回傳，Anthropic 的容器只負責跑那段編排程式碼。</li>
  <li>結果回到「正在執行的那段程式碼」繼續往下，這些中間結果<strong>完全不進 Claude 的 context window</strong> (也不計入 token)。</li>
  <li>整段程式碼跑完，Claude 只收到最後印出來的 stdout。</li>
</ol>

<blockquote>
  <p>編按: 這正好補上 composition tax 的另一半。Code Mode 解決了「工具定義」塞爆上下文，PTC 解決的是「工具結果」塞爆上下文。例如一次搜尋回傳 50 筆原始結果，程式碼可以在容器裡直接解析、過濾、交叉比對，只留下相關的幾筆，而不是把 50 筆全倒進 Claude 腦袋裡讓它自己讀。</p>
</blockquote>

<p>所以 PTC 做到的是: <strong>Claude 用程式碼組合多個工具呼叫 (迴圈、條件、串接輸出)，但工具還是跑在你那邊，你可以檢查、拒絕、記 log、排進人工審核佇列。</strong></p>

<p>但官方也講得很白，PTC 不是萬靈丹。<strong>嚴格循序、每一步都要 Claude 看完上一步才能決定下一步的工作，PTC 幫不上忙</strong> (那種情況本來就省不掉來回)，一次只呼叫一兩個工具的場景甚至會小貴一點。它真正發威的是「大量平行分流」或「結果很大、可以先在程式碼裡過濾」的任務。</p>

<p>(另外有個常見誤會: MCP connector 提供的工具，目前不能被 PTC 程式化呼叫。)</p>

<h2 id="4-開源實作-langchain-deep-agents-的-interpreter-與-interpreter-skills">4. 開源實作: LangChain Deep Agents 的 Interpreter 與 Interpreter Skills</h2>

<p>LangChain 的 Deep Agents 做的 <a href="https://www.langchain.com/blog/give-your-agents-an-interpreter">interpreter</a>，跟剛剛的 PTC 骨子裡是同一套做法，只是搬到了不同的層。PTC 是 Anthropic 在 API 供應商這一層做的 (Anthropic 管容器，工具在你那邊執行); Deep Agents Interpreter 則是 LangChain 在 harness 層做的中介層 (middleware)，跟模型無關，連開源模型都能用。LangChain 自己也說 PTC、Code Mode、interpreter、RLM 殊途同歸。</p>

<p>具體來說，它給 agent 一個內嵌的小型執行環境，agent 可以在裡面寫程式碼、存變數、定義輔助函式、跨呼叫保留狀態，像有了一個 Python 或 Node REPL。實作上它是掛一個 <code class="language-plaintext highlighter-rouge">eval</code> 工具，但別被「工具」兩個字誤導: 它背後是一個有狀態、會跨呼叫存活的執行環境，不是那種呼叫一次就結束的普通工具。</p>

<p>小編初看時覺得這跟 sandbox 功能有什麼差別? 其實差在兩個地方:</p>

<p>🔸 <strong>預設能力的方向相反。</strong> 沙箱是「先給一台電腦，再往下收」: agent 一開始就拿到完整的作業系統、檔案系統、網路、shell，你再去限制。interpreter 剛好倒過來，「先什麼都沒有，要什麼再明確給」: 預設只有一個語言執行環境，沒有檔案系統、沒有網路、沒有 shell，連讀檔、抓網頁、開子代理人(spawn sub-agent) 都得一個一個透過 allowlist 橋接進來。</p>

<p>🔸 <strong>隔離在不同的層。</strong> 這也回答一個常見疑問: 「interpreter 不也是在沙箱裡跑程式碼嗎?」其實不一定。interpreter 底層是像 QuickJS 這種內嵌的小引擎，跟 harness 跑在同一個行程裡，它的「隔離」是語言層的: 執行環境預設沒綁任何主機 API，所以裡面的程式碼根本沒有東西可以濫用。真正的沙箱 (gVisor、microVM 那種) 則是作業系統或硬體層的隔離，目的是擋住 agent 自己生成、可能亂來的程式碼「逃逸」。一句話: 能力控管 (capability scoping) 防的是「能碰到什麼」，VM 隔離防的是「會不會逃出去」，是兩回事。跑不可信的程式碼時，你還是可以把整個 interpreter 再包進一個沙箱，那是多加的一層防禦，不是 interpreter 自帶的機制。</p>

<p>PTC 跟 Deep Agents Interpreter 共同做到的關鍵動作是: 一旦把 Task 或 agent 工具也橋接進來，程式碼就能開子代理人。程式碼從「呼叫工具」升級成「呼叫 agent」，正式參與了 agent loop。</p>

<p>LangChain 後來進一步做了 <a href="https://www.langchain.com/blog/interpreter-skills">interpreter skills</a>: 開發者事先把一段「已知有效的固定流程」寫成 TypeScript 模組，註冊成一個 skill。模型在對話中判斷「現在該用哪個 skill、傳什麼輸入」，但 skill 內部的步驟是寫死的程式碼，不是模型即時決定的。</p>

<p>拿他們的 GitHub repo triage 範例來看，一個 interpreter skill 由 <code class="language-plaintext highlighter-rouge">SKILL.md</code> (告訴模型什麼時候該用) 和 <code class="language-plaintext highlighter-rouge">index.ts</code> (實際的流程程式碼) 組成。當使用者說「幫我整理這個 repo 的 issue」，模型判斷該用這個 skill，在 interpreter 裡呼叫它:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// skills/github-triage/SKILL.md 告訴模型: "Use this skill when a user asks for repository triage."</span>
<span class="c1">// skills/github-triage/index.ts 匯出 triage() 函式，內部流程是寫死的:</span>
<span class="c1">//   抓所有 open items → 開子代理人做摘要 → 逐一分群歸類</span>

<span class="kd">const</span> <span class="p">{</span> <span class="nx">triage</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">import</span><span class="p">(</span><span class="dl">"</span><span class="s2">@/skills/github-triage</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">triage</span><span class="p">(</span><span class="dl">"</span><span class="s2">langchain-ai/deepagents</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">issues</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">prs</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">discussions</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">result</span><span class="p">.</span><span class="nf">toMarkdown</span><span class="p">();</span>
</code></pre></div></div>

<p>模型決定的是「要不要用、傳什麼參數、結果怎麼處理」; 但 <code class="language-plaintext highlighter-rouge">triage()</code> 內部怎麼抓資料、怎麼開子代理人、怎麼分群，全是寫死在 <code class="language-plaintext highlighter-rouge">index.ts</code> 裡的確定性程式碼。</p>

<p>這跟 Claude Code dynamic workflows 已經非常接近了，差別是 interpreter skills 由開發者事先寫好，而 dynamic workflows 是模型在接到任務時才即時生成那份編排腳本。</p>

<h2 id="這套設計背後的理論支點-rlm">這套設計背後的理論支點: RLM</h2>

<p>dynamic workflows 不只是工程上的巧思，它底下有一條清楚的研究脈絡撐著，叫 RLM (Recursive Language Models)。</p>

<p>這是 MIT 的 Alex Zhang、Tim Kraska、Omar Khattab 提出的框架 (2025 年 10 月先以部落格形式提出，年底發表<a href="https://arxiv.org/abs/2512.24601">論文</a>)。核心想法是: 把整段 prompt 當成一個放在 REPL 裡的「外部物件」。主模型不把上下文一口氣吞進腦袋，而是寫程式碼去窺看它、拆解它，對其中的片段遞迴呼叫自己或子模型，中間結果留在 REPL 的變數裡，只有精簡過的結果才回到主模型。這樣就繞過了固定 context window 的天花板。</p>

<p>關鍵在於 RLM 長期主張、而 coding agent 一直缺的那個能力: <strong>以程式化的方式呼叫 sub-agent，把它們的輸出在程式碼裡傳來傳去，而不經過主模型的上下文。</strong> 這正是 dynamic workflows 在做的事。</p>

<p>也因此，RLM 作者群直接認領了這個發表: <a href="https://x.com/lateinteraction/status/2060078643133763839">Omar Khattab 說</a>「Claude Code 終於是一個 RLM 了」; <a href="https://x.com/a1zhang/status/2060071701879066626">Alex Zhang 則認為</a> Opus 4.8 加上 dynamic workflows，大概是第一個被認真訓練成 RLM 的前沿模型實例。</p>

<p>Dynamic workflows 的設計跟 RLM 的主張高度吻合: 模型寫的那段 JS 裡，<code class="language-plaintext highlighter-rouge">agent()</code> 就是在遞迴開子代理人，而編排這層是決定性的。</p>

<h2 id="5-claude-code-dynamic-workflows">5. Claude Code Dynamic Workflows</h2>

<p>終於來講到本篇的主角 Claude Code dynamic workflows 了，一樣是讓模型寫程式碼去編排 sub-agent。模型即時寫出程式碼這件事，跟 PTC、Deep Agents Interpreter 並沒有不同。真正不一樣的，是把這套編排<strong>磨硬、做成產品</strong>: 編排腳本可以中斷後原地 resume，而且是決定性的 (第 8 節會講); 規模一次拉到上千個 sub-agent; 在背景獨立跑完才把結果交回對話; 還收斂成六種可互相組合的模式、能存檔重用。前面那幾種都沒有這層「可重播、可規模化」的保證。</p>

<p>實際操作時，你在 prompt 裡帶一個 workflow (或 <code class="language-plaintext highlighter-rouge">ultracode</code>)，Claude 就會即時寫出一份 JavaScript 編排腳本，用 <code class="language-plaintext highlighter-rouge">agent()</code>、<code class="language-plaintext highlighter-rouge">parallel()</code>、<code class="language-plaintext highlighter-rouge">pipeline()</code>、<code class="language-plaintext highlighter-rouge">phase()</code> 這些函式 (<a href="https://code.claude.com/docs/en/workflows">官方文件</a>)，平行啟動一大批 sub-agent (同時最多 16 個，單次最多 1000 個)，跑完再把結果交回來。</p>

<p>借用 <a href="https://x.com/voxyz_ai/status/2061782441606451381">Voxyz 那篇文章</a>的講法: <strong>計畫從對話的上下文搬進了可執行腳本。</strong> 以前用 Claude Code 跑大任務，所有步驟都擠在同一個對話上下文裡: 做完一步、等結果、決定下一步、再等結果。任務越複雜，上下文本身反而變成瓶頸，卡住的從來不是「誰來做」，而是「誰記得整個計畫」。dynamic workflows 把計畫從對話裡抽出來: 迴圈、分支、中間狀態、審查鏈、重試，全都在腳本裡跑，主對話只收到最後的摘要。</p>

<p>官方那篇 <a href="https://x.com/trq212/status/2061907337154367865">“A harness for every task”</a> 非常推薦一讀，把「為什麼需要這個」講得很清楚。當 Claude 在單一 context window 裡又要規劃又要執行，跑久了會出現三種失敗模式:</p>

<p>1️⃣ <strong>Agentic laziness (偷懶)</strong>: 複雜的多步任務做到一半就宣告完成，例如 50 項的安全檢查只做了 20 項就說好了。</p>

<p>2️⃣ <strong>Self-preferential bias (偏袒自己)</strong>: Claude 傾向偏好自己產出的結果，叫它對照評分準則自我審查時，這個偏差特別明顯。</p>

<p>3️⃣ <strong>Goal drift (目標漂移)</strong>: 多輪之後，尤其每次 compaction 都是有損壓縮，「不要做 X」這種邊界約束會慢慢被弄丟。</p>

<p>把任務拆給各自擁有獨立、乾淨上下文的多個 Claude，從結構上就避開這三個坑。</p>

<blockquote>
  <p>編按: 這三個失敗模式雖然是 Anthropic 自己歸納的，但跟學界的觀察高度吻合。Berkeley 那篇 <a href="https://arxiv.org/abs/2503.13657">《Why Do Multi-Agent LLM Systems Fail?》</a> (MAST) 分析了 1600 多份多 agent 系統的失敗軌跡，歸納出 14 種失敗模式，其中很大一塊正是「驗證不足」跟「提早收工」: 沒有獨立的把關者、任務沒做完就被宣告完成。dynamic workflows 用獨立上下文的 agent 配上對抗式驗證，剛好是針對這類失敗的結構性解法。</p>
</blockquote>

<p>最有名的案例是 Bun 從 Zig 改寫成 Rust: 約 75 萬行 Rust、99.8% 既有測試通過、從第一個 commit 到 merge 只花 11 天，靠的就是把任務切成「map → generate → review → verify → fix loop → cleanup」的流水線。</p>

<p>從 Code Interpreter 到 Dynamic Workflows，雖然都是「讓 LLM 輸出程式碼來執行」，但真正分出差異的是: <strong>那段生成的程式碼，被准許呼叫什麼、又被限制成什麼。</strong></p>

<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;margin:1.6rem 0;font-size:.86rem;">
<div style="display:flex;flex-wrap:wrap;align-items:center;background:var(--surface);padding:.55rem .8rem;font-weight:600;border-bottom:1px solid var(--border);">
<div style="flex:2 1 120px;">階段</div>
<div style="flex:3 1 160px;">生成的程式碼呼叫什麼</div>
<div style="flex:1 1 90px;">能開子代理人?</div>
<div style="flex:3 1 190px;">限制 → 換到的穩定性</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:2 1 120px;"><strong>Code Interpreter</strong></div>
<div style="flex:3 1 160px;">只有 Python 套件、檔案 (碰不到 agent 工具)</div>
<div style="flex:1 1 90px;color:var(--text-secondary);">✗</div>
<div style="flex:3 1 190px;">運算不設限，但站在 agent loop 外</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:2 1 120px;"><strong>CodeAct</strong></div>
<div style="flex:3 1 160px;">agent 的工具 (固定函式)</div>
<div style="flex:1 1 90px;color:var(--text-secondary);">✗</div>
<div style="flex:3 1 190px;">幾乎不設限: 表達力最大，但最難控管</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:2 1 120px;"><strong>Code Mode</strong></div>
<div style="flex:3 1 160px;">包好的一整套 SDK (= 那套 API)</div>
<div style="flex:1 1 90px;color:var(--text-secondary);">✗</div>
<div style="flex:3 1 190px;">框在 SDK 內: 用熟悉的程式碼駕馭龐大 API、省上下文</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:2 1 120px;"><strong>PTC・Deep Agents Interpreter</strong></div>
<div style="flex:3 1 160px;">agent tools，甚至開子代理人</div>
<div style="flex:1 1 90px;color:var(--accent);font-weight:600;">✓</div>
<div style="flex:3 1 190px;">靠 allowlist 開放: 結果留在程式碼、保住控制面</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;background:var(--tag-bg);">
<div style="flex:2 1 120px;"><strong style="color:var(--accent);">Dynamic Workflows</strong></div>
<div style="flex:3 1 160px;">只有編排函式 agent()/parallel()/pipeline()</div>
<div style="flex:1 1 90px;color:var(--accent);font-weight:600;">✓</div>
<div style="flex:3 1 190px;">只能編排、禁時間亂數: 換到 resume、可重播、決定性</div>
</div>
</div>

<p style="text-align:right;font-size:.8rem;color:var(--text-secondary);margin-top:-.8rem;">✏️ 小編製圖</p>

<p>Code Interpreter 什麼都能寫，但程式碼碰不到 agent 的工具，等於站在 agent loop 外面。CodeAct 的關鍵一步，是讓程式碼呼叫 agent 的工具，把程式碼拉進 loop 裡。之後每個階段的設計取捨不同: Code Mode 框在一套 SDK 裡; PTC 跟 Deep Agents Interpreter 用 allowlist 決定能碰哪些工具跟 agent; Dynamic Workflows 則把程式碼限制在「編排」這一件事，不能直接碰檔案、shell、網路 (那些都丟給 sub-agent)，連時間跟亂數都被禁掉，換來的是 resume 跟決定性。</p>

<p>表格裡另一個值得注意的差異: <strong>程式碼呼叫的是「死的函式」，還是「會自己推理的 sub-agent」。</strong> 原始 CodeAct 論文裡，程式碼呼叫的工具都是 <code class="language-plaintext highlighter-rouge">lookup_rate</code> 這種固定函式: 你可以用迴圈、條件去串它們的輸出，但函式本身不會思考。</p>

<p>不過嚴格講，CodeAct 這條線本身就能開子代理人，只要塞進去的那個「工具」其實是一個 agent 就行。Hugging Face 的 <a href="https://github.com/huggingface/smolagents">smolagents</a> 早就做到了: 你給一個 agent 設好 <code class="language-plaintext highlighter-rouge">name</code> 跟 <code class="language-plaintext highlighter-rouge">description</code>，當成 <code class="language-plaintext highlighter-rouge">managed_agents</code> 交給一個 manager <code class="language-plaintext highlighter-rouge">CodeAgent</code>，manager 生成的 Python 就能把它當函式呼叫。<a href="https://huggingface.co/docs/smolagents/en/examples/multiagents">官方範例</a>裡，一個 manager 指揮底下的 <code class="language-plaintext highlighter-rouge">web_search_agent</code> 查資料，自己負責規劃跟計算。所以表格裡 CodeAct 那格的 ✗，與其說是「技術上做不到」，不如說是「原論文沒往這個方向想，生態也還沒把 sub-agent 編排當成主軸」。</p>

<p>PTC 跟 Deep Agents Interpreter 帶出的重點是「程式碼能呼叫 agent tools」，雖然 tool 裡面也可以包一個 sub-agent，但這不是它們強調的用法。程式碼可以用迴圈、條件去組合多個工具呼叫，中間結果留在程式碼裡不必繞回主模型的上下文，後一個工具的輸入還可以拿前一個的結果在程式碼裡算出來 (<code class="language-plaintext highlighter-rouge">a = await tool(...); b = await tool(transform(a))</code>)，不必一開始就把所有參數寫死。</p>

<p>真正把 sub-agent 編排當成核心的，是 RLM 的研究和 Dynamic Workflows。RLM 一路主張的就是「以程式化的方式呼叫 sub-agent，把輸出在程式碼裡傳遞，而不經過主模型的上下文」。到了 Dynamic Workflows，開子代理人變成程式碼唯一在做的事，還補上了 resume、可重播、決定性這些硬保證。</p>

<p>表格裡那條 ✗✗✗✓✓ 的分界，標的不是「技術上能不能開子代理人」(CodeAct 透過 smolagents 也做得到)，而是「有沒有把 sub-agent 編排當成一等公民，再用產品級保證撐住」。</p>

<p>PTC / Deep Agents Interpreter 跟 Dynamic Workflows 都能 spawn、也都能分階段，差別不在「能不能」，而在編排層被磨得多硬。Dynamic Workflows 多了 <code class="language-plaintext highlighter-rouge">parallel</code>/<code class="language-plaintext highlighter-rouge">pipeline</code>/<code class="language-plaintext highlighter-rouge">phase</code> 這些原語，可中斷後 resume，還有禁時間亂數換來的決定性; PTC / Deep Agents Interpreter 則更通用、也更鬆，少了這些硬保證。</p>

<p>Dynamic Workflows 把程式碼的角色限制在編排，再用六種固定模式把編排本身也標準化。限制換來的是能 resume、能重播、結果可預期。</p>

<h3 id="六種可以互相組合的編排模式">六種可以互相組合的編排模式</h3>

<p><a href="https://x.com/trq212/status/2061907337154367865">“A harness for every task”</a> 這篇把常見的編排手法整理成六種，實際用時常常會疊在一起:</p>

<p>1️⃣ <strong>分類並執行 (classify-and-act)</strong>: 先用一個分類 agent 判斷任務屬於哪一型，再依結果路由到不同的 agent 或行為。也可以反過來放在最後，用分類器決定要輸出什麼。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 430 150" style="width:100%;max-width:440px;height:auto;"><defs><marker id="a1" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="6" y="58" width="64" height="34" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="38" y="80" text-anchor="middle" font-size="13" style="fill:var(--text)">輸入</text><line x1="72" y1="75" x2="116" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a1)" /><rect x="120" y="56" width="92" height="38" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="166" y="80" text-anchor="middle" font-size="13" style="fill:var(--text)">分類器</text><rect x="312" y="14" width="112" height="30" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="368" y="34" text-anchor="middle" font-size="13" style="fill:var(--text)">行為 A</text><rect x="312" y="60" width="112" height="30" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="368" y="80" text-anchor="middle" font-size="13" style="fill:var(--text)">行為 B</text><rect x="312" y="106" width="112" height="30" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="368" y="126" text-anchor="middle" font-size="13" style="fill:var(--text)">行為 C</text><line x1="212" y1="72" x2="308" y2="30" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a1)" /><line x1="212" y1="75" x2="308" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a1)" /><line x1="212" y1="78" x2="308" y2="120" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a1)" /></svg>
</div>

<p>2️⃣ <strong>分流並整合 (fan-out-and-synthesize)</strong>: 把任務拆成很多小步，每步開一個 agent，最後再把結果整合起來。適合步驟很多、或每步都需要自己乾淨上下文以免互相干擾的情況。整合那一步是個同步點 (barrier): 它會等所有分流的 agent 都跑完，再把它們的結構化輸出併成一份結果。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 450 150" style="width:100%;max-width:460px;height:auto;"><defs><marker id="a2" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="6" y="58" width="60" height="34" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="36" y="80" text-anchor="middle" font-size="13" style="fill:var(--text)">任務</text><rect x="110" y="12" width="84" height="28" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="152" y="31" text-anchor="middle" font-size="12" style="fill:var(--text)">步驟 1</text><rect x="110" y="61" width="84" height="28" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="152" y="80" text-anchor="middle" font-size="12" style="fill:var(--text)">步驟 2</text><rect x="110" y="110" width="84" height="28" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="152" y="129" text-anchor="middle" font-size="12" style="fill:var(--text)">步驟 3</text><line x1="66" y1="72" x2="106" y2="28" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><line x1="66" y1="75" x2="106" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><line x1="66" y1="78" x2="106" y2="122" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><rect x="250" y="48" width="80" height="54" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="290" y="72" text-anchor="middle" font-size="13" style="fill:var(--text)">整合</text><text x="290" y="89" text-anchor="middle" font-size="10" style="fill:var(--text-secondary)">同步點 (barrier)</text><line x1="194" y1="26" x2="248" y2="68" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><line x1="194" y1="75" x2="248" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><line x1="194" y1="124" x2="248" y2="82" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /><rect x="376" y="58" width="64" height="34" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="408" y="80" text-anchor="middle" font-size="13" style="fill:var(--text)">結果</text><line x1="330" y1="75" x2="372" y2="75" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a2)" /></svg>
</div>

<p>3️⃣ <strong>對抗式驗證 (adversarial verification)</strong>: 每開一個 agent 產出結果，就另外開一個 agent，專門拿評分準則或標準去「反駁」它的輸出。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 430 140" style="width:100%;max-width:440px;height:auto;"><defs><marker id="a3" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="6" y="68" width="104" height="34" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="58" y="90" text-anchor="middle" font-size="13" style="fill:var(--text)">產出 agent</text><line x1="110" y1="85" x2="148" y2="85" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a3)" /><rect x="152" y="68" width="76" height="34" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="190" y="90" text-anchor="middle" font-size="13" style="fill:var(--text)">結果</text><rect x="138" y="14" width="104" height="32" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="190" y="35" text-anchor="middle" font-size="13" style="fill:var(--text)">反駁 agent</text><line x1="190" y1="46" x2="190" y2="66" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a3)" /><text x="200" y="60" font-size="11" style="fill:var(--text-secondary)">挑戰</text><line x1="228" y1="85" x2="292" y2="85" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a3)" /><text x="300" y="82" font-size="12" style="fill:var(--text-secondary)">✓ 通過</text><text x="300" y="100" font-size="12" style="fill:var(--text-secondary)">✗ 淘汰</text></svg>
</div>

<p>4️⃣ <strong>生成並篩選 (generate-and-filter)</strong>: 針對一個主題先生成一堆點子，再用評分準則或驗證去篩，去掉重複的，只留下品質最高、通過檢驗的那幾個。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 460 130" style="width:100%;max-width:460px;height:auto;"><defs><marker id="a4" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="6" y="48" width="68" height="34" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="40" y="70" text-anchor="middle" font-size="13" style="fill:var(--text)">生成</text><line x1="74" y1="65" x2="98" y2="65" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a4)" /><circle cx="112" cy="32" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><circle cx="142" cy="55" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><circle cx="118" cy="86" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><circle cx="168" cy="36" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><circle cx="156" cy="82" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><circle cx="186" cy="62" r="7" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><line x1="204" y1="65" x2="224" y2="65" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a4)" /><rect x="226" y="46" width="92" height="38" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="272" y="66" text-anchor="middle" font-size="13" style="fill:var(--text)">篩選</text><text x="272" y="80" text-anchor="middle" font-size="9" style="fill:var(--text-secondary)">評分準則 / 去重</text><line x1="318" y1="65" x2="342" y2="65" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a4)" /><circle cx="360" cy="52" r="7" style="fill:var(--accent)" /><circle cx="378" cy="74" r="7" style="fill:var(--accent)" /><text x="398" y="69" font-size="12" style="fill:var(--text-secondary)">只留精華</text></svg>
</div>

<p>5️⃣ <strong>錦標賽 (tournament)</strong>: 不是分工，而是讓 agent 互相競爭。開 N 個 agent 用不同方法各做同一件事，再用一個評審 agent 兩兩比較、淘汰，直到選出贏家。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 430 160" style="width:100%;max-width:440px;height:auto;"><defs><marker id="a5" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="6" y="8" width="84" height="26" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="48" y="26" text-anchor="middle" font-size="12" style="fill:var(--text)">方案 A</text><rect x="6" y="44" width="84" height="26" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="48" y="62" text-anchor="middle" font-size="12" style="fill:var(--text)">方案 B</text><rect x="6" y="92" width="84" height="26" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="48" y="110" text-anchor="middle" font-size="12" style="fill:var(--text)">方案 C</text><rect x="6" y="128" width="84" height="26" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="48" y="146" text-anchor="middle" font-size="12" style="fill:var(--text)">方案 D</text><rect x="170" y="14" width="80" height="28" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="210" y="33" text-anchor="middle" font-size="12" style="fill:var(--text)">勝者 1</text><rect x="170" y="120" width="80" height="28" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="210" y="139" text-anchor="middle" font-size="12" style="fill:var(--text)">勝者 2</text><line x1="90" y1="21" x2="166" y2="27" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /><line x1="90" y1="57" x2="166" y2="31" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /><line x1="90" y1="105" x2="166" y2="131" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /><line x1="90" y1="141" x2="166" y2="135" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /><rect x="324" y="67" width="92" height="32" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:2" /><text x="370" y="88" text-anchor="middle" font-size="13" style="fill:var(--text)">冠軍</text><line x1="250" y1="28" x2="320" y2="76" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /><line x1="250" y1="134" x2="320" y2="90" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a5)" /></svg>
</div>

<p>6️⃣ <strong>直到完成為止持續迴圈 (loop until done)</strong>: 對「工作量未知」的任務，不要設固定回合數，而是一直開 agent 直到觸發停止條件 (沒有新發現、或 log 裡不再有錯誤) 為止。</p>

<div style="margin:.5rem 0 1.2rem;">
<svg viewBox="0 0 440 150" style="width:100%;max-width:450px;height:auto;"><defs><marker id="a6" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" style="fill:var(--text-secondary)" /></marker></defs><rect x="34" y="56" width="120" height="34" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="94" y="78" text-anchor="middle" font-size="13" style="fill:var(--text)">開一批 agent</text><line x1="154" y1="73" x2="214" y2="73" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a6)" /><rect x="216" y="52" width="150" height="42" rx="6" style="fill:var(--surface);stroke:var(--border);stroke-width:1.5" /><text x="291" y="78" text-anchor="middle" font-size="13" style="fill:var(--text)">達成停止條件?</text><path d="M250,52 C250,20 94,20 94,54" fill="none" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a6)" /><text x="172" y="16" text-anchor="middle" font-size="11" style="fill:var(--text-secondary)">否: 再開一輪</text><line x1="291" y1="94" x2="291" y2="120" style="stroke:var(--text-secondary);stroke-width:1.5" marker-end="url(#a6)" /><text x="300" y="112" font-size="11" style="fill:var(--text-secondary)">是</text><rect x="246" y="120" width="90" height="28" rx="6" style="fill:var(--tag-bg);stroke:var(--accent);stroke-width:1.5" /><text x="291" y="139" text-anchor="middle" font-size="13" style="fill:var(--text)">完成</text></svg>
</div>

<h2 id="6-更多精彩用法-它不只拿來寫程式">6. 更多精彩用法: 它不只拿來寫程式</h2>

<p><a href="https://x.com/trq212/status/2061907337154367865">官方那篇文章</a>裡，小編覺得最有啟發的反而是作者一句話: workflows 用在「非寫程式」的任務上，往往更有威力。挑幾個他給的實際 prompt 範例 (已翻成中文)，順手標出背後用到的模式:</p>

<p>🔹 <strong>抓 1/50 機率的 flaky test</strong>: 「這個測試大概每 50 次會失敗 1 次。開一個 workflow 重現它，提出幾個假設、在各自的 worktree 裡對抗式地驗證，<code class="language-plaintext highlighter-rouge">/goal</code> 設定: 不到有一個假設成立不准停。」(loop-until-done + 對抗式驗證 + 根本原因調查)</p>

<p>🔹 <strong>從自己的歷史紀錄煉出規則</strong>: 「用 workflow 翻過我最近 50 個 session，挖出我一再重複糾正的地方，把常出現的那幾類變成 CLAUDE.md 規則。」(分流並整合 + 記憶與規則遵循)</p>

<p>🔹 <strong>挖出 Slack 裡沒人追的問題</strong>: 「用 workflow 翻過去半年的 #incidents 頻道，找出反覆出現、卻從來沒人開單追蹤的根本原因。」(大規模分流)</p>

<p>🔹 <strong>多視角壓力測試商業計畫</strong>: 「拿我的商業計畫，跑一個 workflow，讓不同 agent 分別站在投資人、客戶、競爭對手的角度，逐點挑毛病。」(對抗式驗證用在非技術任務)</p>

<p>🔹 <strong>80 份履歷排序</strong>: 「這裡有 80 份履歷，用 workflow 依後端職缺排序，再仔細複查前十名。先用 AskUserQuestion 工具問我評分標準。」(排序 + 生成並篩選)</p>

<p>🔹 <strong>命名錦標賽</strong>: 「我要幫這個 CLI 工具取名。用 workflow 腦力激盪一堆選項，再跑一場錦標賽選出前三名。」(tournament)</p>

<p>🔹 <strong>驗證部落格草稿的每個技術宣稱</strong>: 「翻過我這篇部落格草稿，用 workflow 對照 codebase 驗證裡面每一個技術宣稱，我不想出包。」(深度驗證)</p>

<p>這些任務的關鍵不是「大」，而是「有很多獨立的小判斷、需要互相把關、而且最好別全擠在同一個上下文裡」。官方還把適用情境整理成一份清單: 遷移重構、深度研究、深度驗證、上千筆排序、記憶與規則遵循、根本原因調查、大規模分流、探索與品味、評估、模型與智慧路由。反過來作者也提醒: 一般的寫程式任務通常不需要五個審查者的陣仗，別為了用而用。</p>

<h2 id="7-看一份真的-workflow-deep-research-長怎樣">7. 看一份真的 workflow: deep-research 長怎樣</h2>

<p>上面那些模式講起來還是抽象，剛好 Claude Code 內建的 <code class="language-plaintext highlighter-rouge">/deep-research</code> 就是用 dynamic workflows 寫的。以下都是直接讀它實際生成的那份 JS 腳本 (數字、門檻都照腳本，不是憑空猜的)。</p>

<p>它的 <code class="language-plaintext highlighter-rouge">meta</code> 先宣告整條流水線分成五個 phase:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">phases</span><span class="p">:</span> <span class="p">[</span>
  <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Scope</span><span class="dl">"</span><span class="p">,</span>      <span class="na">detail</span><span class="p">:</span> <span class="dl">"</span><span class="s2">把問題拆成 5 個搜尋角度</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Search</span><span class="dl">"</span><span class="p">,</span>     <span class="na">detail</span><span class="p">:</span> <span class="dl">"</span><span class="s2">5 個平行 WebSearch agent，一個角度一個</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fetch</span><span class="dl">"</span><span class="p">,</span>      <span class="na">detail</span><span class="p">:</span> <span class="dl">"</span><span class="s2">URL 去重，抓前 15 個來源，抽出可查證的主張</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Verify</span><span class="dl">"</span><span class="p">,</span>     <span class="na">detail</span><span class="p">:</span> <span class="dl">"</span><span class="s2">每個主張 3 票對抗式驗證，2/3 票反駁就淘汰</span><span class="dl">"</span> <span class="p">},</span>
  <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Synthesize</span><span class="dl">"</span><span class="p">,</span> <span class="na">detail</span><span class="p">:</span> <span class="dl">"</span><span class="s2">合併語意重複、依信心排序、附上出處</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">]</span>
</code></pre></div></div>

<p>這就是 Claude 為「深度研究」這個任務即時寫出來的 harness，整條流水線長這樣:</p>

<div style="margin:1.6rem 0;font-size:.9rem;">
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.55rem .75rem;border-radius:8px;background:var(--tag-bg);border:1px solid var(--border);">
<div style="flex:0 0 96px;font-weight:600;color:var(--accent);">Scope</div>
<div style="flex:0 0 auto;font-size:.75rem;background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:.05rem .5rem;">1 個 agent</div>
<div style="flex:1 1 180px;color:var(--text);">把問題拆成 5 個搜尋角度</div>
</div>
<div style="text-align:center;font-size:.76rem;color:var(--text-secondary);padding:.2rem 0;">↓ &nbsp;pipeline，階段間不等齊</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.55rem .75rem;border-radius:8px;background:var(--tag-bg);border:1px solid var(--border);">
<div style="flex:0 0 96px;font-weight:600;color:var(--accent);">Search</div>
<div style="flex:0 0 auto;font-size:.75rem;background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:.05rem .5rem;">5 個 agent · 平行</div>
<div style="flex:1 1 180px;color:var(--text);">一個角度一個 WebSearch</div>
</div>
<div style="text-align:center;font-size:.76rem;color:var(--text-secondary);padding:.2rem 0;">↓ &nbsp;純 JS: URL 正規化 + 去重 + 抓取額度控制</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.55rem .75rem;border-radius:8px;background:var(--tag-bg);border:1px solid var(--border);">
<div style="flex:0 0 96px;font-weight:600;color:var(--accent);">Fetch</div>
<div style="flex:0 0 auto;font-size:.75rem;background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:.05rem .5rem;">≤15 個 agent</div>
<div style="flex:1 1 180px;color:var(--text);">每個來源抽出可查證的主張</div>
</div>
<div style="text-align:center;font-size:.76rem;color:var(--text-secondary);padding:.2rem 0;">↓ &nbsp;同步點 (barrier): 等所有主張到齊，排序取前 25</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.55rem .75rem;border-radius:8px;background:var(--tag-bg);border:1px solid var(--border);">
<div style="flex:0 0 96px;font-weight:600;color:var(--accent);">Verify</div>
<div style="flex:0 0 auto;font-size:.75rem;background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:.05rem .5rem;">25 × 3 個 agent</div>
<div style="flex:1 1 180px;color:var(--text);">每個主張三個找碴 agent，2/3 反駁就淘汰</div>
</div>
<div style="text-align:center;font-size:.76rem;color:var(--text-secondary);padding:.2rem 0;">↓ &nbsp;純 JS: 計票 + 篩出存活的主張</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.55rem .75rem;border-radius:8px;background:var(--tag-bg);border:1px solid var(--border);">
<div style="flex:0 0 96px;font-weight:600;color:var(--accent);">Synthesize</div>
<div style="flex:0 0 auto;font-size:.75rem;background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:.05rem .5rem;">1 個 agent</div>
<div style="flex:1 1 180px;color:var(--text);">合併、排序、附出處，輸出報告</div>
</div>
<div style="font-size:.76rem;color:var(--text-secondary);padding:.55rem .2rem 0;line-height:1.5;">藍底方塊 = 模型判斷 (<code>agent()</code>)，中間的連接線 = 寫死的 JavaScript 編排。判斷交給模型，串接交給程式。</div>
</div>

<p style="text-align:right;font-size:.8rem;color:var(--text-secondary);margin-top:-.8rem;">✏️ 小編製圖</p>

<p>逐段拆:</p>

<p><strong>Scope (1 個 agent):</strong> 把使用者的問題交給一個 agent，要它拆成 5 個互補的搜尋角度 (廣度、學術、近期新聞、反方、實作面)，並用 schema 強制回傳結構化 JSON，不准自由發揮。</p>

<p><strong>Search → Fetch (用 <code class="language-plaintext highlighter-rouge">pipeline</code>，不等齊):</strong> <code class="language-plaintext highlighter-rouge">pipeline()</code> 跟 <code class="language-plaintext highlighter-rouge">parallel()</code> 的差別是「要不要等所有人都跑完才往下」(後者是同步點/barrier)。pipeline 不等，所以角度 A 搜完、去重、開始抓網頁的同時，角度 B 可能還在搜，不必等齊。每個角度: 一個 search agent 找 4-6 個結果 → 一段「純 JavaScript」做 URL 正規化、去重、抓取額度控制 → 再對每個沒看過的 URL 平行開一個 fetch agent 抽出主張。</p>

<p>去重、配額這些事完全不是 agent 在做，是普通的 JS:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">novel</span> <span class="o">=</span> <span class="nx">sorted</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="nf">normURL</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">url</span><span class="p">)</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">seen</span><span class="p">.</span><span class="nf">has</span><span class="p">(</span><span class="nx">key</span><span class="p">))</span> <span class="p">{</span> <span class="nx">dupes</span><span class="p">.</span><span class="nf">push</span><span class="p">(...);</span> <span class="k">return</span> <span class="kc">false</span> <span class="p">}</span>    <span class="c1">// 看過了，丟掉</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">fetchSlots</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">relRank</span><span class="p">[</span><span class="nx">r</span><span class="p">.</span><span class="nx">relevance</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>      <span class="c1">// 額度用完，低相關的丟掉</span>
    <span class="nx">budgetDropped</span><span class="p">.</span><span class="nf">push</span><span class="p">(...);</span> <span class="k">return</span> <span class="kc">false</span>
  <span class="p">}</span>
  <span class="nx">seen</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="p">...);</span> <span class="nx">fetchSlots</span><span class="o">--</span><span class="p">;</span> <span class="k">return</span> <span class="kc">true</span>
<span class="p">})</span>
</code></pre></div></div>

<p><strong>Verify (用 <code class="language-plaintext highlighter-rouge">parallel</code>，這裡刻意要同步等齊):</strong> 對排序後的前 25 個主張，每個都平行開「3 個」對抗式驗證 agent。它們的 prompt 開門見山就是「要懷疑、想辦法反駁這個主張」，而且「不確定就預設 refuted=true」。3 票有 2 票反駁，這個主張就淘汰。這正是前面講的對抗式驗證加上生成並篩選，而 self-preferential bias 在這裡被結構性擋掉了: 驗證的不是寫出主張的那個 agent，是另外三個被叫來找碴的 agent。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">parallel</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="mi">3</span> <span class="p">},</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">v</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">()</span> <span class="o">=&gt;</span>
  <span class="nf">agent</span><span class="p">(</span><span class="nc">VERIFY_PROMPT</span><span class="p">(</span><span class="nx">claim</span><span class="p">,</span> <span class="nx">v</span><span class="p">),</span> <span class="p">{</span> <span class="na">schema</span><span class="p">:</span> <span class="nx">VERDICT_SCHEMA</span> <span class="p">})</span>
<span class="p">)).</span><span class="nf">then</span><span class="p">(</span><span class="nx">verdicts</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">refuted</span> <span class="o">=</span> <span class="nx">verdicts</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="nx">v</span> <span class="o">=&gt;</span> <span class="nx">v</span><span class="p">.</span><span class="nx">refuted</span><span class="p">).</span><span class="nx">length</span>
  <span class="k">return</span> <span class="p">{</span> <span class="p">...</span><span class="nx">claim</span><span class="p">,</span> <span class="na">survives</span><span class="p">:</span> <span class="nx">refuted</span> <span class="o">&lt;</span> <span class="mi">2</span> <span class="p">}</span>   <span class="c1">// 2 票反駁就淘汰</span>
<span class="p">})</span>
</code></pre></div></div>

<p><strong>Synthesize (1 個 agent):</strong> 把存活下來的主張交給一個 agent，合併語意重複、分組成一條條 finding、依信心高低排序、附上出處，產出一份有引用的報告。</p>

<p>看完這份腳本就會發現，所有「需要判斷」的事 (拆角度、搜尋、抽主張、反駁、整合) 都包在 <code class="language-plaintext highlighter-rouge">agent()</code> 裡; 而所有「不該讓模型亂猜」的事 (串接、去重、計票、排序、淘汰) 全是寫死的 JavaScript。一個 agent 偷懶或看走眼，只會壞掉它自己那一格，不會污染整條流水線。</p>

<h2 id="8-模型生成腳本但腳本本身是決定性的">8. 模型生成腳本，但腳本本身是決定性的</h2>

<p>這裡有個觀念要分清楚，也是整套設計最巧妙的地方: <strong>寫 workflow 的是模型，但 workflow 一旦寫好，執行就是決定性的。</strong></p>

<p>模型的判斷只發生在兩個地方: 一是「寫出這份腳本」的當下，二是每個 <code class="language-plaintext highlighter-rouge">agent()</code> 呼叫的內部。中間的迴圈、分支、去重、計票、合併，全是寫死的 JavaScript，跑幾次結果都一樣。所謂的「dynamic」，動的是「腳本被生出來」這一刻; 一旦生出來，它就是一份規規矩矩、可重播的程式。</p>

<p><a href="https://x.com/yi_ding/status/2060448752331268334">Yi Ding 的觀察</a>蠻精準的: 這東西本質上就是一個 (相當詳細的) prompt，教模型用 JavaScript 寫出一段像圖結構的編排描述。那段「教模型寫 workflow」的 prompt 大致在教這些 (示意，非逐字):</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>你可以寫一支 JavaScript 編排腳本，開頭必須是:
  export const meta = { name, description, phases }   // 純字面值，不能放變數或運算

腳本裡可以用這些函式:
  agent(prompt, { schema, label })  // 開一個 sub-agent;給 schema 就強制它回傳結構化 JSON
  pipeline(items, stage1, stage2)   // 每個項目獨立流過各階段，不等齊 ← 預設首選
  parallel(thunks)                  // 一次跑多個、等全部完成才往下 (barrier) ← 真的需要才用
  phase(title) / log(msg)           // 分組與進度回報

規則:
  - 預設用 pipeline，只有「下一階段真的需要上一階段全部結果」時才用 parallel
  - 想更可信就用對抗式驗證: 每個發現另外開 N 個 agent 試著反駁它
  - 不准用 Date.now()、Math.random()、無參數的 new Date()
</code></pre></div></div>

<p>最後那條規則小編覺得最特別。一個正常的 JavaScript 環境怎麼可能不給你取時間跟亂數? 但 workflow 偏偏把這幾個函式做成會直接報錯。原因藏在 resume 機制裡: workflow 會<a href="https://alexop.dev/posts/claude-code-workflows-deterministic-orchestration/">把每一個 <code class="language-plaintext highlighter-rouge">agent()</code> 呼叫的結果寫進一份執行日誌</a>，這樣中斷的執行才能原地接續 (已經跑完的 agent 直接從日誌裡回傳快取結果，只有沒跑完的才重跑)。一旦腳本裡摻了時間或亂數這種非決定性的東西，重跑時就會跟上一次對不起來，日誌裡的快取立刻失效。所以要時間戳記，就從 <code class="language-plaintext highlighter-rouge">args</code> 傳進來; 要讓每個 agent 長得不一樣，就用索引 (index) 去變化它的 prompt 或 label。</p>

<p>這個特別的限制也說明了整套設計的核心: 編排層必須是純粹、可重播的，模型的不確定性被嚴格關在 <code class="language-plaintext highlighter-rouge">agent()</code> 這個盒子裡。外面是一份能 resume、能重跑、能存檔重用的程式; 裡面才是會思考、會判斷、偶爾也會犯錯的模型。</p>

<p>也有幾個現實邊界值得先知道:</p>

<ul>
  <li><strong>成本</strong>: 一次跑幾十上百個 agent，token 成本明顯比一般對話高，官方建議先拿小範圍試水溫</li>
  <li><strong>不能中途插手</strong>: 執行中不能插入使用者輸入，需要人工核准的地方，得拆成兩個 workflow、中間留一個核准空檔</li>
  <li><strong>檔案修改自動核准</strong>: sub-agent 預設跑在 acceptEdits 模式，所以真正該把關的是腳本本身與 workflow 邊界</li>
  <li><strong>resume 有限制</strong>: 只保證在同一個 Claude Code session 內有效，關掉重開就是從頭跑</li>
</ul>

<h2 id="收束-這一切其實很符合-bitter-lesson">收束: 這一切其實很符合 Bitter Lesson</h2>

<p>ihower 之前在這個部落格寫過一篇 <a href="/2026/02/17/bitter-lesson-agent-harness/">Bitter Lesson 與 agent harness</a>，裡面點名一個「workflow trap」: 視覺化的 workflow builder 把任務拆解鎖死在一張固定的圖上，模型之後變強了，這張圖也不會自動受益，反而變成包袱。照 Bitter Lesson 的邏輯，把可靠性押在人工手刻的鷹架程式碼 (scaffold) 上，是把複雜度搬錯方向。</p>

<p>dynamic workflows 巧妙地閃過了這個陷阱。差別在於: <strong>拆解 workflow 的不是人，是模型，而且每個任務都重新寫一份客製的。</strong> 官方那句話講得最到位: Claude 現在可以「為手上的任務即時寫出自己的 harness」。你拿到了確定性編排帶來的可靠性 (避開偷懶、偏袒、漂移)，卻不必付出人工設計鷹架的代價，而那種鷹架正是模型一變強就第一個被淘汰的東西。所以這個設計是順著 Bitter Lesson 的: 連「編排」這件事本身，也交還給模型。</p>

<p>ihower 那篇的結論是: 原本覺得需要人工編排的 workflow 任務，幾乎都能丟給模型即時生成的 harness，不必再事先設計流程。dynamic workflows 基本上就是這個方向的具體實現。</p>

<h2 id="小編看法">小編看法</h2>

<p>最後小編補幾個比較主觀的想法。</p>

<h3 id="需要-gui-支援">需要 GUI 支援</h3>

<p>如果它就停在 CLI 這個形態，小編覺得採用門檻會偏高。原因是這套概念其實偏抽象: 計畫從上下文搬進腳本、決定性編排、對抗式驗證，這些好處都要想一層才懂，不容易一眼看懂。對多數使用者來說，體感上最直接的反而是「token 怎麼燒這麼兇」，那個爽點 (省下的上下文、避開的偷懶與漂移) 卻是隱形的，藏在你看不到的地方。</p>

<p>但如果把它做成 GUI，讓那份編排腳本變成一張動態的節點與連線圖，agent 怎麼分流、在哪裡匯合、哪一條審查鏈正在跑、哪個節點卡住了，全部即時長在眼前，採用率會高很多。因為「看見流程怎麼跑」這件事太有感了，它把隱形的價值變成你盯著看的畫面。</p>

<h3 id="希望同時支援事先生成和動態生成">希望同時支援事先生成和動態生成</h3>

<p>目前 Claude Code 是靠 <code class="language-plaintext highlighter-rouge">ultracode</code> 自動判斷這個任務要不要動用 workflow，而且每次都是當場重新編排一份。你雖然可以把喜歡的那份存下來、下次重用，但小編實測後發現，存下來的腳本常常把這一次執行的具體場景 (檔名、數量、路徑那些) 寫死進去，重用性其實沒那麼完美。更可惜的是，你沒辦法「先讓它把 workflow 的 JavaScript 生出來、自己檢查過、確認沒問題，再真正執行」，生成跟執行是綁在一起的。</p>

<p>其實這就是前面講的 LangChain interpreter skills 在做的事: 事先寫好一份確定性的編排腳本，存起來重用。小編希望 Claude Code 能兩種都支援: 對重複性高的任務，可以事先生成、檢視、微調一份 workflow 腳本再拿去跑; 對一次性的任務，維持現在每次動態生成的做法。</p>

<p>不過還是得說一下這兩種做法的優缺點:</p>

<ul>
  <li><strong>事先生成</strong>: 可檢視、可測試、可微調，對重複性任務更可控。但缺點是一旦把編排凍結下來，模型下一版變強了，這份腳本也不會自動受益，反而可能變包袱，這正是前面講的 Bitter Lesson 的風險。</li>
  <li><strong>動態生成</strong>: 每次針對當下任務量身定做，模型變強就自動受益，不會被舊腳本綁住。但缺點是你沒辦法事先檢查，生成跟執行綁在一起，而且存下來的腳本容易把場景寫死。</li>
</ul>

<p>或是有一種中間解: 執行前能把生成的腳本叫出來瞄一眼、改幾筆 (要不要看是你的自由，不強制)，存檔時多一步「幫我把這次的檔名、數量、路徑抽成參數」，讓重用的是「編排骨架」而不是「那一次的場景」。這樣既保住動態生成的彈性，又補掉現在重用會寫死的痛點。</p>

<h3 id="openai-的缺席">OpenAI 的缺席</h3>

<p>小編覺得比較可惜的是，整條脈絡最起點的 Code Interpreter，當年其實是 OpenAI 在 ChatGPT 上帶火的 (一度叫 Advanced Data Analysis)，是它先讓大家看到「讓模型寫程式碼、跑程式碼」有多好用。但後來把「程式碼當行動空間」一路往 agent 編排推的，反而是 Cloudflare、Anthropic、LangChain 跟學界。OpenAI 在 Codex 產品層有做 subagents (平行 spawn 多個 agent)，但在 API 層、SDK、產品上一直沒有走「程式碼當行動空間」這個方向，沒有 PTC 等級的東西，Code Interpreter 裡的程式碼到現在還是碰不到 agent 的工具。</p>

<h2 id="補充比較-那-agent-teams-呢-小編沒那麼看好">補充比較: 那 agent teams 呢? 小編沒那麼看好</h2>

<p>跟 dynamic workflows 幾乎同期，Claude Code 還推了另一個平行化功能 <a href="https://code.claude.com/docs/en/agent-teams">agent teams</a> (目前還是 experimental、預設關閉)。</p>

<p>一句話講清楚差別: dynamic workflows 是「模型寫一份決定性腳本，編排一群各自獨立的 sub-agent」; agent teams 則是「開一個 team lead，底下生出好幾個<strong>完整的</strong> Claude Code 實例當隊友，隊友各有自己的上下文、能<strong>互相點對點傳訊</strong>、搶同一張 task list 的工作」。subagent 只會把結果回報給主 agent，彼此不講話; agent teams 的隊友則能互相討論、互相挑戰，你甚至可以分別對每個隊友下指令。</p>

<p><img src="/assets/images/code-act-to-dynamic-workflows-teams-vs-workflows.jpg" alt="Agent Teams vs Dynamic Workflows" />
<small>圖片來源: <a href="https://x.com/_catwu/status/2060054180379689074">Cat Wu (@_catwu)</a></small></p>

<p>小編在 <a href="/2026/05/19/multi-agent-anti-patterns-and-patterns/">《Multi-Agent 反模式與推薦模式》</a> 那篇整理過一個核心判斷: <strong>multi-agent 的價值是「並行覆蓋」，不是「分工」。</strong> 而 agent teams 的賣點剛好踩在這條線上，看你怎麼用，很容易滑到錯的那邊:</p>

<p>1️⃣ <strong>「角色分工」很容易滑進三省六部的幻覺。</strong> 官方範例那種「一個管 UX、一個管架構、一個當魔鬼代言人」「一個查安全、一個查效能、一個查測試」，如果是<strong>唯讀的審查或研究</strong>，那是健康的並行覆蓋，沒問題。但只要任務變成「每個隊友各認領一個模組去<strong>寫 code</strong>」，就掉進那篇點名的反模式: LLM 沒有人類的注意力上限，貼上「架構師」「QA」的角色標籤不會讓它更強，只會多出人為的推諉跟一層層的傳話漂移。</p>

<p>2️⃣ <strong>並行寫入的老問題它沒解，只是叫你自己閃。</strong> 官方文件自己就寫: 「兩個隊友改同一個檔案會互相覆蓋，請把工作切開、讓每個隊友各自負責不同的檔案。」避免衝突的責任，被原封不動丟回給你手動切。Devin、Cognition 的工程經驗早就講白了: <strong>平行寫入到現在還是行不通</strong>，可靠的做法是「單線寫入、其他 agent 只負責判斷」。</p>

<p>3️⃣ <strong>點對點通訊 = 通訊開銷 + 傳話遊戲。</strong> 隊友彼此直接傳訊 (官方範例還鼓勵它們「像科學辯論一樣互相反駁」)，agent 一多，訊息就多、漂移就多、也更難觀測。對照之下，orchestrator-worker (subagent、dynamic workflows 走的就是這條) 把溝通收斂到中心，反而更穩、也更好整合。那篇也提過，大廠 (Anthropic、OpenAI、Google) 實際用的都是 orchestrator-worker，不是角色扮演的流水線。</p>

<p>4️⃣ <strong>成本跟可靠性都還沒到位。</strong> 每個隊友是一個<strong>完整的</strong> Claude Code 實例 (不是輕量 subagent)，token 隨人數線性往上疊。multi-agent 普遍燒 3-10 倍，Anthropic 自家 research 系統甚至約 15 倍。而且三個 90% 正確率的 agent 串起來，整體正確率掉到 72.9%。目前它還掛著一排 experimental 限制: 不能 resume、task 狀態會 lag、關機很慢、一次只能帶一個 team、不能巢狀、lead 還不能換人。學界也才剛出一篇 <a href="https://arxiv.org/abs/2601.13295">CooperBench</a>，直接以「<strong>為什麼 coding agent 還當不了你的隊友</strong>」為題，點出它們缺的正是找共識、協調這種社會性能力。</p>

<p>另外一個觀察: agent teams 到現在還是 experimental、預設關閉，你得自己去設一個 <code class="language-plaintext highlighter-rouge">CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS</code> 環境變數才打得開。相比之下 dynamic workflows 直接上線、<code class="language-plaintext highlighter-rouge">ultracode</code> 隨叫隨用，兩者的成熟度差距從預設值就看得出來。</p>

<p>小編認為 agent teams 相對 dynamic workflows 的優勢並不明確。官方推薦的入門場景是唯讀的並行探索 (PR 審查、競爭假設除錯、查資料)，但 dynamic workflows 用決定性編排加上對抗式驗證，一樣能做這些事，而且更便宜、更可重播。agent teams 真正不同的地方是「你可以在執行中途跟個別隊友對話、調整方向」，這是 workflow (背景跑完才回來) 做不到的，但這個互動性是否值得額外的成本和複雜度，目前沒有明確的證據。一旦想拿它來「分工寫 code」，就撞進那篇反模式的失敗區。</p>

<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;margin:1.6rem 0;font-size:.86rem;">
<div style="display:flex;flex-wrap:wrap;align-items:center;background:var(--surface);padding:.55rem .8rem;font-weight:600;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">維度</div>
<div style="flex:1.4 1 150px;color:var(--accent);">Dynamic Workflows</div>
<div style="flex:1.4 1 150px;">Agent Teams</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">編排者</div>
<div style="flex:1.4 1 150px;">模型寫的決定性腳本</div>
<div style="flex:1.4 1 150px;">team lead 即時協調，隊友自己搶工作</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">寫入模型</div>
<div style="flex:1.4 1 150px;">收斂到腳本控管，偏單線</div>
<div style="flex:1.4 1 150px;">多個完整實例可同時寫，衝突要你自己切檔案</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">agent 間通訊</div>
<div style="flex:1.4 1 150px;">不互聊，結果回腳本</div>
<div style="flex:1.4 1 150px;">點對點互相傳訊</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">可重播 / resume</div>
<div style="flex:1.4 1 150px;color:var(--accent);">有 (執行日誌、禁時間亂數)</div>
<div style="flex:1.4 1 150px;">還不行 (experimental)</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--border);">
<div style="flex:1 1 90px;">成本</div>
<div style="flex:1.4 1 150px;">高，但編排層零推理</div>
<div style="flex:1.4 1 150px;">更高，每個隊友一個完整實例</div>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;padding:.6rem .8rem;">
<div style="flex:1 1 90px;">最適合</div>
<div style="flex:1.4 1 150px;">大規模、可決定性編排的覆蓋型任務</div>
<div style="flex:1.4 1 150px;">唯讀的並行探索 (審查 / 研究 / 競爭假設)</div>
</div>
</div>

<p style="text-align:right;font-size:.8rem;color:var(--text-secondary);margin-top:-.8rem;">✏️ 小編製圖</p>

<p>這兩者真正的差異在前面講的那些: 決定性與可重播、成本結構、通訊模型 (中心化 vs. 點對點)、以及並行寫入的可靠性。從這些維度看，dynamic workflows 目前成熟度明顯領先，而 agent teams 還在找它真正不可替代的場景。</p>

<h2 id="參考連結">參考連結</h2>

<ul>
  <li><a href="https://arxiv.org/abs/2402.01030">Executable Code Actions Elicit Better LLM Agents (CodeAct 論文)</a></li>
  <li><a href="https://github.com/huggingface/smolagents">smolagents (Hugging Face CodeAct 實作)</a></li>
  <li><a href="https://developers.openai.com/api/docs/guides/tools-code-interpreter">OpenAI Code Interpreter</a></li>
  <li><a href="https://blog.cloudflare.com/code-mode/">Cloudflare Code Mode</a></li>
  <li><a href="https://blog.cloudflare.com/code-mode-mcp/">Cloudflare Code Mode MCP Server</a></li>
  <li><a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling">Claude Programmatic Tool Calling (PTC)</a></li>
  <li><a href="https://www.langchain.com/blog/give-your-agents-an-interpreter">LangChain Deep Agents Interpreter</a></li>
  <li><a href="https://www.langchain.com/blog/interpreter-skills">LangChain Interpreter Skills</a></li>
  <li><a href="https://arxiv.org/abs/2512.24601">RLM 論文 (Recursive Language Models)</a></li>
  <li><a href="https://claude.com/blog/introducing-dynamic-workflows-in-claude-code">Claude Code Dynamic Workflows 官方 Blog</a></li>
  <li><a href="https://code.claude.com/docs/en/workflows">Claude Code Dynamic Workflows 官方文件</a></li>
  <li><a href="https://x.com/trq212/status/2061907337154367865">“A harness for every task” by trq212</a></li>
  <li><a href="https://x.com/voxyz_ai/status/2061782441606451381">Voxyz: 計畫從上下文搬進腳本</a></li>
  <li><a href="https://x.com/yi_ding/status/2060448752331268334">Yi Ding: workflow prompt 分析</a></li>
  <li><a href="https://alexop.dev/posts/claude-code-workflows-deterministic-orchestration/">alexop.dev: 決定性編排與 resume 機制</a></li>
  <li><a href="https://x.com/lateinteraction/status/2060078643133763839">Omar Khattab: Claude Code 終於是一個 RLM</a></li>
  <li><a href="https://x.com/a1zhang/status/2060071701879066626">Alex Zhang: 第一個被認真訓練成 RLM 的前沿模型</a></li>
  <li><a href="https://x.com/_catwu/status/2060054180379689074">Cat Wu: Agent Teams vs Dynamic Workflows</a></li>
  <li><a href="https://code.claude.com/docs/en/agent-teams">Claude Code Agent Teams 文件</a></li>
  <li><a href="https://arxiv.org/abs/2503.13657">Why Do Multi-Agent LLM Systems Fail? (MAST)</a></li>
  <li><a href="https://arxiv.org/abs/2601.13295">CooperBench: 為什麼 coding agent 還當不了你的隊友</a></li>
  <li><a href="https://huggingface.co/docs/smolagents/en/examples/multiagents">smolagents Multi-Agent 範例</a></li>
  <li><a href="/2026/02/17/bitter-lesson-agent-harness/">Bitter Lesson 與 agent harness (愛好 AI Blog)</a></li>
  <li><a href="/2026/05/19/multi-agent-anti-patterns-and-patterns/">Multi-Agent 反模式與推薦模式 (愛好 AI Blog)</a></li>
</ul>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Workflow" /><category term="Tool Use" /><summary type="html"><![CDATA[Claude Code 上週發表 dynamic workflows (官方文件)，大家第一反應大多是「終於能一次跑幾百個 sub-agent 了」。這當然很猛，但小編覺得更值得講的，是它背後那條技術脈絡: 從 2024 年初的 Code Act 一路長出來的。]]></summary></entry><entry><title type="html">Codex App 那些 CLI 做不到的 GUI 特色</title><link href="https://blog.aihao.tw/2026/06/05/codex-app-gui-features/" rel="alternate" type="text/html" title="Codex App 那些 CLI 做不到的 GUI 特色" /><published>2026-06-05T00:00:00+00:00</published><updated>2026-06-05T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/05/codex-app-gui-features</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/05/codex-app-gui-features/"><![CDATA[<p>最近 ihower 在 <a href="https://ihower.tw/blog/13658-aie-openai-codex">AI Engineer 看 OpenAI Codex 的筆記</a>裡提到一個觀察: 他把日常寫程式從 CLI 工具搬到了 Codex，而且更推薦桌面 App 版本而不是 CLI。小編覺得這個判斷蠻準的: 等到大家把人機互動的模式摸清楚之後，設計良好的 GUI 終究會超越純命令列的體驗。</p>

<p>OpenAI 對 <a href="https://openai.com/zh-Hant/codex/">Codex App</a> 的投入，確實比 CLI 下了更多功夫。這篇就來條列幾個「只有 GUI 才辦得到」的特色，很多都是 CLI 裡根本想像不到的玩法。主軸參考自 Jason Liu (jxnl) 的 <a href="https://jxnl.co/writing/2026/05/10/codex-maxxing/">Codex-maxxing</a> 和他後來寫的 <a href="https://x.com/jxnlco/status/2066970432855581052">Three Ways Codex Can Use a Computer</a>，加上近期幾波官方更新。各特色也附上<a href="https://developers.openai.com/codex/app">官方文件</a>連結方便深入。</p>

<h2 id="1-appshots-把畫面外的內容也一起餵進去">1. Appshots: 把畫面外的內容也一起餵進去</h2>

<p>按住左右兩顆 Command 鍵 (快捷鍵可改)，Codex 就會把你滑鼠所在的視窗截圖，自動塞進輸入框。</p>

<p>你會說這不就是截圖而已嗎? 不，不只是截圖而已，它<strong>連畫面上的文字，甚至畫面外的文字也會一起傳入</strong>，超猛。舉例來說，你在看一份 Google Doc 拍 appshot，已經捲出畫面外的內容也會被讀進去。這跟 computer use 一樣，拿得到 App 裡的完整文字，而不只是肉眼可見的那一塊。(官方文件: <a href="https://developers.openai.com/codex/appshots">Appshots</a>)</p>

<p>實務上很有感: 以前在瀏覽器看到一個 bug、在設計稿看到一個要實作的介面，得自己截圖貼上、再打字補充脈絡。現在一個快捷鍵就把完整上下文帶進去了。</p>

<p>Jason 給了一個好記的分法: Appshot 是你「指」給 Codex 看，<code class="language-plaintext highlighter-rouge">@browser</code>、<code class="language-plaintext highlighter-rouge">@chrome</code>、<code class="language-plaintext highlighter-rouge">@computer</code> 才是 Codex「動手」。而且 Appshot 抓的是最前面那個視窗、不是整個桌面，所以你可以只給它需要的脈絡，不用把整個 App 的控制權交出去。</p>

<h2 id="2-remote-control-手機或另一台電腦都能遠端操控">2. Remote control: 手機或另一台電腦都能遠端操控</h2>

<p>Codex 可以遠端控制跑在另一台電腦上的 Codex: 手機的 ChatGPT App 能遙控，桌面版的 Codex app 也能遙控另一台機器，連螢幕鎖定的狀態下也跑得動。iPhone 上的操作體驗做得相當完整，不只是看進度而已。</p>

<p>而且 Linux 上的 Codex 也能被遠端遙控，不一定要 Mac 裝 Codex app 才行。有趣的是官方文件沒寫這段，可能還算是個隱藏功能 🤣 指令是在那台 Linux 上跑 <code class="language-plaintext highlighter-rouge">codex remote-control</code>，它就會起一個 Codex server，然後你用 Mac 或手機上的 Codex app 就能去遙控這台機器。ihower 實測拿 Ubuntu Desktop 來遠端遙控、打開 Chrome 都沒問題，比 SSH connect 好用多了。</p>

<blockquote>
  <p>編按: 官方文件: <a href="https://developers.openai.com/codex/remote-connections">Remote connections</a>、公告 <a href="https://openai.com/index/work-with-codex-from-anywhere/">Work with Codex from anywhere</a>。文件主要寫 Mac/Windows 當 host，Linux 的 <code class="language-plaintext highlighter-rouge">codex remote-control</code> 走法目前沒明說。</p>
</blockquote>

<h2 id="3-computerchromebrowser-三種操作介面該用哪一個">3. @computer、@chrome、@browser: 三種操作介面，該用哪一個?</h2>

<p>GUI 版把「讓 Agent 去操作介面」拆成三個情境。這三者能做的事重疊到很容易搞混，所以 Jason 後來又專門寫了一篇 <a href="https://x.com/jxnlco/status/2066970432855581052">Three Ways Codex Can Use a Computer</a> 來講該怎麼選:</p>

<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;margin:1.5em 0;font-size:.95em;line-height:1.5">
<div style="display:flex;flex-wrap:wrap;background:var(--surface);border-bottom:1px solid var(--border);font-weight:600;color:var(--text-secondary)">
<div style="flex:1 1 220px;padding:.6em .9em">任務需要的是⋯</div>
<div style="flex:1 1 140px;padding:.6em .9em">用哪個</div>
<div style="flex:1 1 140px;padding:.6em .9em">怎麼觸發</div>
</div>
<div style="display:flex;flex-wrap:wrap;border-bottom:1px solid var(--border)">
<div style="flex:1 1 220px;padding:.6em .9em">原生桌面 App，或橫跨多個 App 的流程</div>
<div style="flex:1 1 140px;padding:.6em .9em">Computer Use</div>
<div style="flex:1 1 140px;padding:.6em .9em"><code>@computer</code> 或 <code>@AppName</code></div>
</div>
<div style="display:flex;flex-wrap:wrap;border-bottom:1px solid var(--border)">
<div style="flex:1 1 220px;padding:.6em .9em">你已登入的瀏覽器狀態、cookie、profile 或分頁</div>
<div style="flex:1 1 140px;padding:.6em .9em">Chrome 外掛</div>
<div style="flex:1 1 140px;padding:.6em .9em"><code>@chrome</code></div>
</div>
<div style="display:flex;flex-wrap:wrap">
<div style="flex:1 1 220px;padding:.6em .9em">本機 web app、公開頁面，或隔離的瀏覽器 session</div>
<div style="flex:1 1 140px;padding:.6em .9em">內建瀏覽器</div>
<div style="flex:1 1 140px;padding:.6em .9em"><code>@browser</code></div>
</div>
</div>

<p>不過在選之前有個前提: 能用外掛或 MCP 做的就不要用視覺操作，Slack 外掛抓一條討論串比在畫面上點來點去精準，GitHub 外掛產出的動作也比操作網站容易檢查。視覺操作真正的價值，是在結構化工具做不到的那些地方。</p>

<h3 id="computer-涵蓋最廣也最慢"><code class="language-plaintext highlighter-rouge">@computer</code>: 涵蓋最廣，也最慢</h3>

<p>macOS 和 Windows 上你授權過的 App，它都能看畫面、操作視窗和選單、輸入鍵盤、用剪貼簿，依你裝了什麼，範圍可以涵蓋 Spotify、Xcode、系統設定、iOS 模擬器，甚至透過 iPhone Mirroring 去操作你的 iPhone。慢是因為它得先看介面、判斷該點哪裡、等 App 回應、再檢查下一個狀態，但換來的是連完全沒有 API 的 App 都能操作。慢也不代表會打斷你: macOS 上它在背景操作已授權的 App，核心技術是 OS 層級的 sandbox，每個 agent 有自己獨立的滑鼠指標，可以平行跑。</p>

<p>Jason 舉的例子蠻有意思: 他的包裹被偷，Amazon 說要等 25 分鐘才排得到客服，他就開一條 thread 交代 Codex 每五分鐘看一次聊天室、等真人出現改成每分鐘一次，然後盡力把退款要回來，他洗完澡回來退款已經處理好了。這也是三者裡信任邊界最大的，一次只給一個明確的 App 或流程，遇到金流、帳號、憑證這類動作人要在旁邊。(官方文件: <a href="https://developers.openai.com/codex/app/computer-use">Computer use</a>)</p>

<h3 id="chrome-帶著你的登入狀態還能管多個分頁"><code class="language-plaintext highlighter-rouge">@chrome</code>: 帶著你的登入狀態，還能管多個分頁</h3>

<p>任務只要牽涉到你的帳號、cookie、瀏覽器 profile 或已經登入的分頁就用這個: Gmail、客服後台、內部儀表板，或把資料大量搬進 CRM/CMS 這類重複操作。它跟 <code class="language-plaintext highlighter-rouge">@computer</code> 的差別在於，它把工作理解成「瀏覽器的工作流」而不是一連串螢幕座標，所以能同時掌握多個分頁: 一個分頁讀脈絡、跟另一個比對、再到第三個把事情做完。每個任務開一個 tab group，做完自動清掉，只在需要你 review 時才把分頁交還，所以你照常用瀏覽器它也不打架。</p>

<p>要注意的是網站會把 Codex 的點擊、送出表單、發訊息當成「你本人」的動作，而頁面上的內容是不可信任的輸入。研究、瀏覽、草擬可以放手讓它自動做，但送出、發布、購買這些步驟要明確要求先讓你過目。另外，如果整個任務都在瀏覽器裡完成，優先用 <code class="language-plaintext highlighter-rouge">@chrome</code> 而不是 <code class="language-plaintext highlighter-rouge">@computer</code>，不必連整台桌面的存取權都一起交出去。(官方文件: <a href="https://developers.openai.com/codex/app/chrome-extension">Chrome extension</a>)</p>

<h3 id="browser-隔離環境用來做你正在開發的網站"><code class="language-plaintext highlighter-rouge">@browser</code>: 隔離環境，用來做你正在開發的網站</h3>

<p>你跟 Codex 看的是同一個算繪出來的頁面，適合本機開發伺服器、不用登入的公開頁面、重現視覺 bug、檢查 responsive 版面，直接在元素上標註、留言、要求調整，它就照著改、即時重新整理給你看。它不會用你平常的瀏覽器 profile、cookie、擴充套件和登入狀態，需要帳號時這是限制，不需要時反而是個乾淨的界線; 要跟 Google 登入或 passkey 周旋，就改用 <code class="language-plaintext highlighter-rouge">@chrome</code>。</p>

<p>Jason 最喜歡的用法是「頁面本身就是規格」: 請 Codex 把一個想法或專案現況做成單一個 index.html 開在內建瀏覽器裡，然後直接在真的頁面上點元素留言「這裡的層次反了」「這塊不要那麼像卡片」，Codex 收到的是註解加上對應的截圖和元素脈絡，改完檔案再重開同一頁給你看下一版。這比互丟截圖和文字描述，更接近跟設計師在同一張畫布上工作。(官方文件: <a href="https://developers.openai.com/codex/app/browser">Browser</a>)</p>

<h2 id="4-語音輸入-在任何地方都能用快捷鍵口述">4. 語音輸入: 在任何地方都能用快捷鍵口述</h2>

<p>Jason 認為語音輸入的價值在於: 它能在想法被壓縮成精緻文字「之前」，先捕捉到那個粗略原始的版本。像「去找一下 Ben 在 Slack 提過的那個東西」這種帶著語感的指令，用打字反而會懶得寫完整。</p>

<p>而且這是 <strong>App 內建</strong>的，不用另外買 Whisper Flow 之類的語音輸入工具、也不用另外裝什麼。設好快捷鍵後，你在任意地方都能直接口述。</p>

<blockquote>
  <p>編按: 如果你用外接麥克風，記得去 Mac 的音量/麥克風設定改一下預設輸入裝置，不然會抓錯。</p>
</blockquote>

<h2 id="5-steering-與-queuing-它還在跑你就先打字">5. Steering 與 Queuing: 它還在跑，你就先打字</h2>

<p>情境是這樣: Codex 還在輸出、還在跑工具，你不必等它停下來才動。直接在輸入框打字送出，這則訊息會有兩種用法:</p>

<ul>
  <li><strong>Steering (插隊改方向)</strong>: 不等當前步驟做完，立刻打斷它、塞進新的指示。適合你看著它正往歪的方向走、想即時修正的時候,例如「這個改小一點」「不對，先別動資料庫」。</li>
  <li><strong>Queuing (排隊接著做)</strong>: 不打斷當前步驟，讓這則訊息排進佇列，等它把手上這步做完再接著執行。適合你已經想好下一步、想先交代起來放著，例如「跑完記得順手開個 PR」「接著把測試補上」。</li>
</ul>

<p>差別就在「現在就插話」還是「等它講完再講」。其實 CLI 也做得到，但你得記住快捷鍵才能正確切換這兩種行為; GUI 則是直接把選項攤在介面上讓你點選，一看就懂該按哪個，不用背指令。</p>

<h2 id="6-釘選-threads-長執行緒重新變成可行解">6. 釘選 threads: 長執行緒重新變成可行解</h2>

<p>Jason 為每一條他在意的重要工作流，都保留一條釘選的對話串，讓這些持久性執行緒隨手就在附近。這些 thread 會累積歷史與決策，變成耐久的紀錄，而不是用完即丟的對話。</p>

<p>有意思的是，「讓單一執行緒一直長期跑下去」過去常被視為 anti-pattern，但現在可以重新考慮這樣用。不過小編覺得這有個前提: 你心智上要很清楚自己會用到 sub-agents 來分流，不能把所有東西都硬塞進同一條主線。這點 Codex-maxxing 那篇講得蠻細的，值得一讀。</p>

<h2 id="7-fork-從任意一則-ai-輸出岔出一條新-thread">7. Fork: 從任意一則 AI 輸出岔出一條新 thread</h2>

<p>在 GUI 上你可以對之前任意一則 AI 的輸出點「fork」，當場拆出一條新的 thread，從那個點接著走別的方向，原本那條對話原封不動。</p>

<p>最常見的用法是岔題不弄亂主線: 你在一條 thread 裡正做著功能 A，半路發現一個 bug，與其在原本對話裡插一段把脈絡攪亂，不如直接從當下這則訊息 fork 出去，在新 thread 裡專心修 bug，而它先前累積的東西 (摸熟的 codebase、剛討論好的計畫) 全都帶著走。或者你剛跟它把一份計畫討論完，想分頭深入其中一塊、又不想動到計畫主線，fork 一下就有一條乾淨的分支。</p>

<p>其實 CLI 也有 <code class="language-plaintext highlighter-rouge">/fork</code>，但痛點在於很難指定「要從哪一則訊息岔出去」(社群裡就有人抱怨對話開頭都長得一樣，根本分不出該 fork 哪一條)。GUI 把整條 transcript 攤在眼前，你直接點那一則輸出就能 fork，分岔點一目了然，這種操作真的是要有圖形介面才順手。</p>

<h2 id="8-平行多工-一個視窗同時跑很多條任務">8. 平行多工: 一個視窗同時跑很多條任務</h2>

<p>Codex App 的介面左側欄就是一條 threads list (對話列表)，每一條 thread 是一個獨立的任務。你在同一個視窗裡可以同時跑多條 thread、各自獨立推進，左欄掃一眼就知道每條跑到哪、哪條已經跑完、哪條卡住要你處理。可以一邊讓它在背景重構某個模組，一邊在另一條 thread 修 bug，點一下左欄就切過去，互不干擾。</p>

<p>這在 CLI 上特別痛: 純命令列一個視窗就是一條對話，想平行就得自己開一堆終端機分頁、自己記哪個在做什麼。也因此市面上冒出一大票工具專門來補這個洞，像 <a href="https://cmux.com/zh-TW">cmux</a> 這種「同時驅動多個 coding agent」的調度工具，連 Claude Code 最近也補上了 <a href="https://code.claude.com/docs/en/agent-view">Agent View</a>，讓你從一個畫面派發、管理多條 session。Codex App 則是一開始就把平行多工內建在介面裡，不用外掛。</p>

<h2 id="9-thread-automations-排程喚醒回到同一條執行緒">9. Thread automations: 排程喚醒、回到同一條執行緒</h2>

<p>這個概念很像 openclaw 的 Heartbeat: 週期性的喚醒呼叫，依排程<strong>回到同一個 Codex 執行緒</strong>繼續推進，而不是每次都從頭開一個新的。</p>

<p>支援分鐘級的頻繁輪詢，也能設每日/每週的定時 check-in。它特別適合做回饋迴圈: 監看 pull request 留言、Google Docs 留言或 Slack 回覆，在你不在座位時持續推進周邊工作。寫 automation prompt 時要交代清楚「每次醒來該做什麼」「怎麼判斷有沒有重要發現」「何時該停下來問人」。(官方文件: <a href="https://developers.openai.com/codex/app/automations">Automations</a>)</p>

<h2 id="10-側邊面板-就地檢視各種工件">10. 側邊面板: 就地檢視各種工件</h2>

<p>這大概是 GUI 相對 CLI 最直覺的勝場。側邊面板讓你可以就地檢視 Markdown、試算表、資料表、文件和投影片，還有 terminal、瀏覽器、檔案瀏覽。</p>

<p>關鍵在於<strong>你和 Agent 看的是同一份工件</strong>: 不用中斷流程，就能檢查、標註、修訂。配合進階註記模式，甚至可以在內建瀏覽器裡直接拖拉、調整頁面元素並留批注，多條修改攢成一批一起送。對設計師和前端協作來說，再也不用截圖畫圈寫「這裡往左移 10px」了。git diff 的 code review 也一樣: 右側直接看變更、逐行留 inline 註解、挑 chunk 分段 commit，全程不離開 App。</p>

<h2 id="11-影像生成-在開發流程裡直接生圖">11. 影像生成: 在開發流程裡直接生圖</h2>

<p>這是 OpenAI 自家有影像模型 (GPT-Image-2) 才辦得到的整合: 你可以直接在對話裡叫 Codex 生成或編輯圖片，不用切到別的工具或另外接 API。大多數人最直接的用法，就是在做 UI 素材、banner、背景、插圖、遊戲 sprite sheet、投影片 mockup 時，能更穩定地一張接一張持續生成，要幾張生幾張、要微調再讓它改，整個過程都在同一條 thread 裡完成。</p>

<h3 id="進階玩法-先生-ui-圖再讓-codex-對照寫-code">進階玩法: 先生 UI 圖，再讓 Codex 對照寫 code</h3>

<p>再往前一步，OpenAI 的 dkundel 分享過一個更有意思的方向: 一般像 Claude 那種「用 AI 設計網頁」是直接生 code 把畫面長出來; 而 Codex 這裡可以反過來，先用 GPT-Image-2 生出一張 UI 設計圖，再讓 Codex 對照那張圖去產生對應的 code，先有視覺、再有實作。他的完整玩法是: 手上沒設計稿時先請 GPT-Image-2 生一張 UI，當成規格交給 GPT-5.5 寫成真正的 app，過程中用內建瀏覽器驗證，還能順手生出 app 需要的素材來潤飾;「Build Web Apps」外掛就把這條迴圈包成一個 skill。小編還沒認真比較過這兩條路線 (直接生 code vs 先生圖再對照) 的優劣，但這個創新用法蠻值得玩玩看的。(<a href="https://x.com/dkundel/status/2049591675518165134">原文</a>)</p>

<h2 id="12-長任務-goals-用側邊面板盯一個跑很久的目標">12. 長任務 Goals: 用側邊面板盯一個跑很久的目標</h2>

<p><code class="language-plaintext highlighter-rouge">/goal</code> 這個功能本身 CLI 也有: 給一個目標，它就一路執行到完成，過程可能橫跨數小時甚至數天。GUI 的差別在於「怎麼看進度」可以做得很舒服。(官方文件: <a href="https://developers.openai.com/codex/use-cases/follow-goals">Follow goals</a>)</p>

<p>banteg 的用法很巧妙: 讓 goal 一邊跑、一邊產出一個 HTML 進度儀表板，直接用 Codex 內建瀏覽器開在側邊面板。左邊是 Agent 在做事，右邊是即時更新的圖表和指標 (完成度、匹配率、各 commit 的進展)，一眼就看到跑到哪了。</p>

<p><img src="/assets/images/codex-goal-dashboard.jpg" alt="goal 一邊跑，側邊面板開著即時更新的 HTML 進度儀表板" /></p>

<blockquote>
  <p>圖片來源: <a href="https://x.com/banteg/status/2054436210844528877">banteg on X</a></p>
</blockquote>

<p>另一招是 dotey 提到的 <a href="https://x.com/dotey/status/2058612576775229669"><code class="language-plaintext highlighter-rouge">/side</code></a>: 對一個跑很久的 goal，開一個 side chat 不影響主任務、又帶著完整上下文，直接問「目前進度如何? 還要多久?」。</p>

<p>要補一句 Jason 強調的: 目標的品質決定一切。弱的目標像「把這份 Markdown 實作出來」，強的目標帶著<strong>可衡量的成功標準</strong>。他做 Rich 轉 Rust 的遷移時，直接拿原本的測試套件當驗證標準。「執行的成效，頂多只會跟你給的目標與驗證一樣好。」沒有 oracle 的長任務，跑再久也只是漂移。</p>

<hr />

<p>回到最初的問題: 為什麼這些是 GUI 才辦得到的?</p>

<p>Jason 在 Codex-maxxing 裡點得很到位: 側邊面板讓 Codex「不再只是一個聊天 App，而開始變成工作真正發生的地方」。重點不只是 Codex 能產出工件，而是<strong>你能在不打斷迴圈的情況下，當場檢視並標註它</strong>。這正是 CLI 結構上做不到的: 命令列的輸出跑完就消散，你沒辦法跟 Agent 盯著同一份試算表、同一張投影片邊看邊改。GUI 把「對話」升級成「工作台」，這是純文字介面塞不進來的東西。</p>

<blockquote>
  <p>編按: Jason 另一篇 <a href="https://jxnl.co/writing/2026/05/18/six-levels-of-complexity-of-an-ai-powered-morning-brief-with-codex/">用 Codex 做晨間簡報的六個複雜度層級</a> 也很值得一讀。他用大家都懂的「晨間簡報」當例子，從最陽春的一句 prompt，一層層加上連接器、個人化預設、排程自動化、分專案執行緒、自動草擬回覆、持久記憶庫，示範怎麼把一個小工作流調教成「你工作的迷你作業系統」。核心心法是: 先從新手也懂的版本開始，每次只加一個真正的能力，直到整個系統的形狀自己浮現出來。</p>
</blockquote>

<p>還有一點別忘了: 對非工程師來說，CLI 的上手門檻其實還是偏高。要開終端機、要記指令、要懂一堆參數，光是這關就把很多設計師、PM、行銷的人擋在外面了。Appshots、就地標註、點選式的操作這些 GUI 設計，等於把參與 AI 開發流程的門檻一口氣拉低，讓不寫 code 的人也能直接上手。</p>

<p>CLI 仍然有它輕快、可組合、好自動化的價值，而且可以跑在沒有圖形介面的 Linux server 上，方便接進 CI、排程或各種自動化腳本，這是 GUI 取代不了的。而 Codex App 特別用心經營的，是<strong>人機協作</strong>: 把人跟 Agent 並肩工作的每個環節都做得更順手。當協作的對象從工程師擴大到設計師、PM、各種角色時，一個夠好的 GUI 能裝下的東西，是命令列怎麼也塞不進來的。</p>]]></content><author><name>ihower</name></author><category term="Coding" /><category term="Tool Use" /><category term="Agent" /><summary type="html"><![CDATA[最近 ihower 在 AI Engineer 看 OpenAI Codex 的筆記裡提到一個觀察: 他把日常寫程式從 CLI 工具搬到了 Codex，而且更推薦桌面 App 版本而不是 CLI。小編覺得這個判斷蠻準的: 等到大家把人機互動的模式摸清楚之後，設計良好的 GUI 終究會超越純命令列的體驗。]]></summary></entry><entry><title type="html">Coding Agent 作為軟體優化器: 從 Autoresearch 說起</title><link href="https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer/" rel="alternate" type="text/html" title="Coding Agent 作為軟體優化器: 從 Autoresearch 說起" /><published>2026-06-03T00:00:00+00:00</published><updated>2026-06-03T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/03/coding-agent-as-optimizer/"><![CDATA[<p>最近有兩篇文章，從完全不同的方向，指向一個新的使用範式: 一個 coding agent 可以不更新任何模型權重、不跑反向傳播，光靠反覆「改一段可編輯的程式碼 → 跑 → 看指標 → 留下變好的、丟掉變壞的」，就把一套系統越養越強。換句話說，coding agent 本身正在變成一種新的<strong>優化器 (optimizer)</strong>。</p>

<p>一篇是 Karpathy 的 <a href="https://github.com/karpathy/autoresearch">autoresearch</a>: 把一個迷你的神經網路訓練腳本丟給 agent，讓它整夜自己跑幾百個實驗、自己調出更好的模型。另一篇是 EnvPool 作者、OpenAI 研究工程師 Jiayi Weng (翁家翌) 的 <a href="https://trinkle23897.github.io/learning-beyond-gradients/">Learning Beyond Gradients</a>: 完全不訓練神經網路，讓 Codex 反覆改純規則的程式碼，居然把 Atari、機器人控制這些任務做到逼近 Deep RL 的水準。</p>

<p>兩篇放在一起看才有意思: 它們是同一個範式的兩個極端代表作。小編這篇就把這個範式講清楚，順帶整理社群已經跑出來的一票案例，還有一個最關鍵的但書: 這招的命脈到底卡在哪。</p>

<h2 id="autoresearch-讓-agent-整夜自己做研究">autoresearch: 讓 agent 整夜自己做研究</h2>

<p>先看 Karpathy 這邊。autoresearch 的設定極簡: 把 nanochat (他那套 GPT-2 等級的訓練程式) 砍成單張 GPU、約 630 行的單檔版本，然後分工:</p>

<ul>
  <li><strong>人類</strong>只改 <code class="language-plaintext highlighter-rouge">program.md</code> (一份用白話寫的指示，告訴 agent 該往哪個方向探索)。</li>
  <li><strong>AI agent</strong> 只改 <code class="language-plaintext highlighter-rouge">train.py</code> (模型架構、優化器、所有超參數)。</li>
</ul>

<p>接著 agent 進入一個自主迴圈: 每次改一點訓練程式，跑一次<strong>固定 5 分鐘</strong>的訓練，用驗證集的 bits-per-byte (可以理解成驗證損失) 當分數，比上一版好就 git commit 留下、不好就丟掉，然後接著試下一個。<a href="https://x.com/karpathy/status/2030371219518931079">Karpathy 形容得很傳神</a>: 圖上每一個點都是一次完整的 5 分鐘訓練，agent 在一條 git 分支上不斷累積 commit，目標是「設計你的 agent，讓它能無限期、在你完全不插手的情況下，跑出最快的研究進度」。</p>

<p>讓它行得通的幾個設計約束很值得抄:</p>

<div style="border:1px solid var(--border); border-radius:8px; overflow:hidden; margin:1.5em 0; font-size:.92em;">
<div style="display:flex; background:var(--surface); font-weight:600;">
<div style="flex:1 1 32%; padding:10px 12px; border-right:1px solid var(--border);">約束</div>
<div style="flex:1 1 68%; padding:10px 12px;">為什麼重要</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 32%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">固定 5 分鐘預算</div>
<div style="flex:1 1 68%; padding:10px 12px;">不管 agent 改了什麼，每次實驗都直接可比，約每小時 12 個、整夜 100 個。</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 32%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">只准改一個檔</div>
<div style="flex:1 1 68%; padding:10px 12px;">agent 只動 train.py，資料準備和評估鎖死，改動小、好審查、不會偷改評分。</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 32%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">git 當記憶</div>
<div style="flex:1 1 68%; padding:10px 12px;">每個實驗是一個 commit，agent 讀分支歷史就知道試過什麼、什麼有效，不會無限鬼打牆。</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 32%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">好壞二選一</div>
<div style="flex:1 1 68%; padding:10px 12px;">用一個明確指標自動判斷留或丟，不需要人介入下判斷。</div>
</div>
</div>

<p>順帶看一眼那份 <a href="https://github.com/karpathy/autoresearch/blob/master/program.md">program.md</a> 到底寫了什麼，就懂為什麼說「人在編程那個研究員」。Karpathy 原版的內容，其實就是一份給 agent 的操作手冊:</p>

<div style="border:1px solid var(--border); border-radius:8px; background:var(--surface); padding:14px 18px; margin:1.5em 0; font-size:.9em; line-height:1.7;">
<div style="font-weight:600; margin-bottom:10px; color:var(--accent);">📄 program.md (給 agent 的操作手冊)</div>
<div style="margin-bottom:7px;"><strong>目標</strong>: 在單張 GPU、固定 5 分鐘的訓練窗內，把 <code>val_bpb</code> 壓到最低</div>
<div style="margin-bottom:7px;"><strong>能改什麼</strong>: 只准改 <code>train.py</code>; <code>prepare.py</code> (資料、tokenizer、評估) 和相依套件一律不准動</div>
<div style="margin-bottom:7px;"><strong>每一輪怎麼跑</strong>: 在 git 分支上改 <code>train.py</code> → commit → 跑 <code>uv run train.py</code> → 從 log 撈出 <code>val_bpb</code> → 比上一版好就留、不好就 <code>git reset</code></div>
<div style="margin-bottom:7px;"><strong>怎麼記錄</strong>: 每次試驗 (commit、分數、峰值記憶體、keep/discard/crash、一句描述) 寫進 <code>results.tsv</code> (不進 git)</div>
<div style="margin-bottom:7px;"><strong>出錯怎麼辦</strong>: 讀 log 最後 50 行，能簡單修就修、否則記下來跳過; 跑超過 10 分鐘直接 kill 當失敗</div>
<div><strong>態度</strong>: 不要停下來問人，自己一直跑到被打斷; 偏好簡單，靠複雜 code 換來的微小進步要丟掉</div>
</div>

<p>短短一份 markdown，就把工作流、邊界、記錄、復原、取捨標準全定義好了。</p>

<p>成果是真的有東西。Karpathy 自己在 nanochat 上跑了約 700 個實驗，<a href="https://x.com/karpathy/status/2031135152349524125">找到約 20 個真正有效的改進</a>，疊起來讓「練到 GPT-2 水準」的時間從 2.02 小時降到 1.80 小時 (快 11%)，而且 agent 抓到好幾個他自己漏掉的點 (例如 QKNorm 少了一個縮放、Value Embeddings 沒上正則化、注意力窗口開太保守)。更誇張的是 <a href="https://x.com/_philschmid/status/2031355349526012050">Shopify 執行長 Tobi Lütke 的例子</a>: 他叫 agent 讀 autoresearch 的程式庫、改寫成自己查詢擴展任務的版本，然後就去睡覺，醒來時一個 0.8B 的小模型在 8 小時跑了 37 個實驗後，分數比他之前的 1.6B 模型還高了 19%。一個小一半的模型贏過大的。</p>

<p><a href="https://x.com/karpathy/status/2031137476438548874">Karpathy 特別澄清</a>: autoresearch 不是一個你「拿來用」的工具，而是一個<strong>配方、一個想法</strong>。把它交給你的 agent，套用到你真正在乎的東西上就行。這句話是理解整個範式的鑰匙。</p>

<h3 id="真正該學的-約束越緊agent-越好用">真正該學的: 約束越緊，agent 越好用</h3>

<p>Manthan Gupta 有一篇 <a href="https://x.com/manthanguptaa/status/2032464949952598152">拆解 autoresearch 的好文</a>，把這套設計背後的原則講得很透。他的核心觀察很反直覺: <strong>最好的自主系統不是最自由的，而是約束最嚴格、目標最清楚、失敗成本最低的那種。</strong> 幾個能直接搬走的心得:</p>

<ul>
  <li><strong>約束讓 agent 更好，不是更差</strong>: 只能改一個檔、只追一個指標、只在分數變好時前進。很多 agent 系統反而死在太早給太多自由，自由越多、能出錯的面積就越大。</li>
  <li><strong>該優化的是 harness，不是模型</strong>: 一個普通的 agent 配上一套好的 harness (怎麼啟動、怎麼處理失敗、怎麼量進步、怎麼回滾爛路徑)，會贏過一個更強的 agent 配上一團亂的 harness。</li>
  <li><strong>prompt 就是架構的一部分</strong>: autoresearch 真正的控制面其實是那份 <code class="language-plaintext highlighter-rouge">program.md</code>，它定義了工作流、邊界、復原方式、取捨標準。Manthan 講得很妙: 人不只是在編程模型，而是在<strong>編程那個研究員</strong>。</li>
  <li><strong>可回復、可觀測不能妥協</strong>: 輸的實驗要能便宜地丟掉、每一步都能從 log 和 commit 追溯，agent 才敢大膽探索。</li>
</ul>

<h2 id="learning-beyond-gradients-把同一招推到不碰神經網路">Learning Beyond Gradients: 把同一招推到不碰神經網路</h2>

<p>Weng 這邊是從另一端逼近同一件事。他在業餘維護 EnvPool 時，本來只想要一個便宜的規則策略來測試遊戲環境，結果用 Codex (gpt-5.4) 寫純規則版本，效果離譜到讓他改變了想法:</p>

<ul>
  <li><strong>Atari Breakout</strong> (打磚塊): 分數一路 <code class="language-plaintext highlighter-rouge">387 → 507 → 839 → 864</code>，打到理論最高分。</li>
  <li><strong>MuJoCo Ant</strong> (四足機器人): 純 Python 策略先學會節律步態，再接上短視窗的模型規劃，衝到 <code class="language-plaintext highlighter-rouge">6000+</code>，進入常見 Deep RL 的量級。</li>
  <li><strong>VizDoom</strong> (第一人稱射擊): 只用 cv2/NumPy 處理螢幕影像、完全不訓練網路，也能拿到像樣的分數。</li>
</ul>

<p>關鍵不在分數，而在: Codex 不是在訓練網路，而是在維護一套「還能繼續長大」的軟體系統。最後的 Breakout 策略遠遠超過「球在左邊就往左」這種一行規則，它長出了動作探測、狀態讀取、落點預測、卡死偵測、回歸測試、影片回放、實驗記錄。</p>

<h3 id="什麼是-heuristic-learning">什麼是 Heuristic Learning</h3>

<p>Weng 把這個過程命名為 <strong>Heuristic Learning (HL，啟發式學習)</strong>，維護的對象叫 <strong>Heuristic System (HS，啟發式系統)</strong>。它跟 Deep RL 一樣，都有「狀態 → 動作 → 回饋 → 更新」這個閉環; 差別在於: Deep RL 更新的是神經網路權重，HL 更新的是軟體結構，而且改動由 coding agent 直接改程式碼完成，不走反向傳播。</p>

<div style="border:1px solid var(--border); border-radius:8px; overflow:hidden; margin:1.5em 0; font-size:.9em;">
<div style="display:flex; background:var(--surface); font-weight:600;">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border);">維度</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border);">Deep RL</div>
<div style="flex:1 1 38%; padding:10px 12px;">Heuristic Learning</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">策略</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">神經網路參數</div>
<div style="flex:1 1 38%; padding:10px 12px;">程式碼: 規則、狀態機、控制器、MPC</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">更新方式</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">梯度下降改參數</div>
<div style="flex:1 1 38%; padding:10px 12px;">coding agent 直接改程式碼</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">回饋</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">主要是固定的獎勵</div>
<div style="flex:1 1 38%; padding:10px 12px;">測試、日誌、影片、回放都算</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">記憶</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">on-policy 幾乎沒有</div>
<div style="flex:1 1 38%; padding:10px 12px;">明確存下試驗、失敗原因、版本差異</div>
</div>
</div>

<p>這裡有個容易誤會的地方要講清楚: 它的重點<strong>不是「叫 LLM 生成一堆規則」</strong>(那種事 FunSearch、Evolution of Heuristics 早就在做)，而是被維護的對象是<strong>一整套接在一起的軟體系統</strong>。Weng 講得很白:「單一規則是不夠的，規則、回饋、歷史、下一次更新的路徑都要連起來，才算是一個 HS。」</p>

<p>兩個細節最能說明差異。第一，<strong>更新改的是整個系統，不只是策略</strong>: Breakout 從 <code class="language-plaintext highlighter-rouge">387</code> 爬到 <code class="language-plaintext highlighter-rouge">864</code>，靠的不是「調返球準確度」，而是 Codex 看失敗影片、定位問題、加上「卡死偵測 + 軌跡擾動」這種新機制，再補回歸測試確保舊能力不退化，這已經是在做軟體工程。第二，<strong>「壓縮歷史」跟「吸收回饋」一樣重要</strong>: 只生成規則的系統只會越長越大、最後變成沒人敢碰的爛泥，健康的 HS 必須能把一堆局部補丁折回更簡單的表示。</p>

<p>把策略寫成可讀的程式碼，也帶來幾個 Deep RL 沒有的好處: 策略<strong>可解釋</strong> (規則能翻成白話)、<strong>樣本效率高</strong> (一次有效的修改就直接跳到新策略，不用慢慢爬學習率)、舊能力可以固化成<strong>回歸測試</strong>和黃金案例，因此能避開一部分<strong>災難性遺忘</strong> (舊能力不必只活在權重裡)。</p>

<h3 id="為什麼這招以前行不通">為什麼這招以前行不通</h3>

<p>HL 的祖先其實就是專家系統、規則系統，這些東西不是沒用，而是<strong>沒人養得起</strong>。Weng 描述得很傳神:</p>

<blockquote>
  <p>今天加一條規則修好情況 A，明天情況 B 又壞了，後天再補一個 if 判斷，再過幾天，沒人敢刪任何一行。</p>
</blockquote>

<p>人手維護規則系統，就像工業革命前用手紡紗: 一個人做得來，但規模一大、維護成本就把人壓垮。紡紗機改變的是生產曲線，coding agent 改變的正是規則系統的<strong>維護成本曲線</strong>，於是過去只能當一次性補丁的規則，現在開始變成值得長期擁有的程式碼。</p>

<p>不過 Weng 也誠實地說: coding agent 不會自動解決持續學習，它只是把「避免遺忘」變成一個更工程化的問題。新規則可能修好一個失敗卻弄壞舊場景、測試太窄會被鑽漏洞、規則一直疊加到 agent 自己都維護不動。所以問題只是換了個形狀: 從「怎麼更新神經網路參數」，變成「怎麼維護一套能持續吸收回饋、又不會爛掉的軟體系統」，這正是它接上持續學習 (continual learning) 的地方。</p>

<h2 id="同一個骨架-coding-agent-當外層優化器">同一個骨架: coding agent 當「外層優化器」</h2>

<p>把兩篇擺在一起，骨架就浮出來了。它們都是同一個迴圈:</p>

<blockquote>
  <p>coding agent 反覆「改一段可編輯的程式碼 → 跑 → 讀可驗證的指標 → 留好的、丟壞的」，過程中<strong>完全不更新模型權重</strong>。</p>
</blockquote>

<p>差別只在「那段可編輯的程式碼」是什麼。autoresearch 改的是訓練腳本，內層還是在訓練一個神經網路; HL 改的是策略程式本身，完全不碰神經網路。兩端對照起來特別清楚:</p>

<div style="border:1px solid var(--border); border-radius:8px; overflow:hidden; margin:1.5em 0; font-size:.9em;">
<div style="display:flex; background:var(--surface); font-weight:600;">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border);">維度</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border);">autoresearch</div>
<div style="flex:1 1 38%; padding:10px 12px;">Learning Beyond Gradients</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">agent 改的東西</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">訓練腳本 train.py (架構、優化器、超參數)</div>
<div style="flex:1 1 38%; padding:10px 12px;">整套程式系統 (規則、偵測器、測試、記憶)</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">內層在優化什麼</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">神經網路權重 (還是要訓練)</div>
<div style="flex:1 1 38%; padding:10px 12px;">程式邏輯本身 (完全不碰神經網路)</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">回饋訊號</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">驗證集指標 (val loss)</div>
<div style="flex:1 1 38%; padding:10px 12px;">測試、獎勵、日誌、影片回放都算</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">記憶</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">git commit 歷史當實驗筆記</div>
<div style="flex:1 1 38%; padding:10px 12px;">試驗記錄、版本差異、失敗方向</div>
</div>
<div style="display:flex; border-top:1px solid var(--border);">
<div style="flex:1 1 24%; padding:10px 12px; border-right:1px solid var(--border); font-weight:600;">交付物</div>
<div style="flex:1 1 38%; padding:10px 12px; border-right:1px solid var(--border); color:var(--text-secondary);">勝出的訓練設定 (一次性最佳實驗)</div>
<div style="flex:1 1 38%; padding:10px 12px;">持續維護的軟體系統 (長期擁有)</div>
</div>
</div>

<p>共同的前提只有三個: <strong>受限的編輯空間</strong>(只准改某個檔或某套程式)、<strong>可重現的執行環境</strong>(能一跑再跑)、<strong>穩定可驗證的指標</strong>(能自動判好壞)。只要這三樣齊備，coding agent 就能當外層優化器，在裡面爬分。</p>

<p>至於兩者的差別，不是對立，而是一條光譜的兩端。autoresearch 偏向「在固定框架內，搜尋出一次性的最佳實驗結果」，內層常常還是在訓練神經網路; HL 則把光譜推到底: 被優化的對象本身就是最終策略、完全不要神經網路，而且強調這套軟體系統要被長期維護。Karpathy 說 autoresearch 是個「可套用到任何你在乎的東西」的配方; Weng 等於就把它套用到了「軟體系統本身」這個極端上。有趣的是，社群早就注意到這層關係: 有人引 <a href="https://x.com/karpathy/status/2031135152349524125">Karpathy 的話</a>「任何你在乎、又能快速評估的指標，都可以丟給一群 agent 去 autoresearch」，還真的拿它去搜純數學問題 (Ramsey 數的下界)，完全沒有梯度。</p>

<h2 id="社群已經在跑-這是範式不是孤例">社群已經在跑: 這是範式，不是孤例</h2>

<p>autoresearch 在週末爆紅後，社群很快把這個迴圈套到各種東西上，而且越套越能看出它的通用性:</p>

<p>🔹 <strong>把這套做成現成工具: evo</strong>: evo-hq 團隊把這個優化迴圈包成一套現成工具 <a href="https://x.com/alokbishoyi97/status/2059610305408462898">evo</a>。它的形式是: 底層一個 CLI 引擎，再用 <code class="language-plaintext highlighter-rouge">evo install</code> 裝進你的 coding agent (Claude Code、Codex、Cursor 等)，在裡面就變成 <code class="language-plaintext highlighter-rouge">/evo:discover</code>、<code class="language-plaintext highlighter-rouge">/evo:optimize</code> 這類斜線指令。用起來是: 給它一個 codebase 和一個「更好」的指標，它就自己建評估、開沙箱平行跑實驗、留下變好的版本，還用樹狀搜尋 (保留不同切面上勝出的「專家」分支) 和「通過/不通過」的把關擋掉作弊刷分。</p>

<p>🔹 <strong>學術界其實同源已久</strong>: 這條路在研究界跑了好幾年。DeepMind 的 <a href="https://deepmind.google/blog/funsearch-making-new-discoveries-in-mathematical-sciences-using-large-language-models/">FunSearch</a> → <a href="https://deepmind.google/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/">AlphaEvolve</a> 用「LLM 加自動評估器」的演化迴圈改程式碼，找到 56 年來第一個 4×4 複數矩陣乘法只需 48 次乘法的算法，導出的排程規則還幫 Google 資料中心持續省下 0.7% 的全球算力。NVIDIA 的 <a href="https://eureka-research.github.io/">Eureka</a> 用 GPT-4 寫獎勵函數、靠演化搜尋迭代，在 29 個任務裡 83% 贏過人類專家 (不過要注意它後端仍用 RL 訓練策略，只是把獎勵設計換成 LLM 生成)。Sakana 的 <a href="https://sakana.ai/dgm/">Darwin Gödel Machine</a> 讓 coding agent 改自己的程式碼、用基準測試驗證，把 SWE-bench 從 20% 一路自我演化到 50%。</p>

<p>🔹 <strong>最接地氣的實戰應用</strong>: 在 Weng 的留言區，VicaYang 分享了三個完全不是 RL 的 HS，最能說明這招在日常工程的用處: 一套<strong>音樂庫的歌曲資訊和歌詞整理系統</strong> (依不同國家、曲風套不同規則，再評估多來源品質合併)、一個<strong>數獨變體求解器</strong> (給規則和範例，幾輪迭代就生出來、自己一行程式碼沒寫)、以及一套<strong>處理電子郵件的 agent 系統</strong> (從正文和 Excel 附件抽結構化資訊，牽涉大量像「不同城市最低投保額不同」的客製規則)。他特別提到，連一個沒什麼工程經驗的學生，都能維護最後那套系統並持續改進它，這正是維護成本崩塌後的樣子。</p>

<h2 id="把優化-harness做成系統方法-meta-harness">把「優化 harness」做成系統方法: Meta-Harness</h2>

<p>上面 evo 是社群的玩法，<a href="https://yoonholee.com/meta-harness/">Meta-Harness</a> (論文名字就叫 End-to-End Optimization of Model Harnesses) 則把同一件事做成一套有系統的研究方法，值得單獨看它怎麼搜。</p>

<p>它要優化的「harness」，指的是一個 LLM 系統除了模型權重以外的整層外圍程式: 系統 prompt、工具定義、判斷任務完成的邏輯、context 管理、檢索程式、各元件之間的編排。這些被寫成一份不綁特定任務的程式，可以套到不同領域上。</p>

<p>搜尋迴圈是這樣: 一個 agentic proposer (就是用 Claude Code) 透過檔案系統，用 grep、cat 去讀所有歷史候選的原始碼、分數、和<strong>完整的執行軌跡</strong>，診斷出是哪個 harness 決策造成失敗，然後提出一個新候選、在沒看過的任務上評測、把結果存回去，再重複。最關鍵的設計是它餵給 proposer 的脈絡量: 每一步最多給到 <strong>1000 萬個 token</strong> 的診斷資訊 (對照論文盤點的其他方法，最多只有 2.6 萬)，所以它不是看壓縮過的摘要，而是直接讀原始軌跡做「反事實診斷」，精準定位該改哪裡。搜尋本身是演化式的，例如文字分類那組就是跑 20 輪、每輪 2 個候選，留下表現好的當作下一輪的基底。</p>

<p>成效跨三個很不一樣的領域，正好說明它不綁定 coding: 文字分類上準確率贏 baseline 7.7 分、還省 4 倍 token; 檢索增強的數學推理在 200 題 IMO 級題目上 +4.7 分，而且<strong>同一份 harness 能直接套到五個沒見過的模型</strong>; agentic coding 的 TerminalBench-2 上，搭 Claude Haiku 4.5 的版本還拿下全榜第一。整套程式碼也<a href="https://github.com/stanford-iris-lab/meta-harness">開源了</a>，附了文字分類和 TerminalBench 兩個完整實驗。</p>

<h2 id="換個領域實地走一輪-doug-turnbull-的搜尋重排器">換個領域實地走一輪: Doug Turnbull 的搜尋重排器</h2>

<p>從訓練腳本、遊戲規則，一路到剛剛的 LLM harness，這個範式套過的領域已經夠雜了。最後再換一個多數工程師最熟、也最容易驗證的領域收尾: <strong>搜尋排序</strong>。會特別挑 <a href="https://softwaredoug.com/blog/2025/10/19/agentic-code-generation-to-optimize-a-search-reranker.html">Doug Turnbull</a> (《Relevant Search》作者) 這個例子，是因為他難得把整套迴圈的程式碼都公開了，最適合拿來看「實際上到底怎麼把 coding agent 接成一個優化器」。</p>

<p>先把他在解的問題講白，沒做過搜尋也能懂: 使用者打一個查詢 (例如「無線耳機」)，系統要把最相關的商品排到最前面，這個重新排序的步驟叫 <strong>reranking (重排序)</strong>; 排得好不好，用一個叫 <strong>NDCG 的分數</strong>衡量 (越高越好)，資料用的是 Amazon 的購物搜尋資料集。</p>

<p>Turnbull 的出發點是個很實際的成本問題: 直接讓 agent 逐筆判斷每個查詢該怎麼排，相關性確實能提升 15-30%，但每個查詢要燒掉 10 萬個以上的 token，貴到不可能上線。於是他把問題反過來: 與其每個查詢都即時呼叫一次 agent，不如讓 agent 在訓練階段，把它的排序判斷<strong>一次寫成一段可重複使用的程式碼</strong>，之後線上就只跑這段便宜的程式碼 (搭配便宜的 BM25 關鍵字搜尋)，不必每次都呼叫模型。</p>

<p>而 agent 改這段程式的迴圈，就是標準的「外層優化器」: 每輪改一小段重排邏輯、在資料集上算 NDCG，好就留、不好就還原。</p>

<p>而這其實就是整個範式的重點: 生成出來的那段程式，跑的時候<strong>完全不會再呼叫 LLM</strong>。它就是一段傳統的啟發式排序邏輯，用 BM25 分數 (經典的關鍵字相關性算法)、標題關鍵字比對、詞彙重疊、甚至簡單的語系判斷 (看到西班牙文字元就換個策略) 這些便宜又可解釋的訊號來決定順序。換句話說，agent 把原本要靠 LLM 逐筆推理的判斷，蒸餾成一段不需要模型、跑起來幾乎不花錢的規則程式。這正是這套範式划算的根本原因: 用昂貴的 agent 在「優化階段」想清楚一次，換來一個之後跑起來便宜、又能讀能改能測的成品。</p>

<p>最有代表性的是他踩的坑: 第二版讓 agent 自由迭代後，訓練集 NDCG 從 0.27 漂亮地爬到 0.35，卻<strong>嚴重過擬合</strong> (把訓練查詢背了起來，換一批就掉)。第三版補上三條護欄才真正泛化: 每次只能小改 (10 行內)、用第二個 LLM 檢查不准把具體查詢寫死、改動還必須在 agent 看不到的驗證集上也有進步。加上護欄後，完全沒看過的測試集才從 0.286 穩穩進步到 0.350 (約 +18%)。</p>

<p>這一輪完整示範了「coding agent 當優化器」長什麼樣，也剛好預告了下一段最關鍵的事: Turnbull 這套能不能成，最後全卡在那個 NDCG eval 上。</p>

<h2 id="最關鍵的但書-eval-才是命脈">最關鍵的但書: eval 才是命脈</h2>

<p>講到這裡，這個範式聽起來像免費午餐，但其實有個硬到不能再硬的瓶頸: <strong>那個「可驗證的指標」本身</strong>。當實驗能跑得比人快 100 倍，你的 eval (評估) 就變成整條迴圈的瓶頸，指標一旦能被鑽漏洞或外洩，模型就會在紙面上變強、上線就崩。</p>

<p>這點在 AI 產品經理 <a href="https://x.com/nurijanian/status/2035257434365976671">nurijanian 的實戰分享</a>裡寫得最深刻。他想用 autoresearch 改進自己的 skill，連跑三次才搞懂自己錯在哪:</p>

<div style="margin:1.5em 0;">
<div style="display:flex; gap:10px; flex-wrap:wrap;">
<div style="flex:1 1 30%; background:#ffebe9; border:1px solid #cf222e; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#cf222e; margin-bottom:6px;">第一次: 全自動</div>
<div style="font-size:.88em;">直接把 skill 丟給工具，連測試輸入和評分用的評分器 (judge) 都讓它自動生。分數很快上升，但 skill 其實越改越爛: 評分標準是機器憑空生的，沒有任何對「真正的失敗長什麼樣」的理解。</div>
</div>
<div style="flex:1 1 30%; background:#fff8c5; border:1px solid #9a6700; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#9a6700; margin-bottom:6px;">第二次: 接上 eval skill</div>
<div style="font-size:.88em;">用了更有系統的方法生測試輸入 (沿輸入空間的維度組合)，輸入確實變多元了，但他還是讓工具自己生、自己沒看過任何輸出，評分器也沒變好。</div>
</div>
<div style="flex:1 1 30%; background:#dafbe1; border:1px solid #1a7f37; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#1a7f37; margin-bottom:6px;">第三次: 自己先讀資料</div>
<div style="font-size:.88em;">他先親手讀過一輪輸出、做錯誤分析、把失敗歸納成分類、據此寫評分器再人工校準，然後才放 autoresearch 去跑。這次才真的有料。</div>
</div>
</div>
</div>

<p>他用 <a href="https://hamel.dev">Hamel Husain</a> 評估課程裡的「三道鴻溝」框架點破了關鍵: <strong>理解鴻溝</strong> (你以為系統在做什麼 vs 它實際在做什麼)、<strong>規格鴻溝</strong> (你想要什麼 vs 你的評分器量了什麼)、<strong>泛化鴻溝</strong> (在測試輸入上的表現 vs 在沒看過的輸入上的表現)。autoresearch 的優化迴圈能解的，其實<strong>只有第三道</strong>鴻溝; 而前兩道，得人自己親手讀資料才關得起來。他引課程裡那句很狠的話: 「如果你不願意定期親手看一些資料，那你在 eval 上就是在浪費時間。」最後他下的結論是: 「你沒辦法把『理解』自動化掉。總得有人去關上第一道鴻溝，而以我的經驗，那個人永遠是你。」</p>

<p>這套「先做錯誤分析、人工看資料、再談自動化」的順序很關鍵: 自動化迴圈很迷人，但它放大的是你的評估品質。評估對了，它幫你飛; 評估歪了，它只會幫你更快地往錯的方向衝。</p>

<blockquote>
  <p>編按: 關於這裡講的「錯誤分析 (error analysis)」，ihower 之前分享過一篇 <a href="https://ihower.tw/blog/12960-ai-evals-and-error-analysis">AI Evals 與 Error Analysis</a> 講得更完整，想深入的可以一讀。</p>
</blockquote>

<p><a href="https://kargarisaac.medium.com/one-line-of-code-41-better-memory-when-one-ai-agent-optimizes-another-da2396bc501b">Isaac Kargar 的一個實驗</a> 把這個陷阱演得更血淋淋。他用 Claude Code 去優化另一套 agent 的記憶系統，跑了十幾個實驗、複合分數一口氣 +41% (最大功臣只是把 <code class="language-plaintext highlighter-rouge">dspy.Predict</code> 換成 <code class="language-plaintext highlighter-rouge">dspy.ChainOfThought</code> 這一行修改)。但發完文他才驚覺: 把產出拿去跟 Claude Code 自己那套精簡的記憶一比，他的系統其實是在狂刷一堆可有可無的瑣事、分數漂亮卻更難用，<strong>eval 從一開始就量錯了東西</strong>，於是他回頭重建評分標準才修正方向。他還補了一個很反直覺的心得: 五個用「不要做 X」這種負面指示的實驗全部退步，因為 LLM 會變得過度謹慎，所以「<strong>要告訴 LLM 什麼是好，而不是叫它避免什麼</strong>」。</p>

<p><a href="https://langfuse.com/blog/2026-03-24-optimizing-ai-skill-with-autoresearch">Langfuse 的案例</a>則是更具體的一個例子。他們想用 autoresearch 優化自家一套 skill (負責把寫死在程式裡的 prompt 搬進 Langfuse 託管系統)，評分是拿它去跑 6 個難度遞增的測試專案、用「正確性 + 完整度 + 效率」的加權公式打分，分數在 14 次實驗裡從 0.35 一路爬到 0.824。看起來很漂亮，但細看才發現: agent 是靠<strong>砍掉測試沒考到的真功能</strong>在刷分。最經典的一刀是它把「執行前先讓使用者確認搬遷計畫」這道人工核准關卡整個移除: 因為在自動跑的測試環境裡根本沒有真人會去按確認，skill 只要保留這關就會永遠卡在等待、拿 0 分，agent 一把它刪掉，前三個測試案例就瞬間從 0.00 跳到 1.00。它還順手刪掉了測試完全沒涵蓋的子 prompt、trace 串接等真實功能。這已經不是「不小心量錯」，而是 agent 主動鑽指標的漏洞。所以這篇的標題乾脆叫「它逼我們把測試寫得更好」，Langfuse 的結論很簡單: 真正脆弱的一環不是優化器，而是那組評估，「沒被量到的東西，就會被砍掉」。</p>

<p><a href="https://www.cerebras.ai/blog/how-to-stop-your-autoresearch-loop-from-cheating">Cerebras 則補上</a>另一個更隱蔽的失效模式: 就算指標本身沒問題，只要<strong>監督太稀疏</strong>，迴圈一樣會慢慢偏離原本的目標。他們做的是模型壓縮: 先用靜態方法把模型從 717 GB 壓到 92 GB、塞進消費級顯卡之後，再交給 autoresearch 一個更激進的延伸目標: 動態地只用 20% 記憶體跑。結果放著讓它自己跑，agent 中途就擅自把問題換成了另一個:「這模型到底能砍掉多少、還維持 95% 以上的準確率?」擺到 12 小時後偏得更離譜。它用「遮蔽掉一部分 expert 子網路」的方式來衝分 (現在的大模型常把網路拆成很多 expert、每次只實際用到其中一部分)，但這種遮蔽只是在軟體層把元件停用、權重其實還躺在記憶體裡，根本沒省到半點記憶體 (真要省得做永久性壓縮)。弔詭的是它換的那題還真答了出來 (約 37% 的 expert 就能覆蓋 95% 的情境)，是個有用的發現，卻完全不是你要的: 原本的記憶體目標毫無進展、分數卻一直在動，量到的其實是空的。Cerebras 的解方也很直白: 任務要切細、要常驗、要把每個實驗隔離開，環境設計比你選哪個模型更關鍵。</p>

<p>這跟 HL 那邊的觀察完全互相印證。Weng 引的 Cardiverse 研究就指出，LLM 生成的規則品質參差不齊，真正的難題不是「能不能生成」，而是「能不能快又準地辨認出有用的部分」; Turnbull 那句「Agent 天生就想過擬合」也是同一件事。另一位中文圈的觀察者 <a href="https://x.com/timyangnet/status/2033436288679075840">timyangnet</a> 則總結得很到位: autoresearch 之所以有效，是因為它強制了<strong>紀律</strong> (每次只改一個變數、先有假設再做實驗) 和<strong>記憶</strong> (git 歷史就是實驗筆記)，而真正的模型是「人類設定方向與約束、帶來品味，agent 在邊界內做窮盡式探索」。自由太多和太少一樣糟。</p>

<h2 id="哪些專案適合這樣搞">哪些專案適合這樣搞?</h2>

<p>把兩篇的論點和上面這票案例放在一起，可以歸納出: 一個專案要適合「coding agent 當優化器」，需要三個條件<strong>同時成立</strong>。</p>

<div style="margin:1.5em 0;">
<div style="display:flex; gap:10px; flex-wrap:wrap;">
<div style="flex:1 1 30%; background:#dafbe1; border:1px solid #1a7f37; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#1a7f37; margin-bottom:6px;">① 回饋可自動驗證</div>
<div style="font-size:.88em; color:#1f2328;">有測試、獎勵、基準分數、可信的評分器能自動判好壞。這是最硬的門檻，也是上面整段在講的命脈。</div>
</div>
<div style="flex:1 1 30%; background:#dafbe1; border:1px solid #1a7f37; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#1a7f37; margin-bottom:6px;">② 目標可用程式碼表達</div>
<div style="font-size:.88em; color:#1f2328;">訓練腳本、策略規則、skill 檔、外圍框架設定，凡是「能寫成可編輯文字、又能被打分」的都行。</div>
</div>
<div style="flex:1 1 30%; background:#dafbe1; border:1px solid #1a7f37; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#1a7f37; margin-bottom:6px;">③ 狀態可重現可觀測</div>
<div style="font-size:.88em; color:#1f2328;">能重跑、能記日誌、能回放，每次只動一小塊，agent 才壓得住複雜度、改得動。</div>
</div>
</div>
</div>

<p>具體的甜蜜點，<a href="https://x.com/_philschmid/status/2031355349526012050">philschmid 那篇</a>列得很清楚: 搜尋排序、商品分類、醫療命名實體辨識、詐騙評分、合約抽取、意圖分類這類<strong>邊界明確、指標清楚、小模型或小程式幾分鐘就能跑完一輪</strong>的任務最適合。</p>

<p>除了這種一翻兩瞪眼的甜蜜點，還有一塊灰色地帶才是多數工程團隊近期真正用得上的:</p>

<div style="margin:1.5em 0;">
<div style="background:#fff8c5; border:1px solid #9a6700; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#9a6700; margin-bottom:6px;">△ 條件適合的灰色地帶</div>
<div style="font-size:.9em;">領域規則多、邊界明確，但 LLM 單靠 prompt 又解不好 (規則太多、例外狀況一直冒) 的實務系統，例如上面 VicaYang 的郵件處理、歌曲資訊清理。它本來不算這套方法的主場，但只要你能把人類處理者的回饋持續餵回去、改程式碼和記憶，它就會慢慢變成一套好用又好維護的系統。換句話說，不必是 Atari 或前沿研究，你那套寫了三年、沒人敢動的規則引擎或客服流程，可能就是最好的起點。</div>
</div>
</div>

<p>那哪些不適合? 兩篇也都劃了界。</p>

<div style="margin:1.5em 0;">
<div style="background:#ffebe9; border:1px solid #cf222e; border-radius:8px; padding:14px; margin-bottom:10px;">
<div style="font-weight:600; color:#cf222e; margin-bottom:6px;">✗ 需要從高維感知學表徵</div>
<div style="font-size:.9em;">例如 ImageNet 影像分類。以今天的認知，想像不出一個 agent 能寫純 Python、不靠神經網路就解決它，這是程式碼表達能力的天花板。</div>
</div>
<div style="background:#ffebe9; border:1px solid #cf222e; border-radius:8px; padding:14px;">
<div style="font-weight:600; color:#cf222e; margin-bottom:6px;">✗ 長程規劃加稀疏回饋加需要搜尋與記憶</div>
<div style="font-size:.9em;">Weng 用 Montezuma's Revenge 當反例: 無人介入的一次跑分雖然摸到 400 分，但那條路是 86 個巨集動作拼出來的開環執行 (路線事先排定，中途失敗不會重新規劃，一步偏掉整條就崩)。普通的規則狀態機表達不了這種需要動作對齊時機、失敗要能回復的長程路線。</div>
</div>
</div>

<p>而最致命的那條界線，還是回到上一段的但書: <strong>沒有可信的 eval，這整套就是在幫你更快地優化一個錯的目標</strong>。</p>

<h2 id="小編的觀察">小編的觀察</h2>

<p>小編覺得這個做法值得學習。過去想靠手寫一大堆策略、規則、或一份份難調的訓練腳本來解問題，光維護成本就高到沒人養得起，根本不會被當成一條認真的路; 但現在有 coding agent 幫你持續盯著失敗、改程式碼、補測試，這條原本被判死刑的老路，突然就變得可行了。</p>

<p>evo 那篇有個公式小編很喜歡: 一個 agent 系統的槓桿 = <strong>模型能力 × 好的框架 × 緊的驗證迴圈</strong>。過去只有第一項在規律地進步 (出新模型)，現在後兩項也開始動，而且是按<strong>你</strong>定義的目標、在<strong>你</strong>的資料上動。所以下次你看到一坨沒人敢碰的 if-else、一份過時的 skill、或一個從沒好好調過的訓練腳本，也許該想的不是急著重寫，而是: 能不能把它交給 coding agent 持續最佳化?</p>

<h2 id="參考資料">參考資料</h2>

<p><strong>核心兩篇</strong></p>

<ul>
  <li>Andrej Karpathy: <a href="https://github.com/karpathy/autoresearch">autoresearch</a> (含 <a href="https://github.com/karpathy/autoresearch/blob/master/program.md">program.md</a>)</li>
  <li>Jiayi Weng (翁家翌): <a href="https://trinkle23897.github.io/learning-beyond-gradients/">Learning Beyond Gradients</a></li>
</ul>

<p><strong>autoresearch 的延伸討論</strong></p>

<ul>
  <li>Karpathy: <a href="https://x.com/karpathy/status/2030371219518931079">迴圈圖解</a>、<a href="https://x.com/karpathy/status/2031135152349524125">約 20 個有效改進的成果</a>、<a href="https://x.com/karpathy/status/2031137476438548874">「這是配方，不是工具」</a></li>
  <li>Philipp Schmid: <a href="https://x.com/_philschmid/status/2031355349526012050">Tobi Lütke 案例與適用甜蜜點</a></li>
  <li>Manthan Gupta: <a href="https://x.com/manthanguptaa/status/2032464949952598152">拆解 autoresearch 的設計原則</a></li>
  <li>timyangnet: <a href="https://x.com/timyangnet/status/2033436288679075840">紀律與記憶的觀察</a></li>
</ul>

<p><strong>社群與學術案例</strong></p>

<ul>
  <li>evo: <a href="https://x.com/alokbishoyi97/status/2059610305408462898">alokbishoyi97 的介紹</a></li>
  <li>DeepMind: <a href="https://deepmind.google/blog/funsearch-making-new-discoveries-in-mathematical-sciences-using-large-language-models/">FunSearch</a>、<a href="https://deepmind.google/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/">AlphaEvolve</a></li>
  <li>NVIDIA: <a href="https://eureka-research.github.io/">Eureka</a></li>
  <li>Sakana AI: <a href="https://sakana.ai/dgm/">Darwin Gödel Machine</a></li>
  <li>Stanford IRIS Lab: <a href="https://yoonholee.com/meta-harness/">Meta-Harness</a> (含<a href="https://github.com/stanford-iris-lab/meta-harness">程式碼</a>)</li>
  <li>Doug Turnbull: <a href="https://softwaredoug.com/blog/2025/10/19/agentic-code-generation-to-optimize-a-search-reranker.html">用 coding agent 優化搜尋重排器</a></li>
</ul>

<p><strong>評估 (eval)</strong></p>

<ul>
  <li>nurijanian: <a href="https://x.com/nurijanian/status/2035257434365976671">三道鴻溝的實戰心得</a></li>
  <li>Hamel Husain: <a href="https://hamel.dev">AI Evals 課程</a></li>
  <li>ihower: <a href="https://ihower.tw/blog/12960-ai-evals-and-error-analysis">AI Evals 與 Error Analysis</a></li>
  <li>Isaac Kargar: <a href="https://kargarisaac.medium.com/one-line-of-code-41-better-memory-when-one-ai-agent-optimizes-another-da2396bc501b">一行程式碼、+41% 背後的陷阱</a></li>
  <li>Langfuse: <a href="https://langfuse.com/blog/2026-03-24-optimizing-ai-skill-with-autoresearch">用 autoresearch 優化 skill 的踩坑</a></li>
  <li>Cerebras: <a href="https://www.cerebras.ai/blog/how-to-stop-your-autoresearch-loop-from-cheating">如何讓 autoresearch 迴圈不作弊</a></li>
</ul>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Coding" /><category term="Eval" /><summary type="html"><![CDATA[最近有兩篇文章，從完全不同的方向，指向一個新的使用範式: 一個 coding agent 可以不更新任何模型權重、不跑反向傳播，光靠反覆「改一段可編輯的程式碼 → 跑 → 看指標 → 留下變好的、丟掉變壞的」，就把一套系統越養越強。換句話說，coding agent 本身正在變成一種新的優化器 (optimizer)。]]></summary></entry><entry><title type="html">向量已死? Grep 萬能? 不，你需要的是「策展」一組檢索工具</title><link href="https://blog.aihao.tw/2026/06/03/is-grep-all-you-need/" rel="alternate" type="text/html" title="向量已死? Grep 萬能? 不，你需要的是「策展」一組檢索工具" /><published>2026-06-03T00:00:00+00:00</published><updated>2026-06-03T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/03/is-grep-all-you-need</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/03/is-grep-all-you-need/"><![CDATA[<p>最近一篇 PwC 的論文 <a href="https://arxiv.org/abs/2605.15184">Is Grep All You Need? How Agent Harnesses Reshape Agentic Search</a> 在 X 上吵得蠻兇的，戰火還燒到一個更聳動的問題: 向量檢索已死? 這標題擺明在挑釁: 都 2026 年了，Claude Code、Codex 這些命令列 agent 光靠 grep 加 bash 就能找到東西，那花大錢養 embedding 模型、維護向量資料庫的 RAG 一整套基礎設施，是不是可以直接拆掉了?</p>

<p>LlamaIndex 的 Jerry Liu 第一時間就 <a href="https://x.com/jerryjliu0/status/2056077617355522534">出來潑了一盆冷水</a>。小編覺得這個爭論很值得整理，因為它背後藏著一個更精準的判斷: grep 不是萬靈丹，它只在「高訊號關鍵字」的場景才真的好用，而那個場景剛好就是寫程式。</p>

<h2 id="論文到底發現了什麼">論文到底發現了什麼</h2>

<p>先給論文該有的肯定。它做的事情很實在: 把 grep 和向量檢索兩種工具，接到四種不同的 agent harness 上(自製的 Chronos，以及 Claude Code、Codex、Gemini CLI)，拿 LongMemEval 這個長期記憶基準測試的 116 題來跑，看誰準。</p>

<p>結論有兩層:</p>

<p>🔹 <strong>第一層 (標題黨那層)</strong>: 在「行內回傳」(inline)模式下，grep 的準確率在每一組 harness 與模型的配對上，都贏過向量檢索。最大的差距是 Gemini Flash-Lite 在 Chronos 上的 86.2% 對 62.9%。而且在第二個實驗裡，當你不斷往上下文裡塞無關的干擾對話，向量檢索的準確率會往下掉，grep 卻幾乎不動，因為純字串比對天生免疫於向量空間裡的「距離漂移」。</p>

<p>🔹 <strong>第二層 (作者真正想講的)</strong>: harness 比演算法更重要。同一個 Claude Opus 4.6，在 Chronos 上 grep 能到 93.1%，換到 Claude Code 上只剩 76.7%。換 harness 造成的落差，跟換檢索演算法的落差差不多大。更誇張的是，改用「檔案式」(programmatic，把結果寫進檔案、讓 agent 自己去讀)的回傳方式，原本 grep 的優勢直接被抹平、甚至反轉: Codex 配 GPT-5.4 從行內 grep 的 93.1%，崩到檔案式 grep 的 55.2%。</p>

<blockquote>
  <p>編按: 所以這篇論文真正的重點不是「embedding 已死」，而是「你花在 harness 設計上的心力，應該遠多於花在挑哪個向量資料庫」。grep 只是眾多旋鈕裡的一個，而且通常不是最關鍵的那顆。</p>
</blockquote>

<h2 id="第一個關鍵-它測的是對話記憶不是企業文件">第一個關鍵: 它測的是「對話記憶」，不是企業文件</h2>

<p>Jerry Liu 的批評一針見血。LongMemEval 測的是「翻找使用者過往的聊天紀錄」: 明確的日期、數字、偏好、使用者講過的話。這種題目的答案，往往就藏在某幾段「逐字、原封不動」的文字裡(論文稱為 literal spans，字面證據)。</p>

<p>這正是 grep 的主場。你要找的東西本來就是一個精確的字串，當然是字串比對贏。</p>

<p>但企業 RAG 的典型場景完全不是這樣: 對著一堆財報、法律合約、SOP 文件問複雜問題。這些文件的語言是改寫過的、術語不一致的、偏概念的。LlamaIndex 後來也寫了 <a href="https://www.llamaindex.ai/blog/is-grep-all-you-need-lexical-vs-sematic-search-for-agents">一篇完整回應</a>，把 grep 在這種語料上的硬傷講得更白: 你 grep 不了 PDF、掃描影像、Office 檔;語料一上百萬份檔案，純線性掃描就開始吃力;更要命的是「詞彙對不上」: 使用者問的是「營收認列」，文件裡寫的卻是會計準則代號「ASC 606」，字面比對直接一無所獲。他們開的藥方是「分層處理」: 先用讀得懂版面的工具把文件解析出來、建索引做語意檢索，grep 只留著當精確比對的後援。</p>

<p>連論文作者自己都很誠實，在「限制」(Limitations)那一節白紙黑字寫著:</p>

<blockquote>
  <p>在證據很少以字面形式出現的領域(例如對改寫過的論文摘要做綜整、大量圖表的文件、或程式碼語意)，向量檢索和混合路由的表現可能會很不一樣。我們並不主張 grep 在通用情況下「贏過」向量檢索。</p>
</blockquote>

<p>換句話說，論文自己都沒宣稱 grep 是通用解，是讀者(和那個標題)把它過度延伸了。</p>

<h2 id="第二個關鍵-grep-吃的是高訊號關鍵字">第二個關鍵: grep 吃的是「高訊號關鍵字」</h2>

<p>為什麼寫程式是 grep 的完美場景? 因為程式碼裡的識別字(函式名、變數名、錯誤碼)本來就是必須精確比對的關鍵字。你要找 <code class="language-plaintext highlighter-rouge">handleUserLogin</code> 這個函式，grep 一秒就給你所有出現的位置，零歧義。反過來，向量檢索在程式碼上一直做不好，因為它會把語意相近、但根本不是你要的東西也一起撈進來。</p>

<p>Sara Zan 在 <a href="https://www.zansara.dev/posts/2026-03-15-vector-dbs-vs-grep/">一篇實測文章</a> 裡補了一個「到了 agent 時代才成立」的觀察: 傳統 RAG 假設「查詢必須一次就對」，所以拼命優化單次召回的品質。但 agent 打破了這個限制，它可以先用一個很爛的查詢搜一次，看看結果，再改寫查詢搜第二次。這種「反覆迭代」剛好補掉了關鍵字搜尋最大的弱點(怕你措辭不對)，又保留了它精確的優點。</p>

<p>把這兩件事疊起來，grep 的甜蜜點就很清楚了: <strong>語料是純文字、查詢以高訊號的字面關鍵字為主(程式碼、log、識別字、日期、人名)、而且 agent 可以反覆迭代搜尋</strong>。中小型的程式碼庫幾乎完美命中這三個條件，難怪 Claude Code 這類工具用得這麼順。</p>

<div style="border:1px solid var(--border); border-radius:8px; padding:18px 20px; margin:24px 0; background:var(--surface)">
<div style="font-weight:600; margin-bottom:14px; color:var(--text)">訊號強度光譜: 你的場景適合誰?</div>
<div style="display:flex; flex-wrap:wrap; gap:12px">
<div style="flex:1 1 200px; border-radius:6px; padding:12px 14px; background:rgba(26,127,55,.10); border:1px solid rgba(26,127,55,.35)">
<div style="font-weight:600; color:#1a7f37; margin-bottom:6px">高訊號關鍵字 → grep</div>
<div style="font-size:.9em; color:var(--text-secondary); line-height:1.6">程式碼識別字、錯誤碼、log、SKU 或合約編號、法規條號、日期人名。要的就是精確字串，grep / BM25 完勝</div>
</div>
<div style="flex:1 1 200px; border-radius:6px; padding:12px 14px; background:rgba(154,103,0,.10); border:1px solid rgba(154,103,0,.35)">
<div style="font-weight:600; color:#9a6700; margin-bottom:6px">混合訊號 → 混合檢索</div>
<div style="font-size:.9em; color:var(--text-secondary); line-height:1.6">技術文件、產品手冊、客服知識庫。又有專有名詞、又有口語問法，單用任一邊都會漏，需要 BM25 與向量檢索並用</div>
</div>
<div style="flex:1 1 200px; border-radius:6px; padding:12px 14px; background:rgba(9,105,218,.10); border:1px solid rgba(9,105,218,.35)">
<div style="font-weight:600; color:var(--accent); margin-bottom:6px">低訊號語意 → 向量檢索</div>
<div style="font-size:.9em; color:var(--text-secondary); line-height:1.6">概念性問答、改寫過的長文、滿是同義詞的自然語言。「退款政策」要對到「money back guarantee」，只有 embedding 接得住</div>
</div>
</div>
<div style="font-size:.8em; color:var(--text-secondary); margin-top:12px; text-align:right">✏️ 小編製圖</div>
</div>

<h2 id="第三個關鍵-雙峰的查詢分布逼你走向混合檢索">第三個關鍵: 雙峰的查詢分布，逼你走向混合檢索</h2>

<p>前面說 grep 怕措辭、吃不了概念查詢。有趣的是，向量檢索有個剛好對稱的盲區: 它接不住精確的識別字。<a href="https://dev.to/gabrielanhaia/your-vector-database-is-not-a-search-engine-heres-why-thats-killing-your-rag-2db5">Your Vector Database Is Not a Search Engine</a> 這篇文章整篇就在打這個弱點，而它開出的藥方不是「改用 grep」，而是「混合檢索」。</p>

<p>它先點出一個容易被忽略的陷阱: 大家評估檢索效果都拿 BEIR、MTEB 這類基準測試，而它們清一色是「漂亮的自然語言問句」，在這種題目上向量檢索當然贏 BM25。但你把真實的查詢紀錄拉出來看，分布其實是雙峰的: 一半是對話式的問句，另一半是精確查詢(產品代碼、錯誤碼、內部工具名、版本號、專有名詞)，後者常常佔了超過三成的流量。而 <code class="language-plaintext highlighter-rouge">SKU-47291</code> 這種字串被 tokenizer 切成 subword 之後，根本不會落在它原本該在的位置，向量檢索只會回給你一堆「看起來很像、其實不是」的鄰居。</p>

<p>所以這篇的結論是: 把 BM25(抓精確關鍵字)和向量檢索(抓語意)平行跑，用 RRF(Reciprocal Rank Fusion，互補排名融合)把兩份結果合併，再加一個 cross-encoder reranker 重排。grep 怕措辭、向量怕識別字，兩邊剛好各補對方的盲區，這才是雙峰查詢唯一說得通的解。</p>

<p>這也解釋了為什麼產業正在快速倒向混合檢索。有資料顯示，企業導入混合檢索的意願，在 2025 年底短短一個季度內就從 10.3% 跳到 33.3%;而「超長上下文會讓檢索變得多餘」這個說法，則從 15.5% 崩到 3.5%。大家都是踩了坑才學乖的。</p>

<h2 id="更好的框架-別找銀彈要策展一組工具">更好的框架: 別找銀彈，要「策展」一組工具</h2>

<p>Elastic 的 Leonie Monigatti 最近在 <a href="https://maven.com/p/1c532b/agentic-search-for-context-engineering">一場 Maven 演講</a>(內容也整理成 <a href="https://leoniemonigatti.com/blog/agentic-search-for-context-engineering.html">一篇文章</a>)裡，把這件事講得更透徹。她有個大膽的觀點: 「上下文工程大概有八成，其實就是 agentic search(代理式搜尋)。」你以為自己在做的是上下文策展，但底層真正在運作的，是一堆搜尋工具。她還畫了一條清楚的演進線: 早期的 RAG 是固定流程，每次都只檢索一次、又無法自我修正;agentic RAG 把它換成一個搜尋工具，讓 agent 自己決定要不要搜、要搜幾輪;到了上下文工程的時代，來源變多了(本地檔案、資料庫、網路、記憶)，自然就需要好幾種不同的搜尋工具並用。</p>

<p>她現場示範了一個很傳神的畫面: 只給 agent 一個 shell 工具加上一堆本地檔案，然後丟一個需要語意理解的問題給它。結果 agent 用 grep「作弊」、硬是假裝在做語意搜尋: 自己腦補出一串同義詞(regulate、compliance、constraints、gdpr、governance)，連續 grep 好幾輪，最後居然也湊出了答案。能動是能動，但這真的是對的做法嗎?</p>

<p>她的結論，跟論文、跟前面那些反方文章，完全收斂到同一句話:</p>

<blockquote>
  <p>如果你在找一把萬能的銀彈搜尋工具，很抱歉要讓你失望了。目標不是找到那一把銀彈，而是「策展」(curate)出一組合適的搜尋工具，讓你的 agent 能應付各種不同的查詢。</p>
</blockquote>

<p>Elastic 在 <a href="https://www.elastic.co/search-labs/blog/search-tools-context-engineering">一篇延伸文章</a> 裡，把這個策展原則整理成「低地板、高天花板」(low floor, high ceiling):</p>

<div style="display:flex; flex-wrap:wrap; gap:14px; margin:24px 0">
<div style="flex:1 1 260px; border:1px solid var(--border); border-radius:8px; padding:16px 18px; background:var(--surface)">
<div style="font-weight:600; color:#1a7f37; margin-bottom:8px">低地板 · 專用工具</div>
<div style="font-size:.92em; color:var(--text); line-height:1.7">範圍窄，但超穩、超好用。像只吃一個主題字串的語意搜尋工具，或 <code>get_customer(id)</code> 這種查詢。讓 agent 高效解掉<strong>大多數的日常查詢</strong>，幾乎不會用錯</div>
</div>
<div style="flex:1 1 260px; border:1px solid var(--border); border-radius:8px; padding:16px 18px; background:var(--surface)">
<div style="font-weight:600; color:var(--accent); margin-bottom:8px">高天花板 · 通用工具</div>
<div style="font-size:.92em; color:var(--text); line-height:1.7">範圍廣、彈性大，但很吃模型的推理能力、也吃延遲。像 shell 工具，或讓 agent 自己寫一整句 SQL / ESQL 查詢的工具。拿來接<strong>複雜、模糊、開放式的問題</strong></div>
</div>
</div>

<p>大多數 agent 兩種都需要，重點是兩者混搭，而不是幻想單一工具能通吃。</p>

<p>那一組工具到底該放哪些? Elastic 給的是「用數據決定」的做法，而不是憑空設計: 先給一個通用工具上線，然後記錄 agent 的真實行為(它呼叫了什麼、重試幾次、在哪裡卡住失敗)。當某種查詢模式反覆出現、或反覆出包，再針對它打造一個專用工具。每個工具都得「賺到」自己被放進工具箱的資格。Claude Code 只配大約 20 個工具，而不是塞進幾百個 MCP 工具，就是這個哲學的體現。</p>

<h2 id="連-grep-陣營都在偷偷補語意能力">連 grep 陣營都在偷偷補語意能力</h2>

<p>這裡有一個很有意思的訊號: 連 grep 陣營自己都在偷偷補語意能力。最近冒出一票「語意版 grep」的命令列工具，目標都一樣: 在不架整套向量資料庫的前提下，讓 agent 直接對本地檔案跑自然語言搜尋。</p>

<p><a href="https://github.com/run-llama/semtools">LlamaIndex 的 semtools</a> 是用 Rust 寫的命令列工具，主打兩個指令: <code class="language-plaintext highlighter-rouge">parse</code> 先用 LlamaParse 把 PDF、Word 這些原本 grep 不了的格式轉成 markdown，<code class="language-plaintext highlighter-rouge">search</code> 再用 static embedding(model2vec)做「模糊的語意關鍵字搜尋」，而且 search 全程在本地跑。LlamaIndex 那篇介紹文的標題還故意取作 “SemTools: Are Coding Agents all you Need?”，擺明在跟這波 grep 風潮對話。</p>

<p><a href="https://github.com/jina-ai/jina-grep-cli">Jina 的 jina-grep</a> 則更貼近 grep 原本的使用習慣: 用 Jina embeddings v5、在 Apple Silicon 上透過 MLX 純本地跑。你可以直接用 <code class="language-plaintext highlighter-rouge">jina grep "error handling" src/</code> 以自然語言搜尋，也可以把傳統 grep 的輸出用管線丟給它做語意重排(<code class="language-plaintext highlighter-rouge">grep -rn "error" src/ | jina grep "retry logic"</code>)，等於幫 grep 接上一顆語意大腦。</p>

<p><a href="https://lighton.ai/lighton-blogs/lateon-code-colgrep-lighton">LightOn 的 ColGrep</a> 則是這幾個裡面最能呼應本文主旨的一個。它基於 late interaction(ColBERT，延遲交互)的 LateOn-Code 模型，包成單一個 Rust 執行檔、100% 本地、程式碼完全不外流。關鍵是它預設就是混合的: 先用 regex 像 grep 一樣縮小候選範圍，再用 ColBERT 做語意排序，兩份排名用 RRF 合併。LightOn 講得很直白: ColGrep 不是要取代 grep，而是「把 grep 的優點吸收進來、再往上擴充」，官方數據說它正面對決贏純 grep 約七成，還省下 15.7% 的 token。</p>

<p>三個工具擺在一起看，結論幾乎自己跳出來: 純字面比對確實有天花板，連最擁抱 grep 的寫程式場景，大家都在想辦法把語意能力搬回 shell 裡。而 ColGrep 那套「regex + 語意 + RRF」的預設行為，根本就是把「策展一組工具」直接內建成單一工具，等於幫你預先把混合檢索組好了。</p>

<h2 id="收斂成幾個實務判斷">收斂成幾個實務判斷</h2>

<p>把論文、Jerry Liu、Sara Zan、Elastic 全部疊起來，小編幫大家收斂成幾條:</p>

<p>1️⃣ <strong>別一聽到「搜尋」就反射性地架向量資料庫</strong>: 如果語料是中小型純文字、查詢又以字面關鍵字為主、agent 還能反覆迭代，grep 往往是更省事的起點，省下一整層基礎設施。</p>

<p>2️⃣ <strong>但也別反過來以為 grep 萬能</strong>: 它搜不了 PDF、圖片、Office 檔，搞不定同義詞和概念查詢，語料一大、延遲就線性上升。它的本質，是拿「延遲和 token」去換「彈性」。</p>

<p>3️⃣ <strong>檢索層的標準答案是混合檢索</strong>: 就是上面那套 BM25 加向量檢索、合併後再重排;遇到需要多 hop 的關係推理，再疊上 GraphRAG。在真實的企業語料上，混合幾乎都打贏任何單一方法。</p>

<p>4️⃣ <strong>agent 層的標準答案是策展一組工具</strong>: 低地板的專用工具，搭配高天花板的通用工具，而不是硬塞一個萬能介面。組合勝過任何單一工具，但工具數量要克制，免得撐爆上下文、又害 agent 選錯工具。</p>

<p>5️⃣ <strong>真正該花心力的地方是 harness</strong>: 這是論文最反直覺的發現。一個好的 harness，能讓模型反覆改寫查詢、查看片段、自己決定何時收手，就算配的是向量檢索，也可能贏過爛 harness 配 grep。檢索演算法只是其中一顆旋鈕而已。搜尋老兵 Doug Turnbull 在 <a href="https://softwaredoug.com/blog/2026/04/06/agentic-search-is-having-a-grep-moment">Agentic Search Is Having a Grep Moment</a> 裡進一步點出，harness 其實有兩層迴圈: 內層是 agent 自己改寫查詢、反覆迭代;外層則是一道由你定義的「程式化品質閘門」(他稱為 hook)，agent 搜完後拿結果去對領域標準把關(夠不夠新、夠不夠權威)，不合格就把具體理由回灌、叫它再搜一次。他的結論很犀利: grep 能在生產環境動起來，往往不是因為 grep 本身有多強，而是外面這層約束和驗證在替它扛品質。</p>

<p>回到那個挑釁的標題。Grep is all you need 嗎? 對中小型的程式碼庫，可能真的是;但只要場景一離開「高訊號關鍵字 + 純文字 + 可迭代」這個甜蜜點，答案就從「二選一」變成「怎麼搭配」。與其糾結該選 grep 還是向量檢索，不如先想清楚自己的資料長什麼樣、查詢分布如何、agent 能不能反覆迭代，再決定怎麼組工具，而且一定要建一套評估(eval)流程去量，別憑感覺。檢索沒有銀彈，只有策展得好不好的工具箱。</p>]]></content><author><name>ihower</name></author><category term="RAG" /><category term="Search" /><category term="Agent" /><summary type="html"><![CDATA[最近一篇 PwC 的論文 Is Grep All You Need? How Agent Harnesses Reshape Agentic Search 在 X 上吵得蠻兇的，戰火還燒到一個更聳動的問題: 向量檢索已死? 這標題擺明在挑釁: 都 2026 年了，Claude Code、Codex 這些命令列 agent 光靠 grep 加 bash 就能找到東西，那花大錢養 embedding 模型、維護向量資料庫的 RAG 一整套基礎設施，是不是可以直接拆掉了?]]></summary></entry><entry><title type="html">從 Token 串流到 Agent 事件串流:OpenAI、AG-UI、Vercel、LangChain 的格式設計比一比</title><link href="https://blog.aihao.tw/2026/06/02/agent-streaming-chunk-format/" rel="alternate" type="text/html" title="從 Token 串流到 Agent 事件串流:OpenAI、AG-UI、Vercel、LangChain 的格式設計比一比" /><published>2026-06-02T00:00:00+00:00</published><updated>2026-06-02T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/02/agent-streaming-chunk-format</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/02/agent-streaming-chunk-format/"><![CDATA[<p>LangChain 的 Christian Bromann 寫了一篇 <a href="https://x.com/bromann/status/2057507361972011068">From Token Streams to Agent Streams</a>(從 token 流到 agent 流),講的是一件常被當成傳輸小事、其實是該好好設計的工程問題: 當你的產品從「一次模型呼叫」長成「一個會規劃、會分派子代理(subagent)、會呼叫工具、還會中途停下來等人核准」的 agent,你在前端收到的那串資料,格式整個都得重新設計。</p>

<p>他開頭那句話蠻精準的:「agent 串流已經超出 token 增量(token deltas)能承載的範圍。」小編就借這個命題,把幾家的設計做法整理在一起聊聊: 從最原始的 OpenAI 串流當基準,一路看到 AG-UI、Vercel AI SDK、LangChain 各自怎麼設計這串資料的格式,中間再穿插一些 ihower 實際做串流產品時累積的經驗。算是一篇參考各家設計、加上實戰心得的整理分享,看完你大概會同意:「能不能顯示 token」早就不是重點了。</p>

<h2 id="先談基準-從增量到語意事件">先談基準: 從增量到語意事件</h2>

<p>最原始的串流長這樣。OpenAI Chat Completions 的每個 chunk 是 <code class="language-plaintext highlighter-rouge">choices[0].delta.content</code>,裡頭塞一小段文字,前端要做的就是把這些片段一段段接起來。工具呼叫更麻煩: 它是 <code class="language-plaintext highlighter-rouge">tool_calls[].function.arguments</code> 的片段,用陣列位置(index)標記這是第幾個工具,你得自己照著位置把 JSON 碎片拼回完整的參數物件。這就是典型的「不透明 chunk」: 格式只關心「下一段資料是什麼」,語意全得靠你自己重建。</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// 基準 ①  Chat Completions: 不透明的 token 增量</div>
<div>data: {"choices":[{"delta":{"content":"舊金山"}}]}</div>
<div>data: {"choices":[{"delta":{"tool_calls":[{"index":0,</div>
<div>&nbsp;&nbsp;&nbsp;&nbsp;"function":{"arguments":"{\"ci"}}]}}]}</div>
</div>

<p>文字和工具參數都是裸片段，工具還得靠 <code class="language-plaintext highlighter-rouge">index</code> 自己對位拼回來，語意全靠前端重建。</p>

<p>OpenAI 後來的 Responses API 其實已經進步很多。它不再是沒有語意的增量，而是一串「語意事件」: <code class="language-plaintext highlighter-rouge">response.output_item.added</code> 告訴你新開了一個輸出項目(可能是訊息、工具呼叫、推理),<code class="language-plaintext highlighter-rouge">response.output_text.delta</code> 串文字,<code class="language-plaintext highlighter-rouge">response.function_call_arguments.delta</code> 串工具參數，推理(reasoning)有自己的事件，最後用 <code class="language-plaintext highlighter-rouge">response.completed</code> 收尾。整個輸出被組織成一個個輸出項目，每個項目內部用「開始 / 增量 / 結束」的節奏串。</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// 基準 ②  Responses API: 帶語意的事件</div>
<div><span style="color:var(--accent);">event: response.output_item.added</span></div>
<div>data: {"item":{"type":"function_call","name":"get_weather"}}</div>
<div><span style="color:var(--accent);">event: response.function_call_arguments.delta</span></div>
<div>data: {"delta":"{\"city\":\"Taipei\"}"}</div>
<div><span style="color:var(--accent);">event: response.completed</span></div>
</div>

<p>事件名稱本身就帶語意，前端一看就知道現在在串哪個輸出項目，推理和最終答案也分開了。</p>

<p>這已經是相當像樣的設計: 有型別、知道自己在串什麼、推理和答案分開、連用量(usage)資訊都保得住。所以問題來了,既然基準都做到這樣了,為什麼還不夠用在應用層級?</p>

<h2 id="基準暗藏的三個假設">基準暗藏的三個假設</h2>

<p>關鍵在於 Responses API 這類設計,骨子裡假設了三件事，而 agent 應用把這三件事全打破了:</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px 20px;margin:8px 0;">
<div style="display:flex;flex-wrap:wrap;gap:14px;">
<div style="flex:1 1 200px;"><strong style="color:var(--accent);">假設一: 只有一次模型呼叫</strong><br /><span style="color:var(--text-secondary);font-size:.92em;">但複雜的 agent 會展開成一棵樹: 主 agent 分派三個子代理，每個又各自呼叫工具。一條線性的串流講不清「這段是誰產生的」。</span></div>
<div style="flex:1 1 200px;"><strong style="color:var(--accent);">假設二: 一條串流全包</strong><br /><span style="color:var(--text-secondary);font-size:.92em;">把所有子代理的 token、工具、狀態壓成一條串流，等於逼前端下載每個子代理的每個 token，哪怕畫面上只開了其中一個。</span></div>
<div style="flex:1 1 200px;"><strong style="color:var(--accent);">假設三: 連線是短暫的</strong><br /><span style="color:var(--text-secondary);font-size:.92em;">會跑很久的 agent 一動就是十幾分鐘。瀏覽器一刷新就斷線，沒有排序和回放機制就只能重跑，或是內容重複。</span></div>
</div>
</div>

<p>Bromann 把應用層級真正該問的問題列得很到位，小編挑幾個最有感的: 能不能即時畫出一棵 agent 的工作樹? 能不能只訂閱畫面上那一個子代理，而不用付下載其他所有子代理的頻寬代價? 能不能把「等人核准」當成第一級的事件來處理? 能不能斷線後重新接上、接著上次的進度繼續，而不是整段重播?</p>

<p>這些問題，token 增量一個都答不了。因為它根本沒有「樹」(tree)「頻道」(channel)「斷點」(checkpoint)這些概念。</p>

<h2 id="兩個維度-頻道與命名空間">兩個維度: 頻道與命名空間</h2>

<p>LangChain 這套新串流原語的核心設計，小編覺得最值得學的，是它把每一段資料拆成「兩個正交維度」來標記。</p>

<p>第一個維度是 <strong>頻道(channel)</strong>: 標記這串資料「屬於哪一類關注點」，也就是你會想分開來看的不同面向。<code class="language-plaintext highlighter-rouge">messages</code> 是對話內容、<code class="language-plaintext highlighter-rouge">values</code> 和 <code class="language-plaintext highlighter-rouge">updates</code> 是流程圖共享的狀態(例如目前累積的搜尋結果、計畫清單、步數計數器這類資料: <code class="language-plaintext highlighter-rouge">values</code> 給你完整快照，<code class="language-plaintext highlighter-rouge">updates</code> 給你每一步改了哪些欄位)、<code class="language-plaintext highlighter-rouge">tools</code> 是工具執行的起訖、<code class="language-plaintext highlighter-rouge">lifecycle</code> 是整段執行和子代理的生死、<code class="language-plaintext highlighter-rouge">custom:*</code> 是應用自己定義的投影。</p>

<p>第二個維度是 <strong>命名空間(namespace)</strong>: 描述這個事件「發生在 agent 樹的哪個位置」。根節點、巢狀的子流程圖、某個子代理，都可以發出同一種頻道的事件，但各自保有身分、不會混在一起。</p>

<p>用一張表最好懂。每段資料都落在某個「頻道 × 位置」的格子裡，而你可以只訂閱自己正在畫的那幾格:</p>

<div style="border:1px solid var(--border);border-radius:8px;overflow:hidden;margin:8px 0;font-size:.86em;">
<div style="display:flex;background:var(--surface);font-weight:600;">
<div style="flex:1.2 1 0;padding:8px 10px;border-right:1px solid var(--border);">命名空間 ↓ / 頻道 →</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);">messages</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);">tools</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;">values</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1.2 1 0;padding:8px 10px;border-right:1px solid var(--border);background:var(--surface);">root</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);">·</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);">·</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;">·</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1.2 1 0;padding:8px 10px;border-right:1px solid var(--border);background:var(--surface);">subagent: research-1</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);background:rgba(9,105,218,.15);font-weight:600;">訂閱這格</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);background:rgba(9,105,218,.15);font-weight:600;">訂閱這格</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;">·</div>
</div>
<div style="display:flex;border-top:1px solid var(--border);">
<div style="flex:1.2 1 0;padding:8px 10px;border-right:1px solid var(--border);background:var(--surface);">subagent: research-2</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);color:var(--text-secondary);">不下載</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;border-right:1px solid var(--border);color:var(--text-secondary);">不下載</div>
<div style="flex:1 1 0;padding:8px 6px;text-align:center;color:var(--text-secondary);">不下載</div>
</div>
</div>

<p>「頻道是可重複使用的關注點、命名空間是產生它的位置」這個拆分，就是整套設計的關鍵選擇。有了它，一個子代理檢視器才能只打開它要的那一格，不會把每個子代理的 token 全拉下來。</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// LangChain agent 流: 每個事件多帶 channel + namespace</div>
<div>{"<span style="color:var(--accent);">channel</span>":"messages","<span style="color:var(--accent);">namespace</span>":["root"],"block":"text","delta":"嗨"}</div>
<div>{"<span style="color:var(--accent);">channel</span>":"tools","<span style="color:var(--accent);">namespace</span>":["subagent:research-1"],"name":"search","status":"started"}</div>
<div>{"<span style="color:var(--accent);">channel</span>":"values","<span style="color:var(--accent);">namespace</span>":["root"],"patch":[{"op":"add","path":"/findings/-"}]}</div>
<div>{"<span style="color:var(--accent);">channel</span>":"lifecycle","<span style="color:var(--accent);">namespace</span>":["subagent:research-2"],"status":"started"}</div>
</div>

<p>多了 <code class="language-plaintext highlighter-rouge">namespace</code> 這個維度，同一種事件就能標出「是樹上哪個子代理發的」，這正是前面那些設計都沒有的。(實際 LangGraph 傳輸線上的欄位名稱略有不同，這裡是概念示意。)</p>

<h2 id="投影-不要解析直接拿你要畫的東西">投影: 不要解析，直接拿你要畫的東西</h2>

<p>光有型別還不夠。Bromann 的第二個重點是 <strong>投影(projection)</strong>。先講清楚: 投影不是傳輸線上的某種格式，而是一個設計概念。它指的是執行層在那串底層事件「之上」，幫你組好幾種現成的視圖，讓你的程式碼直接拿來用，不必自己一條條去解析原始事件。</p>

<p>具體長什麼樣? 在 LangChain 的框架 SDK 裡，這個概念落地成一組 API: 你呼叫 <code class="language-plaintext highlighter-rouge">useMessages()</code> 就拿到一串已經組好的訊息、<code class="language-plaintext highlighter-rouge">useToolCalls()</code> 拿到工具呼叫的清單、<code class="language-plaintext highlighter-rouge">useValues()</code> 拿到目前的狀態。至於重組、重新排序、斷線重連這些髒活，執行層全幫你包掉。而且每則訊息本身是「一連串有型別的區塊」: 文字、推理、工具參數、用量，而不是一條要你自己切的字串。這點對現代模型輸出很重要,推理和最終答案本來就該分開呈現，工具參數要當成結構化資料來組裝，多媒體資料更不該被硬塞進純文字介面。</p>

<div style="border:1px solid var(--border);border-radius:8px;padding:16px;margin:8px 0;">
<div style="display:flex;flex-wrap:wrap;align-items:stretch;gap:12px;">
<div style="flex:1 1 210px;">
<div style="font-size:.82em;margin-bottom:2px;"><strong>① 底層事件串</strong></div>
<div style="color:var(--text-secondary);font-size:.74em;margin-bottom:8px;">網路上實際傳的就是這串事件</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.75em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;">message · delta</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.75em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;">tool · start / args</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.75em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;">state · delta</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.75em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;">lifecycle · subagent</div>
</div>
<div style="flex:0 1 120px;display:flex;flex-direction:column;justify-content:center;text-align:center;color:var(--accent);font-size:.78em;">
<div style="font-size:1.6em;line-height:1;">→</div>
<div style="margin-top:4px;">前端 SDK<br />(瀏覽器 JS)<br />組裝 · 排序 · 重連</div>
</div>
<div style="flex:1 1 210px;">
<div style="font-size:.82em;margin-bottom:2px;"><strong>② 投影視圖</strong></div>
<div style="color:var(--text-secondary);font-size:.74em;margin-bottom:8px;">前端 SDK 組好的 JS API，不是網路格式</div>
<div style="font-size:.8em;background:rgba(9,105,218,.1);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;"><code>useMessages()</code> → 聊天泡泡</div>
<div style="font-size:.8em;background:rgba(9,105,218,.1);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;"><code>useToolCalls()</code> → 工具指示器</div>
<div style="font-size:.8em;background:rgba(9,105,218,.1);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin-bottom:5px;"><code>useValues()</code> → 狀態面板</div>
<div style="font-size:.8em;background:rgba(9,105,218,.1);border:1px solid var(--border);border-radius:6px;padding:4px 8px;">subagents → 子代理分頁</div>
</div>
</div>
<div style="border-top:1px solid var(--border);margin-top:12px;padding-top:10px;font-size:.78em;color:var(--text-secondary);">你只訂閱要畫的頻道 / 子代理，後端就只送那部分事件過來;線上跑的單位永遠是事件，組裝成視圖是前端 SDK 的事。</div>
</div>

<p>這裡 ihower 補了一個很實務的提醒: 你不應該把上游 Responses API 或 Completions API 的串流原封不動轉傳給前端。模型 API 吐出來的東西，很多根本不該給使用者看,可能是內部推理、給除錯用的中繼資料、或牽涉權限不該外露的內容。前端要收的，是你「為這個產品設計過」的事件,而不是上游吐什麼就照單全收。換句話說,你前端那層格式，是一個你要主動設計的應用層介面,不是模型 API 的透傳管線。這也是投影這個概念為什麼這麼關鍵: 哪些事件、長什麼形狀、露給前端，都由你決定。</p>

<p>跨模型使用是另一個自己設計格式的硬道理。實務上你很可能今天用 OpenAI、明天換 Anthropic 或 Gemini，每一家原生的串流格式都長得不一樣。要是前端直接綁死某一家的格式，換一次模型前端就得跟著改一輪;但只要中間隔了一層你自己設計的事件格式，換模型時你只要改後端那段轉換，前端依賴的那份約定完全不用動。模型是會換的，你的格式不該跟著換。</p>

<blockquote>
  <p>編按: 這背後其實是一個「抽象反轉」的觀念,真正的事件來源是執行引擎(harness),token 串流和畫面更新都只是它的投影。所以換掉底層模型，換掉的只是投影，你前端依賴的那份語意約定不變。這也是為什麼這類協定都強調「按類別訂閱，而不是按單一事件名稱訂閱」: 訂「所有工具呼叫類的事件」，協定日後新增事件名稱你也不會壞;把每個事件名稱寫死進去就會壞。</p>
</blockquote>

<h2 id="ag-ui-怎麼設計的">AG-UI 怎麼設計的</h2>

<p>講到標準化的事件協定,就不能不看 <a href="https://docs.ag-ui.com">AG-UI</a>(Agent-User Interaction Protocol，由 <a href="https://www.copilotkit.ai">CopilotKit</a> 發起)。它的定位跟 LangChain 那套有點不同: AG-UI 想當的是「agent 後端」和「前端」之間那條通用的線，和 MCP(管工具)、A2A(管 agent 之間的溝通)並列在 agent 協定的堆疊裡。</p>

<blockquote>
  <p>編按: 釐清一下 CopilotKit 和 AG-UI 的關係，免得搞混。CopilotKit 是一間公司、也是一套 React / Angular 的前端 SDK(現成的聊天元件、<code class="language-plaintext highlighter-rouge">useAgent</code> hook、generative UI 那些);AG-UI 則是他們從跟 LangGraph、CrewAI 的合作中抽出來、再開源給整個生態的「協定」,後來被 Google、LangChain、AWS、微軟、Mastra、PydanticAI 等採用。一句話: AG-UI 是開放協定(規格)，CopilotKit 是講這個協定的第一方前端框架。</p>
</blockquote>

<p>它的設計小編覺得乾淨俐落。所有事件都繼承自同一個 <code class="language-plaintext highlighter-rouge">BaseEvent</code>(至少帶 <code class="language-plaintext highlighter-rouge">type</code>，另有可選的 <code class="language-plaintext highlighter-rouge">timestamp</code>、<code class="language-plaintext highlighter-rouge">rawEvent</code>),核心大約十多種，分成幾類:</p>

<ul>
  <li><strong>生命週期</strong>: <code class="language-plaintext highlighter-rouge">RUN_STARTED</code>、<code class="language-plaintext highlighter-rouge">STEP_STARTED</code> / <code class="language-plaintext highlighter-rouge">STEP_FINISHED</code>、<code class="language-plaintext highlighter-rouge">RUN_FINISHED</code> / <code class="language-plaintext highlighter-rouge">RUN_ERROR</code>，帶 <code class="language-plaintext highlighter-rouge">threadId</code>、<code class="language-plaintext highlighter-rouge">runId</code>，還有可選的 <code class="language-plaintext highlighter-rouge">parentRunId</code> 給分支用</li>
  <li><strong>文字訊息</strong>: <code class="language-plaintext highlighter-rouge">TEXT_MESSAGE_START</code> → 多個 <code class="language-plaintext highlighter-rouge">TEXT_MESSAGE_CONTENT</code>(各帶一段 <code class="language-plaintext highlighter-rouge">delta</code>)→ <code class="language-plaintext highlighter-rouge">TEXT_MESSAGE_END</code>，用 <code class="language-plaintext highlighter-rouge">messageId</code> 串起來</li>
  <li><strong>工具呼叫</strong>: <code class="language-plaintext highlighter-rouge">TOOL_CALL_START</code> → 多個 <code class="language-plaintext highlighter-rouge">TOOL_CALL_ARGS</code>(串 JSON 片段)→ <code class="language-plaintext highlighter-rouge">TOOL_CALL_END</code> → <code class="language-plaintext highlighter-rouge">TOOL_CALL_RESULT</code>，用 <code class="language-plaintext highlighter-rouge">toolCallId</code> 串</li>
  <li><strong>狀態管理</strong>: <code class="language-plaintext highlighter-rouge">STATE_SNAPSHOT</code>(完整狀態)、<code class="language-plaintext highlighter-rouge">STATE_DELTA</code>(用 JSON Patch / RFC 6902 送增量)、<code class="language-plaintext highlighter-rouge">MESSAGES_SNAPSHOT</code></li>
  <li><strong>特殊事件</strong>: <code class="language-plaintext highlighter-rouge">RAW</code>(包外部系統的事件)、<code class="language-plaintext highlighter-rouge">CUSTOM</code>(應用自訂)當逃生口</li>
</ul>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// AG-UI: 標準化的型別事件 + JSON Patch 狀態</div>
<div>{"<span style="color:var(--accent);">type</span>":"TEXT_MESSAGE_START","messageId":"m1","role":"assistant"}</div>
<div>{"<span style="color:var(--accent);">type</span>":"TEXT_MESSAGE_CONTENT","messageId":"m1","delta":"嗨"}</div>
<div>{"<span style="color:var(--accent);">type</span>":"TOOL_CALL_START","toolCallId":"t1","toolCallName":"get_weather"}</div>
<div>{"<span style="color:var(--accent);">type</span>":"TOOL_CALL_ARGS","toolCallId":"t1","delta":"{\"city"}</div>
<div>{"<span style="color:var(--accent);">type</span>":"STATE_DELTA","delta":[{"op":"replace","path":"/step","value":2}]}</div>
</div>

<p>這裡有兩個小編覺得很值得抄的設計。一是 <strong>「開始 / 內容 / 結束」三段式</strong>，套用在所有串流內容上(文字、工具參數都是同一個節奏),前端只要實作一套組裝邏輯就好。二是 <strong>「快照 / 增量」模式</strong> 來同步狀態: 偶爾送一次完整快照當基準,中間用 JSON Patch 送小增量,兼顧完整和省頻寬。它還貼心地提供 <code class="language-plaintext highlighter-rouge">TEXT_MESSAGE_CHUNK</code> 這種便利事件，第一段自動展開成 start，串流切到下一個 id 時自動補上 end，省掉手動管理生命週期的麻煩。</p>

<p>傳輸層的選擇上 AG-UI 也刻意保持開放: SSE、WebSocket、webhook 甚至自家的二進位傳輸都能載，不綁死單一種(對照之下 Vercel 那套主要走 SSE)。再配上雙向溝通，使用者的中斷和確認可以回傳給 agent，所以「人在迴路」(human-in-the-loop)是它內建的概念(<code class="language-plaintext highlighter-rouge">RUN_FINISHED</code> 的結果可以是一個中斷，代表停下來等人)。</p>

<p>不過對照一下會發現一個有意思的差別: AG-UI 的核心比較像「單一 agent + 工具 + 狀態」的一條扁平串流，多 agent 樹的部分主要靠 <code class="language-plaintext highlighter-rouge">RAW</code> 和 <code class="language-plaintext highlighter-rouge">CUSTOM</code> 當擴充點。而 LangChain 那套多了命名空間這個維度，把 agent 樹的位置直接做進協定的第一層。兩種取捨沒有絕對好壞: 前者簡單通用、好接既有框架，後者為複雜的樹狀 agent 而生。</p>

<h2 id="還有哪些值得參考的設計">還有哪些值得參考的設計</h2>

<p>小編另外撈了幾個業界的做法，會發現大家其實在往同一個方向走。</p>

<p><strong>Vercel AI SDK 的 data stream 協定</strong> 跟 AG-UI 幾乎是同一個形狀,只是換了名字: 文字用 <code class="language-plaintext highlighter-rouge">text-start</code> / <code class="language-plaintext highlighter-rouge">text-delta</code> / <code class="language-plaintext highlighter-rouge">text-end</code>(每段內容有自己的 id),工具用 <code class="language-plaintext highlighter-rouge">tool-input-start</code> / <code class="language-plaintext highlighter-rouge">tool-input-delta</code> / <code class="language-plaintext highlighter-rouge">tool-input-available</code> / <code class="language-plaintext highlighter-rouge">tool-output-available</code>,推理也是「開始 / 增量 / 結束」。它有兩個設計小編覺得很實用: 一是 <strong>資料片段的就地更新</strong>,你送一個帶 <code class="language-plaintext highlighter-rouge">id</code> 的資料片段,之後用同一個 <code class="language-plaintext highlighter-rouge">id</code> 再送一次就會更新同一塊(很適合做進度條、載入狀態、協作中的文件);二是 <strong>暫時性片段</strong>,送給前端顯示、但不寫進訊息歷史(適合那種一閃即逝的通知)。它一樣走 SSE，主打連線保活、可重連、好快取。</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// Vercel AI SDK: 形狀同 AG-UI，外加可就地更新的資料片段</div>
<div>data: {"<span style="color:var(--accent);">type</span>":"text-start","id":"t1"}</div>
<div>data: {"<span style="color:var(--accent);">type</span>":"text-delta","id":"t1","delta":"嗨"}</div>
<div>data: {"<span style="color:var(--accent);">type</span>":"tool-input-start","toolCallId":"c1","toolName":"getWeather"}</div>
<div>data: {"<span style="color:var(--accent);">type</span>":"tool-output-available","toolCallId":"c1","output":{"temp":28}}</div>
<div>data: {"<span style="color:var(--accent);">type</span>":"data-weather","id":"w1","data":{"status":"loading"}}</div>
</div>

<p>這兩個設計用程式碼最好懂。上面那筆 <code class="language-plaintext highlighter-rouge">data-weather</code> 是 <code class="language-plaintext highlighter-rouge">loading</code>，之後用同一個 <code class="language-plaintext highlighter-rouge">id</code> 再送一次，前端就會就地把那塊換成新內容(很適合進度條、協作中的文件);而帶 <code class="language-plaintext highlighter-rouge">transient:true</code> 的片段只會即時顯示、不寫進訊息歷史(適合一閃即逝的通知):</p>

<blockquote>
  <p>編按: 這裡的 <code class="language-plaintext highlighter-rouge">data-</code> 前綴和整套資料片段機制(<code class="language-plaintext highlighter-rouge">id</code> 就地更新、<code class="language-plaintext highlighter-rouge">transient</code> 不入歷史)是 Vercel AI SDK 協定定義的，但後面的 <code class="language-plaintext highlighter-rouge">weather</code>、<code class="language-plaintext highlighter-rouge">notification</code> 是你自己取的名字、可搭配型別做到型別安全，並非內建事件;對照之下 <code class="language-plaintext highlighter-rouge">text-start</code>、<code class="language-plaintext highlighter-rouge">tool-input-delta</code> 那些才是固定的內建事件名。</p>
</blockquote>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// 同一個 id 再送 → 就地更新那一塊</div>
<div>data: {"type":"data-weather","<span style="color:var(--accent);">id</span>":"w1","data":{"status":"done","temp":28}}</div>
<div style="color:var(--text-secondary);margin:10px 0 6px;">// transient:true → 只即時顯示，不進訊息歷史</div>
<div>data: {"type":"data-notification","data":{"msg":"查到了"},"<span style="color:var(--accent);">transient</span>":true}</div>
</div>

<h3 id="順帶分清楚-a2ui-是格式ag-ui-是傳輸">順帶分清楚: A2UI 是「格式」，AG-UI 是「傳輸」</h3>

<p>既然講到 AG-UI，順手帶一個名字超像、又最容易跟它搞混的東西: <a href="https://a2ui.org">A2UI</a>。兩者其實是不同層的協定。</p>

<p>A2UI(Agent to UI)是 Google 提出的協定，要解決的問題是:「agent 要怎麼安全地把一個畫面送到前端?」尤其在多代理場景，有些 agent 跑在別人的伺服器上，你不可能讓它直接塞 HTML/JavaScript 進你的頁面(有安全風險，畫出來也跟你的 app 樣式不搭)。</p>

<p>A2UI 的做法是: agent 不送程式碼，而是送一串「宣告式的 JSON」描述畫面長怎樣，前端再用自己的原生元件(React、Flutter、SwiftUI 都行)把它畫出來。它的幾個設計重點:</p>

<ul>
  <li><strong>送的是「資料」不是「程式碼」</strong>: agent 只能從前端提供的「元件目錄(catalog)」裡挑元件，沒有任意執行程式碼的風險，所以連跑在別人伺服器上的 agent 送來的 UI 都能安全地畫。</li>
  <li><strong>結構和資料分開</strong>: 一種訊息(<code class="language-plaintext highlighter-rouge">updateComponents</code>)描述畫面結構、另一種(<code class="language-plaintext highlighter-rouge">updateDataModel</code>)灌資料，前端可以只更新某個欄位、不必重畫整個畫面。</li>
  <li><strong>扁平的元件清單 + ID 互相參照</strong>(adjacency list): 不用一次生出完美的巢狀 JSON，LLM 可以邊生邊串，收到 root 元件就先開始畫。</li>
</ul>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// A2UI: 送「宣告式 JSON」描述畫面，前端用自己的原生元件畫</div>
<div>{"<span style="color:var(--accent);">updateComponents</span>":{"surfaceId":"booking","components":[</div>
<div>&nbsp;&nbsp;{"id":"root","component":"Column","children":["title","submit"]},</div>
<div>&nbsp;&nbsp;{"id":"title","component":"Text","text":"Book Your Table"},</div>
<div>&nbsp;&nbsp;{"id":"submit","component":"Button","action":{"event":{"name":"confirm"}}}]}}</div>
<div>{"<span style="color:var(--accent);">updateDataModel</span>":{"surfaceId":"booking","path":"/booking","value":{"date":"2025-12-16"}}}</div>
</div>

<p>關鍵就在這個分工: <strong>A2UI 管「要畫什麼」(格式)，AG-UI 管「事件怎麼在前後端之間流動」(傳輸)</strong>。兩者是互補的，A2UI 本身就標明自己不挑傳輸，可以走 AG-UI、A2A、SSE、WebSocket;反過來 AG-UI 也可以把一包 A2UI 內容當成某個事件的酬載載過去。實際上任何已經會講 AG-UI 的 agent，幾乎零成本就能驅動 A2UI。把這兩層分清楚，你才不會拿「要畫什麼」去跟「怎麼傳」硬比。</p>

<div style="display:flex;flex-wrap:wrap;gap:12px;margin:8px 0;">
<div style="flex:1 1 250px;border:1px solid var(--border);border-radius:8px;padding:14px 16px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:6px;">AG-UI: 串「事件」</div>
<div style="font-size:.88em;">agent 說「發生了什麼」</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.74em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin:6px 0;">TOOL_CALL_START · STATE_DELTA</div>
<div style="font-size:.88em;color:var(--text-secondary);">↓ 你的前端決定怎麼畫</div>
<div style="font-size:.82em;margin-top:8px;background:#dafbe1;color:#1a7f37;border-radius:6px;padding:3px 8px;display:inline-block;">適合: 前端是你自己的</div>
</div>
<div style="flex:1 1 250px;border:1px solid var(--border);border-radius:8px;padding:14px 16px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:6px;">A2UI: 描述「畫面」</div>
<div style="font-size:.88em;">agent 說「畫成這樣」</div>
<div style="font-family:ui-monospace,Menlo,monospace;font-size:.74em;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;margin:6px 0;">Column · Text · Button(元件樹)</div>
<div style="font-size:.88em;color:var(--text-secondary);">↓ 通用 renderer 直接畫</div>
<div style="font-size:.82em;margin-top:8px;background:#fff8c5;color:#9a6700;border-radius:6px;padding:3px 8px;display:inline-block;">適合: 前端不屬於你</div>
</div>
</div>

<p>你可能會問: AG-UI 不是也有「generative UI」能嗎? 有，但差別在「誰決定畫面長怎樣」。AG-UI 的 UI 是「事件驅動、由你的 app 自己渲染」: agent 發出工具呼叫或自訂事件，你在前端自己決定哪個事件對應哪個元件，渲染邏輯寫在你(受信任的)app 程式碼裡，它本身沒有規定元件樹的標準格式。A2UI 則是把「UI 描述本身」標準化成一棵宣告式元件樹，一個通用 renderer 就能畫。所以前者載的是「你 app 會自己渲染的訊號」，後者載的是「通用 renderer 就能畫的 UI 描述」，這也是為什麼不受信任的遠端 agent 適合用 A2UI。</p>

<blockquote>
  <p>編按: CopilotKit 自己把 generative UI 分成三種模式，剛好把這個層次標得很清楚: <strong>Static</strong>(走 AG-UI，前端預先寫好元件、agent 用事件或狀態觸發)、<strong>Declarative</strong>(走 A2UI，agent 吐元件樹、通用 renderer 畫)、<strong>Open-Ended</strong>(MCP Apps / Open JSON，更自由)。重點是: AG-UI 是事件導向、本身不是宣告式 UI;同一個前端可以底層用 AG-UI 載事件，需要時再把 A2UI 當酬載塞進某個事件裡，兩者疊著用。</p>
</blockquote>

<p>講完設計，小編插一段比較主觀的看法: 對 A2UI 這種「宣告式 UI」，小編其實蠻保留的。畫面長怎樣、怎麼排版、怎麼互動，本來就是前端最擅長、也最該掌握的事;讓 agent 去描述一棵 UI 元件樹，某種程度上就是在重新發明一套 HTML/CSS，多數情況下是 over-engineering。<a href="https://news.ycombinator.com/item?id=46286407">Hacker News 上的討論</a>也有人吐同樣的槽:「看這些範例，感覺它最後會收斂回我們早就有的 HTML，那為什麼不乾脆讓各平台支援 HTML 就好? LLM 本來就很會生 HTML」、「『用 JSON 描述畫面、客戶端來畫』這套我們搞很多年了，難的從來不是線上格式，而是元件版本管理跟跨客戶端 debug」。</p>

<p>那 A2UI 真正有價值的情境是什麼? 小編認為其實就一個: <strong>當前端不屬於你的時候</strong>。比方你要把 agent 上架到別人的平台、嵌進別人的 app，你沒辦法自己寫前端、也不能塞程式碼進去，只能用對方提供的元件目錄,這時候一套宣告式、跨平台、又安全(只能挑核可過的元件)的 UI 描述格式才划算。同一串討論裡也有人精準點出這點: A2UI 最有意思的就是「遠端訊息傳遞、你不擁有那個 UI」的場景。反過來說，如果前端是你自己的，老老實實寫前端就好，別繞這一圈。</p>

<blockquote>
  <p>編按: 這種「agent 吐一份 UI 描述、通用 renderer 來畫」的路子,A2UI 並不是唯一一家。Thesys 的 <a href="https://www.openui.com/">OpenUI</a>(MIT 授權)走同一個方向,切入點不同: 它不送 JSON,而是設計了一套叫 OpenUI Lang 的精簡 DSL(每行一個 <code class="language-plaintext highlighter-rouge">identifier = Expression</code>),主打兩點,一是 token 更省(官方自宣稱比 JSON 少約 67%),二是 line-oriented,每收到一行就能立刻畫一塊,streaming 更順。</p>
</blockquote>

<h2 id="存下來的不是答案是整段串流">存下來的不是答案，是整段串流</h2>

<p>ihower 還提了一個小編覺得超多人會踩到的坑: 串流型的應用不能只存最後那段最終答案的文字。</p>

<p>想想看,使用者關掉頁面、隔天再回來,你要讓他看到的應該是「完整的過程」: 中間呼叫了哪些工具、各個子代理做了什麼、推理怎麼一步步走、多媒體怎麼一塊塊長出來。如果你資料庫裡只存了最終答案，這些全沒了，回訪的使用者只看到一個乾巴巴的結果，和當時親眼看著它一步步生出來的體驗完全是兩回事。</p>

<p>所以你真正該存下來的，是「整段串流本身」: 當時串流長什麼樣，存下來就是什麼樣，回放的時候也放同一份。這正好解釋了為什麼前面那些協定都那麼在意型別事件、排序資訊和「快照 / 增量」,因為那串有序的事件本身，同時就是你的「儲存格式」和「回放格式」。格式設計得好不好,直接決定了你能不能把一段跑了十分鐘的 agent 執行完整存起來、之後一模一樣地放出來。</p>

<p>這跟斷線重連其實是同一個能力的兩面: 重連是「執行還在跑,我接回去」,回放是「執行早就結束,我重看一遍」,兩者吃的都是同一份有序的事件記錄。一個沒有設計好格式的串流應用，這兩件事都別想做。</p>

<h3 id="整段都存會踩到的儲存效率問題">「整段都存」會踩到的儲存效率問題</h3>

<p>不過「整段都存」馬上會帶出另一個工程問題: 怎麼存才不會爆掉? LangGraph 團隊最近就在處理這件事。</p>

<p>問題出在它預設的存法: 每走一步，就把當下「完整的狀態」整包存一次。對話的訊息清單尤其慘，因為每一輪都是在前面所有訊息後面再接一句，於是第 1 步存 1 則、第 2 步存 2 則、到第 N 步存 N 則… 前面的內容被一存再存，儲存成本是用 <strong>O(N²)</strong> 在膨脹。官方給的數字很嚇人: 一段累積到約 10 萬 token 的對話，單一執行緒就吃掉約 250 MB;外推到百萬 token 等級會到約 25 GB。多輪對話拖越長，重複浪費越誇張。</p>

<p>他們的解法叫 <code class="language-plaintext highlighter-rouge">DeltaChannel</code>，精神跟前面 AG-UI 的「快照 / 增量」一模一樣: 別每步都存完整清單，只存「這一步新增了什麼」(增量)，要讀的時候再順著增量鏈把狀態重播回來;再加一個 <code class="language-plaintext highlighter-rouge">snapshot_every</code> 參數，每隔幾步存一張完整快照，免得重播鏈拉太長。儲存成本就從 O(N²) 降到 O(N)，同一段對話省下幾十倍空間(官方實測 500 輪、約 10 萬 token 的對話，從 252 MB 降到約 712 KB)。</p>

<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin:8px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78em;line-height:1.75;overflow-x:auto;">
<div style="color:var(--text-secondary);margin-bottom:6px;">// 預設: O(N²)，每步都把整包歷史存一次</div>
<div>messages: Annotated[list[AnyMessage], add_messages]</div>
<div style="color:var(--text-secondary);margin:10px 0 6px;">// 改用 DeltaChannel: O(N)，只存每步的增量</div>
<div>messages: Annotated[list[AnyMessage], <span style="color:var(--accent);">DeltaChannel</span>(add_messages)]</div>
</div>

<blockquote>
  <p>編按: 這個儲存最佳化的細節可以參考 LangGraph 的 <a href="https://github.com/langchain-ai/langgraph/pull/7547">DeltaChannel PR #7547</a> 和官方<a href="https://docs.langchain.com/oss/python/langgraph/persistence">持久化文件</a>的「Optimize checkpoint storage」一節。</p>
</blockquote>

<p>這也帶出一個蠻漂亮的呼應: 「快照 / 增量」這組設計，在串流時是為了省頻寬，在儲存時是為了省空間，在斷線重連和回放時又變成「接得回去、放得出來」的關鍵。同一套格式上的巧思，一次解決了好幾層的問題。</p>

<p>而「一段執行就是一串事件」這個想法，甚至已經被做進基礎設施。LangChain 在 2026 年 5 月發表了一個專門的資料庫 <a href="https://www.langchain.com/blog/introducing-smithdb">SmithDB</a>，用來扛 agent 的觀測(observability)資料，它的核心設計原則一句話就講完，而且跟這篇通篇在講的是同一件事:「一段執行是一串事件，不是一筆不可變的資料列」(a run is a sequence of events, not a single immutable row)。當 agent 動輒跑好幾個小時、夾帶上百個巢狀步驟和圖片影音，連儲存層都得照著「事件序列」的形狀重新設計。它還有個細節值得一抄: 把核心欄位和大塊內容(工具的長輸出、模型回應)分開存，主資料列只放指標，真的要看某一筆時才把大內容拉出來，列表和篩選就不會被大 payload 拖慢。從串流、儲存到觀測，大家其實都在往同一個方向收斂。</p>

<h2 id="收尾-串流是一個你要設計的應用層介面">收尾: 串流是一個你要設計的應用層介面</h2>

<p>繞了一圈，這些設計講的其實是同一件事: 串流不再是每個應用都得自己解析的低階傳輸細節，它變成了一個 <strong>你要主動設計的應用層介面</strong>。</p>

<p>而且這層格式並不貴。把上游 API 轉成你自己的事件格式，不過是讓 CPU 做點轉換，擺在動輒好幾秒的 LLM 請求延遲面前根本不算什麼。所以就算只是純聊天介面，也建議隔一層自己的格式(裡頭留一個文字增量事件，照樣有逐字打字的感覺)，差別只在要做到多細: 聊天可能只需要訊息加上少量狀態，agent 當同事的產品才需要長出完整的頻道、命名空間和回放。</p>

<p>格式設計得好不好，直接決定了你能不能只訂閱螢幕上那塊 agent 的工作、用對的抽象去呈現它、在執行越拉越長時保持連線、在使用者回訪時完整重播。用 Bromann 的話收尾最貼切: 串流 agent 該像在「寫應用程式」，而不是在「讀一堆日誌」。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="API" /><category term="Tool Use" /><summary type="html"><![CDATA[LangChain 的 Christian Bromann 寫了一篇 From Token Streams to Agent Streams(從 token 流到 agent 流),講的是一件常被當成傳輸小事、其實是該好好設計的工程問題: 當你的產品從「一次模型呼叫」長成「一個會規劃、會分派子代理(subagent)、會呼叫工具、還會中途停下來等人核准」的 agent,你在前端收到的那串資料,格式整個都得重新設計。]]></summary></entry><entry><title type="html">如何用 AI 分析 Agent traces? 持續改進 Agent 產品</title><link href="https://blog.aihao.tw/2026/06/02/agent-trace-analysis/" rel="alternate" type="text/html" title="如何用 AI 分析 Agent traces? 持續改進 Agent 產品" /><published>2026-06-02T00:00:00+00:00</published><updated>2026-06-02T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/02/agent-trace-analysis</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/02/agent-trace-analysis/"><![CDATA[<p>做 AI agent 產品最不性感、卻最重要的工作之一，就是讀 traces。一條複雜 agent 的 trace 動輒幾十上百輪，裡面藏著工具呼叫、推理步驟、跟 prompt 的來回。過去大家都靠肉眼一條一條掃，掃到眼花，而且根本掃不完。</p>

<h2 id="先搞懂-一條-agent-trace-長什麼樣">先搞懂: 一條 agent trace 長什麼樣</h2>

<p>如果你還沒實際翻過 agent 的 trace，先建立個畫面。一條 trace 就是 agent 處理「一個使用者請求」的完整紀錄: 從使用者問了什麼，到它最後回了什麼，中間每一步都被記了下來。而 agent 很少是單純的一問一答，它會推理、規劃、呼叫工具、看結果、再決定下一步，一路跑到收工。</p>

<p>這些步驟在 trace 裡是一層層巢狀的 <strong>span</strong>: 最外層的 root span 是整個 agent run，底下掛著一個個子 span，每次 LLM 呼叫(推理)、每次工具呼叫(搜尋、計算、查資料庫)都是一個 span。每個 span 還記著自己的輸入、輸出、花了多久、用掉多少 token、有沒有報錯。把父子關係攤開，大概長這樣:</p>

<div style="margin:1.5em 0;padding:1.1em 1.2em;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:.86em;">
<div style="font-weight:600;margin-bottom:.8em;color:var(--text);">一條 trace = 一次 agent run 底下巢狀的 spans</div>
<div style="display:flex;justify-content:space-between;gap:.5em;background:var(--tag-bg);border:1px solid var(--accent);border-radius:6px;padding:.45em .7em;color:var(--accent);font-weight:600;"><span>🟦 root span · 整個 agent run — 使用者:「幫我比較台積電和聯發科」</span><span>8.2s</span></div>
<div style="margin-left:1em;border-left:2px solid var(--border);padding-left:1em;margin-top:.5em;color:var(--text-secondary);">
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🧠 LLM 推理: 先查兩檔基本面<span>1.8s · 1.2k tok</span></div>
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🔧 工具呼叫 · search_stock(台積電)<span>0.3s</span></div>
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🔧 工具呼叫 · search_stock(聯發科)<span>0.3s</span></div>
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🧠 LLM 推理: 資料夠嗎? 再查財報<span>2.1s · 1.6k tok</span></div>
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🔧 工具呼叫 · get_financials(2330)<span>0.5s</span></div>
<div style="display:flex;justify-content:space-between;gap:.5em;padding:.3em 0;">🧠 LLM · 整理成最終回答<span>3.2s · 2.4k tok</span></div>
</div>
<div style="margin-top:.8em;font-size:.82em;color:var(--text-secondary);">每個 span 都記著輸入、輸出、延遲、token、有沒有報錯;讀 trace 就是讀這整棵樹。✏️ 小編製圖</div>
</div>

<p>重點是: 這東西的資訊量很可觀。一段對話跨個好幾輪，可能就是幾十上百個 span、好幾 MB 的資料。Adam Lucek <a href="https://x.com/AdamRLucek/status/2059383656506920970">講得好</a>: 「trace 資料這年頭簡直貴如黃金，前提是你得知道拿它做什麼。」難就難在後半句，而那正是這篇要談的。</p>

<p>為什麼 agent 這麼難搞? 因為它跟傳統軟體很不一樣: 輸出是非確定性的、對 prompt 的一點點改動都可能很敏感、又得接受沒有邊界的自然語言輸入。講白了，你不會知道 agent 會做出什麼，直到它真的上線、跑給真實使用者。於是除錯的方式也跟著變: 從「讀程式碼、找邏輯錯誤」，變成「分析 trace」，你要抓的不再是某一行程式寫錯，而是推理出錯、工具用得很沒效率、決策品質不好這類問題，這些在程式碼裡根本看不出來。</p>

<p>2026 年一個很明顯的趨勢是: 乾脆讓 agent 來讀 agent 的 trace。LangChain 把這件事叫做「agent 改進 agent」。小編最近把 LangChain、FutureSearch、Raindrop 還有 Shreya Shankar 幾篇放在一起看，發現關鍵的分歧其實是: 這個讀 trace、找問題的「質性分析」，到底要<strong>交給誰的 agent、在什麼時間點做</strong>。</p>

<p>讀 trace 其實就是 error analysis，也就是「看你自己的資料」，這是做評估的地基。問題不在於要不要做，而在於人工讀根本沒辦法規模化: 一個團隊一天可能就產出幾千上萬條 trace，沒有人讀得完。所以生產環境的 agent 監控，跟傳統軟體監控很不一樣: 你沒辦法每一筆都細看，只能把「最值得看的子集」送進一套結構化的審查流程，讓 LLM 自動跑品質評估，再從中跑線上評估、偵測已知的失敗、探索還沒發現的問題、把好例子加進資料集、或是觸發人工審查。</p>

<p>而且這件事的回報，遠不只是「抓 bug」。LangChain 的 Viv Trivedi <a href="https://x.com/Vtrivedy10/status/2059389255957532890">點得很準</a>: 不管你想做 harness 工程、寫評估、換模型還是後訓練，這些改進 agent 的手段，幾乎全都在「先把 trace 蒐集起來、讀懂」的下游。換句話說，「先出一個 v1 → 打開 tracing → 讀懂 trace 和錯誤 → 做實驗 → 再循環」就是 agent 開發的主迴圈。所以真正的問題變成: 這個迴圈裡最累的「讀懂」這一段，能不能交給 agent?</p>

<div style="margin:1.5em 0;padding:1.2em;background:var(--surface);border:1px solid var(--border);border-radius:8px;">
<div style="font-weight:600;margin-bottom:.9em;color:var(--text);">Agent 改進迴圈</div>
<div style="display:flex;flex-wrap:wrap;align-items:stretch;gap:.5em;font-size:.9em;">
<div style="flex:1 1 110px;padding:.7em;background:var(--bg);border:1px solid var(--border);border-radius:6px;text-align:center;">📈<br />生產 trace</div>
<div style="display:flex;align-items:center;color:var(--text-secondary);">→</div>
<div style="flex:1 1 110px;padding:.7em;background:var(--bg);border:1px solid var(--border);border-radius:6px;text-align:center;">🔍<br />篩出重複的失敗</div>
<div style="display:flex;align-items:center;color:var(--text-secondary);">→</div>
<div style="flex:1 1 110px;padding:.7em;background:var(--tag-bg);border:1px solid var(--accent);border-radius:6px;text-align:center;color:var(--accent);font-weight:600;">📋<br />變成可處理的問題</div>
<div style="display:flex;align-items:center;color:var(--text-secondary);">→</div>
<div style="flex:1 1 110px;padding:.7em;background:var(--bg);border:1px solid var(--border);border-radius:6px;text-align:center;">🛠️<br />評估器 + 資料集 + 修復</div>
<div style="display:flex;align-items:center;color:var(--text-secondary);">↺</div>
</div>
<div style="margin-top:.9em;font-size:.82em;color:var(--text-secondary);">過去這整圈靠人工跑;2026 的做法是把 agent 塞進每個環節，而人類退到「審核關卡」上把關。✏️ 小編製圖</div>
</div>

<p>迴圈裡最吃功夫的，就是「讀 trace、判斷哪裡出錯」這個質性分析的環節。目前小編看下來，有三種不同作法:</p>

<div style="margin:1.5em 0;display:flex;flex-wrap:wrap;gap:.7em;font-size:.86em;">
<div style="flex:1 1 200px;padding:1em;background:var(--surface);border:1px solid var(--border);border-radius:8px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:.5em;">① 你自己的 coding agent</div>
<div style="color:var(--text-secondary);line-height:1.7;">用一個自訂 skill，讓你的 Claude Code 讀 trace、找失敗模式、跑實驗驗證<br /><span style="color:var(--text);">代表: FutureSearch、ihower 自己寫的 trace 分析 skill</span></div>
</div>
<div style="flex:1 1 200px;padding:1em;background:var(--surface);border:1px solid var(--border);border-radius:8px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:.5em;">② 執行時 · agent 自己診斷</div>
<div style="color:var(--text-secondary);line-height:1.7;">agent 跑的當下就自己回報故障，平台再把這些信號彙總分群<br /><span style="color:var(--text);">代表: Raindrop Self Diagnostics</span></div>
</div>
<div style="flex:1 1 200px;padding:1em;background:var(--surface);border:1px solid var(--border);border-radius:8px;">
<div style="font-weight:600;color:var(--accent);margin-bottom:.5em;">③ 事後 · 另一個專門的 agent</div>
<div style="color:var(--text-secondary);line-height:1.7;">另外派一個自主 agent 批次掃生產 trace，產出問題 / 評估器 / 修復 PR<br /><span style="color:var(--text);">代表: LangChain 監控平台 LangSmith 的 LangSmith Engine</span></div>
</div>
</div>

<h2 id="-帶你自己的-coding-agent-去讀">① 帶你自己的 coding agent 去讀</h2>

<p>這條路的精神是: 不另外養一個分析 agent，而是把 trace 接到你<strong>本來就在用的 coding agent</strong>(通常就是 Claude Code)，讓它幫你讀。</p>

<h3 id="futuresearch-一次深讀一條-trace">FutureSearch: 一次深讀一條 trace</h3>

<p>最省事的版本是 FutureSearch 分享的 <a href="https://futuresearch.ai/blog/llm-trace-analysis/">這篇</a>。作者說他花很多時間在讀 agent 的 trace，一半是為了知道怎麼改進 agent，一半是為了確認「跑出來的評估到底能不能信」。</p>

<p>他們的 trace 存在 <a href="https://langfuse.com/">Langfuse</a>，而所謂「接給 Claude Code」其實樸素到不行: 直接把一條 Langfuse 的 trace 連結貼進去，下一個 <code class="language-plaintext highlighter-rouge">review this trace: &lt;Langfuse 連結&gt;</code> 指令就開工了。背後是一個自訂的 <code class="language-plaintext highlighter-rouge">/review-agent-trace</code> skill，裡面放滿常見失敗模式的範例，外加「怎麼形成假設、怎麼跑快速實驗來驗證」的指引。他們把要找的問題分成四大類，蠻實用的:</p>

<p>1️⃣ <strong>鷹架程式碼(scaffolding)的 bug</strong>: 系統 prompt 設定錯、agent 在某些條件下莫名其妙就停了</p>

<p>2️⃣ <strong>工具失效</strong>: 搜尋回傳一堆亂碼或無意義的結果</p>

<p>3️⃣ <strong>prompt 引發的問題</strong>: 框架太狹隘、指令互相衝突</p>

<p>4️⃣ <strong>推理失敗</strong>(最難): agent 搞混日期、亂呼叫工具、漏掉任務的一部分</p>

<p>最有意思的，是模型代差帶來的質變。他們早期用 Sonnet 3.7 時，得把分析拆成數十個很狹隘的 prompt，套到每一個 ReAct 步驟上，又貴又脆弱，還是會漏掉一堆;而且 Sonnet 3.7 太容易相信 agent 的自我評估，agent 說「好，我找到答案了」它就信了。換成 Opus 4.6 驅動的 Claude Code 之後，一個 session、一段通用的 prompt，就能把整條 trace 從頭吃到尾，不需要人工切割，而且「真的能動」。更厲害的是這個 skill 不只挑錯，還會自己<strong>形成假設、跑實驗來驗證</strong>: 抓到可疑的問題後，它會去模擬一個改過的 prompt 跑跑看，確認真的能修好，才把建議交出來。</p>

<p>那這份報告長什麼樣? 文章貼了兩個實際的輸出。一個是 agent 該查 Google 卻沒查，導致它沒注意到美伊局勢升溫;Claude Code 不只指出這點，還追到根因(這是 Opus 4.6 在 effort = low 下的通病)，並給出具體的修法(把 effort 調到 medium 就好)。另一個更能看出它的「實驗精神」:</p>

<div style="margin:1.3em 0;padding:1em 1.2em;background:var(--surface);border-left:3px solid var(--accent);border-radius:6px;font-size:.9em;">
<div style="font-weight:600;margin-bottom:.6em;color:var(--text);">🔍 一則 <code>review this trace</code> 的輸出</div>
<div style="line-height:1.85;color:var(--text-secondary);"><strong style="color:var(--text);">問題:</strong> agent 要找四月中到十一月的最高收盤價，但它找到的第一個資料集只涵蓋到十月，就停手回報了錯誤答案<br /><strong style="color:var(--text);">根因:</strong> agent 太早滿足，沒檢查資料是否涵蓋完整區間<br /><strong style="color:var(--text);">驗證:</strong> 它接著模擬幾個用改過 prompt 的 ReAct 步驟實際跑跑看，確認這樣改真的能修好</div>
</div>

<p>還有一點很值得提: 維護成本幾乎是零。他們在 skill 裡順手寫清楚「去哪裡拿 trace、agent 的實作在哪幾個 Python 檔、評估任務的標準答案在哪」，省得 Claude Code 每次重新摸索;再配一份 <code class="language-plaintext highlighter-rouge">CLAUDE.md</code>，鼓勵它「發現哪條指令過時了就自己更新 skill」，等於這個分析工具會自我維護。</p>

<h3 id="ihower-自己寫的-trace-分析-skill-生產規模的取樣審查">ihower 自己寫的 trace 分析 skill: 生產規模的取樣審查</h3>

<p>那如果要上到生產規模、一天好幾千條 trace 呢? 不必換成大平台，自帶 agent 這條路一樣能做，只是得先解決一個問題: <strong>該看哪些?</strong> ihower 自己就手刻了一個這樣的 trace 分析 skill，用 <a href="https://www.braintrust.dev/">Braintrust</a> 的 <code class="language-plaintext highlighter-rouge">bt</code> CLI 把 trace 拉出來，小編借來當第二個案例。生產流量一大，就不可能每筆都讀，重點是把「最值得看的子集」送進結構化的審查。它用三種取樣訊號互補:</p>

<ul>
  <li>🚩 <strong>明確的壞訊號</strong>: 被護欄(guardrail)攔下的、使用者倒讚的、出現 error 的、工具呼叫爆量的、對話超過 N 輪的，這些<strong>全部撈進來看</strong></li>
  <li>🤖 <strong>judge 篩選</strong>: 用便宜的模型挑出可疑的，例如已知失敗模式的 judge、情緒 / 風險的 judge</li>
  <li>🎲 <strong>隨機基準</strong>: 再隨機抽一定比例，才看得到那些「沒觸發任何訊號」的隱性問題，不會被訊號本身的偏誤綁住</li>
</ul>

<p>skill 裡把這寫成一套加權抽樣規則: 全部 error 加全部 guard 都看，高互動輪數的隨機抽 75%、中互動抽 50%、基準流量只抽 15%。背後的邏輯是: 稀有但高風險的全看，高互動代表高資訊量所以多看，剩下的用隨機覆蓋。用比例(而不是固定條數)，抽樣量會隨流量自動縮放，審查量就跟著訊號密度走。取樣完再把樣本分組、切片，一個子 agent 讀一片，最後合成一份報告。</p>

<p>而這份報告刻意用一套<strong>固定骨架</strong>，讓每次跑出來的報告都能直接比較:</p>

<div style="margin:1.3em 0;padding:1.1em 1.2em;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:.88em;">
<div style="font-weight:600;margin-bottom:.7em;color:var(--text);">📄 固定報告骨架(節錄)</div>
<div style="padding:.45em 0;border-bottom:1px solid var(--border);"><strong style="color:var(--accent);">1 · 整體讀評</strong>: <span style="color:var(--text-secondary);">一句話總評，agent 哪裡好、哪裡有明顯問題</span></div>
<div style="padding:.45em 0;border-bottom:1px solid var(--border);"><strong style="color:var(--accent);">2 · 立即動作建議</strong>: <span style="color:var(--text-secondary);">1–5 條，放在最前面，讓報告讀起來像決策文件</span></div>
<div style="padding:.45em 0;border-bottom:1px solid var(--border);"><strong style="color:var(--accent);">3 · 問題類別</strong>: <span style="color:var(--text-secondary);">只列已確認 / 可能，給穩定編號 I-1、I-2(使用者影響 / 負責方 / 修法)</span></div>
<div style="padding:.45em 0;border-bottom:1px solid var(--border);"><strong style="color:var(--accent);">4 · 建議人工追查的對話</strong>: <span style="color:var(--text-secondary);">Top 10–20，連回上面的問題編號</span></div>
<div style="padding:.45em 0;border-bottom:1px solid var(--border);"><strong style="color:var(--accent);">5 · 使用者滿意 / 不滿意訊號</strong>: <span style="color:var(--text-secondary);">每筆標「明確」或「推測」，附原話與 trace 連結</span></div>
<div style="padding:.45em 0;"><strong style="color:var(--accent);">6 · 用戶模式與常見意圖</strong>: <span style="color:var(--text-secondary);">使用者都在問什麼、從哪個產品表面進來</span></div>
<div style="margin-top:.7em;font-size:.82em;color:var(--text-secondary);">骨架固定，每次報告就能直接比較，看出版本之間的進退。✏️ 小編製圖</div>
</div>

<p>其中有一條規矩很關鍵: <strong>事實與推論要分開標</strong>，別把「樣本裡直接看到的」跟「我推測的」糊成同一句。</p>

<blockquote>
  <p>編按: 這種「依流量決定怎麼看」的思路，Raindrop 共同創辦人 Ben Hylak 在 <a href="https://www.howtoeval.com/">howtoeval.com</a> 整理成一條階梯: 一天十來條時直接全讀;量一大就得讓系統告訴你哪些值得看，從 Stumbles(在原始紀錄裡撈苗頭)→ Issues(重複出現的就立案)→ Signals(值得長期監控的行為)→ Experiments(改完用真實流量做 A/B 驗證)。ihower 的加權取樣，就是這條階梯高流量那一端的具體做法。</p>
</blockquote>

<h2 id="-執行時-讓-agent-自己診斷自己">② 執行時: 讓 agent 自己診斷自己</h2>

<p>第二種位置更前面，前到 agent 還在跑的當下。Raindrop 的另一個功能 <a href="https://www.raindrop.ai/blog/agent-self-diagnostics">Agent Self Diagnostics</a> 就是這個想法。小編必須說，這是這輪看下來<strong>最有創意的一招</strong>: 大家都在想怎麼「事後把 trace 讀懂」，Raindrop 反過來問「那不如讓 agent 在當下自己講」。背後的觀察蠻反直覺但很對: 「現在的 agent 比以前聰明太多了，它們往往比我們更清楚自己哪裡壞了。」</p>

<p>所以與其等事後有人(或有 agent)去翻 trace 才發現問題，不如讓 agent 在執行過程中<strong>自己回報故障</strong>，預設分成四種信號: 缺少上下文、工具重複失敗、能力缺口、任務徹底失敗(也能自訂)。</p>

<p>機制其實很乾淨，而且回答了一個容易誤會的問題: 這不是平台另外派一個 agent 來分析你，而是<strong>你自己那個 agent</strong>。你用 Raindrop SDK 把模型呼叫包一層(<code class="language-plaintext highlighter-rouge">raindrop.wrap(ai, { selfDiagnostics: { enabled: true } })</code>)，SDK 就會<strong>偷偷塞一個隱藏工具</strong>(預設叫 <code class="language-plaintext highlighter-rouge">__raindrop_report</code>)進你 agent 的工具清單，連 system prompt 都不用你動。照 <a href="https://raindrop.ai/docs/integrations/vercel-ai-sdk">官方文件</a> 的說法: 啟用之後，SDK 會塞進一個隱藏工具，當 agent 撞上無法復原的問題時，它會靜默呼叫這個工具。每個信號類別，本質上就是給這個工具的一段描述(「什麼情況該回報 missing_context」)，你的模型在跑的當下自己判斷卡在哪、靜默呼叫這個工具，SDK 再把信號(類別 + 一段它自己寫的白話說明，綁在同一個 <code class="language-plaintext highlighter-rouge">eventId</code> 上)送回 Raindrop。所以這裡的「智能」，就是你的底層模型在讀那幾段工具描述、對自己的處境做第一層分類，不需要另一個分析模型。回到 Raindrop 之後，平台才負責跨多次執行的彙總、分群、追趨勢、告警。</p>

<p>換句話說，Raindrop 在這裡做的是<strong>分散式的質性分析</strong>: 把「我哪裡卡住了」這種第一層判斷推到 agent 自己身上(誰會比它更清楚?)，平台只負責把這些信號跨多次執行聚合起來。比起 LangSmith 那種「養一個外部大 agent 事後讀原始 trace」，這是把分析拆開、塞到源頭的另一種賭法。</p>

<p>不過也要老實說一句，而且這話是 Ben Hylak 自己講的: 這招創意十足，實戰上卻<strong>沒有表面看起來那麼好用</strong>。他在最新的 <a href="https://www.howtoeval.com/">agent 評估指南</a>(這篇非常讚，推薦一讀)裡直言，自診信號比較像「大水漫灌」: 一堆隨機回報裡，偶爾撈到一個有趣的，得花不少功夫去調靈敏度，才能變成穩定可用的訊號;更微妙的是，你給那個隱藏工具的<strong>措辭</strong>，會直接左右 agent 到底報不報，說穿了它就是一個二元分類問題。所以小編的評語是: 概念很漂亮、值得關注，但別期待它一開下去就一勞永逸。</p>

<h2 id="-事後-另外派一個專門的-agent-批次掃生產-trace">③ 事後: 另外派一個專門的 agent 批次掃生產 trace</h2>

<p>第三種位置在最後面: 等 trace 都進了生產資料庫，另外派一個專門的 agent，主動、批次地把整個迴圈跑完。最完整的代表，是 LangChain 在它的監控平台 LangSmith 上、五月推出的 <a href="https://www.langchain.com/blog/introducing-langsmith-engine">LangSmith Engine</a>。它本身就是一個 agent，做三件事: 找出重複出現的失敗、把失敗變成「可處理的問題」、再把問題轉成能長期沉澱的東西(評估器、資料集範例、修復 PR)。重點不在指出「這條 trace 壞了」，而是把一個生產失敗，變成團隊未來能拿來測試、防止退步的資產。</p>

<p>LangChain 還另外寫了一篇<a href="https://x.com/palashshah/status/2056786835322687640">技術細節文</a>講他們怎麼蓋這個東西，小編覺得比公告本身有料多了，幾個工程巧思特別值得學:</p>

<p>🔹 <strong>先壓縮再讀(trajectory)</strong>: 生產專案的回看窗口裡可能有上萬條 trace，全塞進上下文根本不可行，光 10 條長 agent 的 trace 就能有上百個工具呼叫。解法是先把每條壓成「骨架」: 每一輪只留角色、工具名稱、延遲、內容字數，不放完整內容。agent 先看骨架找出「形狀可疑」的，再回頭把需要的細節撈進來。</p>

<p>🔹 <strong>篩選與調查兩階段分離</strong>: 第一階段用便宜的 Haiku 當「篩選員」，一次掃約 20 條，只回報「乾淨 / 可能有問題」，不做診斷;第二階段才派「調查員」把可疑 trace 的完整內容和程式碼撈進來深入分析。篩選員追求規模，調查員追求深度，職責切乾淨。</p>

<p>🔹 <strong>限制問題分類</strong>: Engine 不讓 agent 自由發明問題類別，而是給它一份預先定義好的清單(像 <code class="language-plaintext highlighter-rouge">pii_leak</code>、<code class="language-plaintext highlighter-rouge">agent_looping</code>、<code class="language-plaintext highlighter-rouge">incorrect_tool_args</code>、<code class="language-plaintext highlighter-rouge">missing_tool</code>)。理由很實在: 讓 agent 亂取類別，輸出就難以評估、也難以信任。</p>

<p>🔹 <strong>評估器先測過再交出來</strong>: Engine 為每個問題提一個評估器，但在拿給使用者之前，會先用 <code class="language-plaintext highlighter-rouge">test_evaluator</code> 工具拿證據 trace 跑一遍，確認它真的抓得到對應的失敗(回傳 PASS / FAIL / SKIPPED)。因為一個評估器「看起來合理」跟「真的抓得到問題」是兩回事。</p>

<p>🔹 <strong>找問題的 agent 跟修問題的 agent 分開</strong>: 早期他們讓同一個 agent 又找問題又提修復，結果什麼都做不好。後來拆成: 主 agent 只負責找問題、產出評估器和資料集，需要修的就打一個 <code class="language-plaintext highlighter-rouge">needs_fix</code> 標籤，再交給專門的修復 agent 去提修改。</p>

<p>🔹 <strong>跨次執行的記憶(Agent Overview)</strong>: Engine 維護一份類似 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> 的活文件，記著「你的 agent 在做什麼、會出現哪些 trace 結構、常見的地雷、團隊的偏好」，跨次更新，甚至會從使用者的動作(關掉某個問題、建立某個評估器)學習。</p>

<p>這套已經實際幫 Cogent、Harmonic、Campfire 等團隊處理過影響數千條 trace 的問題。而且這也不只 LangChain 一家在做，Arize、Braintrust、Databricks 在 2026 年都推了方向類似的東西，「平台幫你自動分析 trace」已經是一整個賽道。</p>

<h2 id="踩個剎車-agent-真的會分析嗎">踩個剎車: agent 真的會「分析」嗎?</h2>

<p>三種作法講完，得踩一下剎車。Shreya Shankar 有一篇 <a href="https://www.sh-reya.com/blog/ai-qual-analysis/">Exploring Agent-Assisted Qualitative Analysis</a>(用 agent 協助做質性分析)，做了一個很扎實的實驗。她本身就是做這行研究的(剛拿到教職)，這次直接拿 agent 去做學術界的「質性分析」: grounded theory，也就是讀大量雜亂的資料、貼標籤(編碼)、再歸納成高階的主題。她問了一個很本質的問題: 用 agent 做這種分析，到底行不行?</p>

<p>她的素材是 451 條推文(Sholto Douglas 問「什麼時候你會改用別的模型、而不是 Claude?」底下的回覆)，要分析的是「使用者為什麼想跳槽」。她跑了六種條件，差別在兩個軸: agent 有沒有被教 grounded theory 的方法、以及人類介入到什麼程度(完全不介入、逐批審編碼、讀備忘給回饋、甚至兩個 agent 互相對照)。</p>

<p>她先點出為什麼這題對 AI 特別難: 質性分析的「對的答案」高度依賴語料以外的脈絡，需要一種說不太清楚的品味與判斷(不像「程式有沒有編譯過」有標準答案);更麻煩的是，評斷的標準本身會在分析過程中不斷演化，而現在的 agent 偏偏假設目標固定、又急著收斂。實測下來，毛病很一致:</p>

<p>❌ <strong>是轉述，不是分析</strong>: agent 產生的編碼數量，跟推文長度高度相關(ρ = 0.81)，但長推文往往只是同一個抱怨講得更長，它卻照著字數狂貼標籤。而且 93.8% 的編碼整份語料只用過一次，明明完整的編碼簿就在上下文裡，它卻不重用、每條都發明新標籤，根本沒在歸納。</p>

<p>❌ <strong>沒把資料編碼完</strong>: 雖然被要求讀完整份、甚至多跑幾輪，agent 卻常讀到一半就自己宣布「分析完成」。表現最好的一組只編碼了 68%，最差的只有 5.5%，多數落在 25–35%;還有些推文直接被跳過、給了空標籤。</p>

<p>❌ <strong>工作管理很差</strong>: 它傻傻地一條一條順序處理，不會像人類研究者那樣切分資料、有策略地採樣、邊讀邊組裝。最離譜的一組: 兩個 agent 跑到逾時後，主 agent 乾脆改寫成「關鍵字比對的 Python 腳本」(看到 sycoph 就標 overly_agreeable)，這已經完全不是在做分析了。</p>

<p>❌ <strong>回饋迴圈很弱</strong>: 她講一次「我在意競品比較」，agent 就過度擬合、把它捧成最大的分類;她說「標籤太多了」，agent 收斂一下、下一批又故態復萌(最後 96.5% 還是一次性標籤)。要嘛過度擬合早期回饋，要嘛轉頭就忘。</p>

<p>更值得 agent 圈警惕的，是她當「人類審核者」的體感。她發現一個陷阱: agent 產出的高階分類，常常含糊到無法反駁，例如「可靠性與信任」這種大到什麼抱怨都塞得進去的類別，你很難說它錯，但這種「不可證偽」恰恰不是好分類。她一句話點破: <strong>含糊的分類讓人容易相信、卻無法據以行動</strong>。如果連「幻覺算不算主要問題」這個類別都定義不清，那它接著提出的「修掉幻覺」方案，你也沒有基礎去信任。這正好戳中 LangSmith Engine 那類「自動產問題、提修復」的要害。</p>

<p>她的結論很精準: agent「能很快做完機械性的部分，但缺乏品味」。所以真正有意思的問題，根本不是「如何自動化質性分析」，而是「如何打造一個系統，讓人類的品味跟 agent 的規模真正組合起來」。而她隨手點的一個未來方向，剛好就是這整篇的主題: 把這套拿去做 <strong>agent 的 error analysis: 資料本身就是 agent 的 trace，目標是找出重複出現的失敗模式</strong>。換句話說，本文講的三種作法，正是這個還沒被解好的題目的早期答案。</p>

<p>Shreya 這個質疑，跟前面三種作法其實不矛盾，反而互補。注意到沒有: LangSmith Engine 那麼多設計(固定分類、評估器先測過、所有問題都進「待審核」狀態等人按)，本質上都是在替 agent 的「缺乏品味」做防護欄;FutureSearch 乾脆讓你自己的 Claude Code 在你眼皮底下開車，Raindrop 則讓 agent 把判斷攤開成可追蹤、人能在儀表板上把關的信號，也是同一回事。沒有人真的在假裝 agent 能取代人，差別只在於把人擺在迴圈的哪個位置。</p>

<h2 id="想自己做好-trace-分析-幾條小編的收穫">想自己做好 trace 分析? 幾條小編的收穫</h2>

<p>把上面的成功和翻車擺在一起，如果你打算自己搭一套用 agent 分析 trace 的流程，小編覺得有幾條原則特別關鍵:</p>

<p>🔸 <strong>別讓 agent 邊讀邊即興生分類</strong>: 這是 Shreya 那組實驗最大的教訓。讓 agent 一條一條讀、順手就把錯誤分類(axial code)生出來，它幾乎永遠在「換句話說」而不是「歸納」。比較好的做法，是把開放編碼和歸類拆開，或讓 agent 只負責提議、由你來定分類。</p>

<p>🔸 <strong>迴圈的編排交給程式碼，不要交給 agent 判斷</strong>: agent 很愛走一走就自己宣布「分析完成」(Shreya 那組最慘只讀了 5.5%)。所以「要讀哪些、分幾批、跑幾輪、什麼時候停」這種流程控制，用程式碼寫死(像 ihower 的取樣腳本、或 LangSmith 用篩選員去派發)，別讓 agent 自己拿主意。</p>

<p>🔸 <strong>用一個很簡單的標準，檢驗你抓到的失敗模式</strong>: 每抓出一個失敗模式，問自己一句: 它能不能寫成一條清楚的「納入 / 排除」準則，並且轉成一個自動檢查(LLM-as-judge 或程式斷言)? 不能的話，它就太抽象了，只能拿來自我安慰，沒辦法真的把迴圈閉合起來。這正是 Shreya 說的「含糊分類無法行動」的解藥，也是 LangSmith Engine 堅持每個問題都要配一個測過的評估器的理由。</p>

<p>🔸 <strong>把分類體系當成版本化的產物</strong>: 先把詞講清楚: 一個 axial code 是「一個錯誤類別」，分類體系(taxonomy)則是「整組類別」合起來的結構，改分類體系，通常就是在新增、合併、刪掉裡面那些類別。原則是: 分類體系一改，就回頭重跑，別讓新舊標準混在一起。而且人該介入的點，是在<strong>分類體系這一層，不是逐條標註那一層</strong>: 讓 agent 提議分類，你來做判斷(合併、刪除、調整、重新加權)，而不是自己一條一條編碼、當人肉標註工。對應的審查介面也該以群集為單位，每個群集直接附幾條範例 trace 連結讓你抽查，並秀出每輪之間的差異(哪些失敗模式是新增的、哪些類別搬了家)。</p>

<h2 id="迴圈在閉合但方向盤還在人手上">迴圈在閉合，但方向盤還在人手上</h2>

<p>把這幾篇串起來，2026 的圖像蠻清楚的: 那個「讀 trace → 找問題 → 修 → 驗證」的改進迴圈，正在被 agent 一段一段接管。差別只在於那個最關鍵的質性分析，你想放在哪裡: 交給你自己的 coding agent(FutureSearch 用一個 skill 配 Claude Code)、讓 agent 在執行的當下自己診斷(Raindrop Self Diagnostics)、還是另外派一個專門的 agent 事後批次掃生產 trace(LangSmith Engine)。同一件「讀 trace、找問題」的事，被擺到了流程裡三個很不一樣的位置。</p>

<p>但 Shreya 的提醒值得貼在牆上: agent 是規模的放大器，不是品味的替代品。它能把上萬條 trace 篩到剩幾十個值得看的問題，這已經是巨大的價值;可是「哪個問題真的重要、這個失敗模式該怎麼定義、修復的方向對不對」，這些需要品味的判斷，方向盤還是握在人手上。</p>

<blockquote>
  <p>編按: 想看一個把這套迴圈跑到生產規模、又刻意保留人類品味的完整案例，可以看小編之前整理的 <a href="https://blog.aihao.tw/2026/06/01/replit-agent-eval-at-scale/">Replit 如何規模化評測和持續改進 Vibe coding</a>，他們用離線的 ViBench 加上線上的 trace 分群 Telescope，雙引擎驅動每天出貨的 coding agent。</p>
</blockquote>

<p>說到底，agent 上線之後本來就是一路邊跑邊修: 你不可能事先想好所有失敗，trace 分析就是你持續發現問題、持續改進的命脈。所以把 agent 做好，從來不只是一個模型問題，而是一個得長期經營的營運問題;而這條營運迴圈裡最累的一段，正是讀 trace，也正是這篇從頭談到尾、最值得想辦法交給 agent 分擔的工作。</p>]]></content><author><name>ihower</name></author><category term="Agent" /><category term="Eval" /><category term="Observability" /><summary type="html"><![CDATA[做 AI agent 產品最不性感、卻最重要的工作之一，就是讀 traces。一條複雜 agent 的 trace 動輒幾十上百輪，裡面藏著工具呼叫、推理步驟、跟 prompt 的來回。過去大家都靠肉眼一條一條掃，掃到眼花，而且根本掃不完。]]></summary></entry><entry><title type="html">當寫 code 不再是瓶頸: Anthropic 的 AI-native 工程組織，與那些不買單的聲音</title><link href="https://blog.aihao.tw/2026/06/01/ai-native-engineering-org/" rel="alternate" type="text/html" title="當寫 code 不再是瓶頸: Anthropic 的 AI-native 工程組織，與那些不買單的聲音" /><published>2026-06-01T00:00:00+00:00</published><updated>2026-06-01T00:00:00+00:00</updated><id>https://blog.aihao.tw/2026/06/01/ai-native-engineering-org</id><content type="html" xml:base="https://blog.aihao.tw/2026/06/01/ai-native-engineering-org/"><![CDATA[<p>Anthropic 的 Fiona Fung (帶 Claude Code 與 Cowork 工程團隊) 有一場演講 <a href="https://www.youtube.com/watch?v=igO8iyca2_g">Running an AI-native engineering org</a>，講的是當 coding 不再是瓶頸之後，他們把整個團隊的工作慣例 (norms) 砍掉重練的過程。小編覺得這是目前少數講得很具體的「AI-native 工程組織到底長什麼樣」第一手紀錄，蠻值得看。</p>

<blockquote>
  <p>編按: 這場演講的中文重點，<a href="https://x.com/dotey/status/2054086398328656383">宝玉 (@dotey) 整理過一個摘要版本</a> (按讚數破四百，傳得蠻廣)，想先快速掃過重點的可以參考。等一下會提到的 Toby 那串反駁，衝著的就是這篇摘要。</p>
</blockquote>

<p>不過這場演講放出來之後，網路上也出現了不少不買單的聲音。剛好 Claude Code 之父 Boris Cherny 最近接受 <a href="https://www.platformer.news/boris-cherny-interview-ai-jobs/">Platformer 訪談</a>又把話說得更滿:「coding 已經被解決了」「軟體工程師這個頭銜今年可能開始消失」。所以這篇就把三件事疊在一起看: Fiona 講了什麼、反對的人在吐槽什麼、以及一票真的在寫嚴肅軟體的人，怎麼反駁「人類不用再寫 code」這個說法。</p>

<h2 id="一fiona-的演講-瓶頸搬家了">一、Fiona 的演講: 瓶頸搬家了</h2>

<p>Fiona 整場的主旋律就一句話: 「過去成就你的，未來不一定還能成就你 (what served you prior may not serve you any longer)」。</p>

<p>她說過去這麼多年，工程師的頻寬 (engineering bandwidth) 一直是最貴的東西，寫 code、寫測試、重構都很花時間，所以我們發明了一堆流程 (瀑布、敏捷、六個月 roadmap、設計文件)，本質上都是因為「打 code 很貴」，要好好規劃才不會浪費。</p>

<p>她也提醒這不是業界第一次得適應。她職涯早期在做 Visual Studio 2005，當年軟體還是燒在 CD-ROM 上出貨 (更早是磁片)，有硬死線要趕著把成品送進工廠壓片、裝盒、上架; 後來能線上發佈，整個出貨方式就被改寫了一次。每次底層條件一變，做事方法就得跟著重來。</p>

<p>但現在這個前提變了。在 Claude Code 團隊，「coding 已經很少是慢的那一環」，而且產出量暴增。瓶頸於是從「打字」搬到了它周圍的所有事情，用她那張投影片的講法: 舊瓶頸是「寫與出貨 code 的頻寬」(寫 code、寫測試、重構)，新瓶頸是「驗證、審查、跨職能協作、安全」。問題從「誰來寫」變成「這段 code 對嗎? 誰來 review? 之後誰維護?」</p>

<p>她有個觀察小編特別喜歡: 很多流程是「悄悄地不再有用 (quietly stops working)」。因為流程很少會自己消失，我們總是一層一層往上疊，疊到後來連 SLA 都要排優先順序。於是 Claude Code 團隊把一大堆慣例砍掉重練。最核心的一句話是: 他們把力氣從「事前」搬到了「事後」，少花時間規劃和爭論，多花時間驗證和對齊。</p>

<div style="display:flex; gap:16px; flex-wrap:wrap; margin:24px 0;">
  <div style="flex:1; min-width:240px; border:1px solid var(--border); border-radius:8px; padding:16px 20px; background:var(--surface);">
    <div style="font-weight:700; font-size:14px; letter-spacing:0.04em; color:var(--text-secondary); margin-bottom:10px;">少做了 ↓</div>
    <ul style="margin:0; padding-left:18px;">
      <li>六個月 roadmap、事前規劃</li>
      <li>每件事都先寫設計文件</li>
      <li>頻繁的產品評審</li>
      <li>白板上的技術爭論</li>
    </ul>
  </div>
  <div style="flex:1; min-width:240px; border:1px solid var(--accent); border-radius:8px; padding:16px 20px; background:var(--tag-bg);">
    <div style="font-weight:700; font-size:14px; letter-spacing:0.04em; color:var(--accent); margin-bottom:10px;">加倍投入 ↑</div>
    <ul style="margin:0; padding-left:18px;">
      <li>驗證與自動化 (shift left)</li>
      <li>團隊對齊與文化</li>
      <li>code review 的人類把關</li>
      <li>用 PR、原型代替文件討論</li>
    </ul>
  </div>
</div>

<p>底下分三塊講她實際改了什麼。</p>

<h3 id="規劃與技術辯論-從先想清楚到先做出來">規劃與技術辯論: 從「先想清楚」到「先做出來」</h3>

<p>規劃變少，而且即時，她叫它 JIT planning (just-in-time，借自 JIT 編譯)。六個月 roadmap 寫完三個月就過期了，所以乾脆少寫; 設計文件大幅減少，改成「與其寫一份 doc，不如丟一個 PR」「有想法就去 prototype」; 產品評審也少做，因為局勢變太快，不如先做原型、讓內部大量試用、再出貨收真實回饋。</p>

<p>至於「加倍投資的驗證」，她叫它「左移 (shift left)」: 與其等 code 出去後自己搶在使用者之前抓 bug，不如用更多自動化，在更接近源頭的地方就攔下來。她講了個很有共鳴的例子: 有次她修了個 bug，隔天看到有人在 Boris 的 thread 上回報「我發現一個 bug」，當下那種「該不會是我吧」的下沉感，所以她希望不管什麼角色，每個人對自己送出的改動都更有信心。</p>

<p>技術辯論也變了，她的講法是「code 說了算」。她剛加入時想做一個重構，本來想拉 Boris 去會議室白板大戰，後來想想，不如直接讓 Claude 生成三個不同版本的 PR，連對所有 API 呼叫端的影響都一起攤開來看。「當『做出來』很便宜，『吵架』就變貴了」。但她特別強調，這反而讓「團隊文化與對齊」更重要，絕對不能變成「最後一個 check-in 的人贏」。</p>

<h3 id="程式碼的歸屬與審查-誰寫的變成一個怪問題">程式碼的歸屬與審查: 「誰寫的?」變成一個怪問題</h3>

<p>因為所有 PR 都是 Claude 輔助生成的，「這段 code 是誰寫的?」現在問起來有點怪。她的建議是「往下追問 (double click)」你真正想問的是什麼: 是想找誰造成了這個 regression? 還是想找懂這塊的專家? 還是只是想了解脈絡? 想清楚之後，很多都能自動化，她甚至把每天早上配咖啡、進客戶回饋頻道請 Claude 摘要的儀式，直接變成了一條 routine 自動跑。</p>

<p>至於 code review，重點是分清楚哪裡信任 Claude、哪裡還是要人。風格、lint、補測試、甚至抓些小 bug 並修掉，這些重度交給 Claude; 但本著「信任但要查證 (trust but verify)」，法務審查、信任邊界與安全敏感的 code、產品品味，她還是堅持要拉真人專家進來。(她講了個好笑的例子: 有次想把終端機裡的 Claude 裝飾成雪人，結果做出來像花生人 Mr. Peanut，是設計師夥伴一眼點出來的。品味這種事，模型還沒接手。)</p>

<p><img src="/assets/images/ai-native-engineering-org/code-reviews.jpg" alt="" /></p>

<blockquote>
  <p>編按: 「你們怎麼跟得上 code review?」是她說過去半年被其他工程主管問最多的一題。這也呼應了新瓶頸的核心: 當生成變快，驗證與審查就成了真正的壓力點。這個主題小編之前整理過一整篇 <a href="https://blog.aihao.tw/2026/04/20/ai-code-review-bottleneck/">AI 時代的 Code Review: 當 Pull Request 成為新瓶頸</a>，挑了五篇 2026 年的文章談 review 跟不上之後的各種路線 (規格驅動開發、直接幹掉 review、五層信任框架…)，想往下深挖的可以接著看。</p>
</blockquote>

<h3 id="團隊組成與組織形狀">團隊組成與組織形狀</h3>

<p>她重度看兩種工程師: 「有產品 sense 的創意建造者」跟「深度系統專家」(例如做 Claude Code Remote 這種分散式系統就少不了後者)，反而比較不看重「原始產出量」，因為那個模型已經幫你補上了。角色也在互相滲透: 她是工程師、自認文筆很糟，就讓 Claude 當她的「內容設計」夥伴，幫忙把問卷文案改得精簡 (終端機裡每一行空間都很珍貴); 反過來她的 PM 也大量在寫 code。</p>

<p>組織則盡量扁平、scrappy，她甚至要求每個 manager 都先從 IC 做起、親手 dogfooding，招募夥伴一度覺得她瘋了，說「哪個 manager 會願意先當 IC」。團隊還立了三條核心原則: ① 每個成員 (含跨職能夥伴) 都要用 Claude Code; ② 能 Claude 化的就 Claude 化 (Claudify everything); ③ 明確授權大家去砍掉沒用的流程。</p>

<p>最後談「這套到底有沒有用」，她給了三個可參考的指標: onboarding 上手時間大幅縮短、PR 週期時間縮短、Claude 輔助的 commit 比例上升 (她說過去四個月幾乎沒看過非 Claude 輔助的 commit)。但她也提醒: 不要盯著新聞標題那種「本公司 X% 的 code 由 AI 生成」，要回去看你真正想解決的問題、想做得更好的產品是什麼，品質與可靠度才是該盯的。</p>

<p>小編覺得這場演講最實在的地方，是它不講願景、只講「我們實際改了哪些慣例」。而且 Fiona 自己留了幾個沒答案的問題給自己 (iOS/Android 還要不要分隊、自動審查要推多遠、角色模糊後怎麼讓大家都覺得有產出感)，姿態其實蠻誠實的。</p>

<p>她最後留給聽眾一個可以帶回去做的事: 挑一個你最「吵」的工作流程 (最貴的、或大家最不想碰的那個)，問一句「它還有在服務當初的目的嗎?」。她舉了個例子: 以前有個 50 人的週會，她發現大家全程都在滑筆電、只有輪到自己報告時才抬頭講兩句，於是她問了句「我們到底為什麼要開這個會?」，然後就把它取消了。</p>

<h2 id="二反方一-toby-的吐槽">二、反方一: Toby 的吐槽</h2>

<p>不過工程師 <a href="https://x.com/TobyAtLarge/status/2058591481951400240">Toby (@TobyAtLarge) 就不太買單</a>，他針對<a href="https://x.com/dotey/status/2054086398328656383">宝玉那篇演講摘要</a>寫了一串挺兇的反駁 (他自己也自嘲「好擔心自己變成槓精」)。幾個重點:</p>

<p>1️⃣ <strong>「code 幾乎免費」是演講修辭，不是事實</strong>。code 確實便宜很多了，但「好的 code」(考慮架構、邊界條件、測試覆蓋) 仍然有成本。而 Fiona 自己也承認「維護成本不會跟著歸零」，代表她心裡清楚，只是台上為了衝擊力把話說滿了。</p>

<p>2️⃣ <strong>Source of truth 不只一層</strong>。她說「code 是唯一的事實來源」太絕對。真實工程至少三層: 需求 (為什麼做) → 設計 (怎麼做) → code (做出來了)。code 是執行結果，常常處在折中狀態。沒有需求文件做參照，你連「這個折中是刻意的還是 bug」都分不清。</p>

<p>3️⃣ <strong>JIT planning = 沒有規劃</strong>。他覺得對一個正經的 toB/toC 產品不做規劃很誇張，「先發 PR、少開會」容易導致功能失控。而且諷刺的是，從使用者端看，Claude Code 的發布節奏看起來反而是有規劃、有節制的，不像她描述的那種野蠻生長。</p>

<p>4️⃣ <strong>整體判斷: 特殊環境裡的個人經驗，被包裝成了普適方法論</strong>。Anthropic 是極端特殊的環境: AI-native 產品、極高人才密度、公司本身就是最好的 dogfooding 場景。在這種環境跑一年的實驗，外推到「所有工程團隊都該這樣」，折扣要打得很大。</p>

<p>小編覺得 Toby 第三點對 open question 的批評有點過嚴 (她明明就說那是「還在想的問題」)，但第一點跟第四點蠻值得記住的。其實 Fiona 自己在台上就講了「do what makes sense for your team」，她沒要你照抄。真正的風險是聽眾，很容易把 Anthropic 的做法當聖經，忘了自己的 codebase 和人才密度根本不是那回事。</p>

<h2 id="三boris-把話說得更滿-coding-is-solved">三、Boris 把話說得更滿: 「coding is solved」</h2>

<p>如果說 Fiona 還算克制，Boris Cherny (Claude Code 的創造者) 在 <a href="https://www.platformer.news/boris-cherny-interview-ai-jobs/">Platformer 的訪談</a>裡就直接得多了。他說自己已經超過六個月沒寫過一行 code、對他做的工作來說 coding「基本上解決了」，還公開預測「軟體工程師」這個頭銜今年就可能開始消失，融化成某種「builder」的角色，因為他身邊的設計師、PM、主管現在全都在寫 code。</p>

<blockquote>
  <p>Claude Code 已經有超過半年是 100% 由 Claude Code 寫的。Boris 說他去 Y Combinator 最新一批新創做分享，問「有誰 100% 的 code 是 Claude Code 寫的」，現場一半的手舉起來; 問「有誰完全沒用模型寫 code」，兩百多人裡只有一隻手。</p>
</blockquote>

<p>不過小編要幫他說句公道話，因為這句話常常被斷章取義。Boris 在訪談裡其實有把 caveat 講清楚: <strong>「對我做的那種 coding 來說，coding 已經被解決了」</strong>。他做的是相對簡單的 codebase (Claude CLI 還很新，桌面和 App 都是小型、單純的 codebase)。但 Anthropic 也有 NASA 這種大型、超複雜 codebase 的企業客戶，「對他們來說還沒解決，模型還是會出錯」。而且他強調 coding 只是工程師工作的一小部分，他以前大概 50% 時間在打 code，另外 50% 在跟使用者聊、腦力激盪、debug、規劃。他甚至預測未來「寫 code / 用 agent 寫 code 的人」會比現在多 100 倍。</p>

<blockquote>
  <p>編按: 問題就出在這個 caveat 在傳播時被剝掉了。新聞標題只留下「coding is solved」「工程師要消失了」，後面那半句「for the kinds of coding that I do」不見了。下面這群人反駁的，其實主要是被剝掉 caveat 之後的那個版本。</p>
</blockquote>

<h2 id="四多方反駁-瓶頸沒消失它搬到了判斷力">四、多方反駁: 瓶頸沒消失，它搬到了判斷力</h2>

<p>小編搜尋了一輪這半年的論戰，發現一件有意思的事: 真正天天在用這些工具寫嚴肅軟體的人，幾乎沒有人站「人類不用再寫 code」這一邊。他們的反駁很一致: 生成 code 從來就不是軟體工程的難處。</p>

<p><strong><a href="https://simonwillison.net/2026/May/6/vibe-coding-and-agentic-engineering/">Simon Willison</a></strong> 把 AI 寫 code 拆成兩個世界: 「vibe coding」(完全不看 code，壞了再求模型一次) 跟「agentic engineering」(專業工程師用 agent 放大自己的專業)。他不怕飯碗不保，因為這些工具是「既有經驗的放大器」，你越懂跑得越快。他有句話講得很白: 「做出軟體是一件兇殘地困難的事，給我全世界所有的 AI 工具，我們想達成的目標還是非常難。」</p>

<p><strong><a href="https://simonwillison.net/2025/Sep/29/armin-ronacher-90/">Armin Ronacher</a></strong> (Flask、Jinja 作者) 的例子更具體。他新公司的基礎設施九成以上是 AI 寫的，但他點出: 要用到這個程度，「你得知道 goroutine 和 thread 的差別、得懂 rate limiter 為什麼需要 jitter」。結論是: 「這些工具不會取代程式設計師，它們讓我們能在更高的層次施展專業。」</p>

<p><strong>Mario Zechner</strong> (Pi agent 框架作者) <a href="https://simonwillison.net/2026/Mar/25/thoughts-on-slowing-the-fuck-down/">講得最直白</a>: 人類是個瓶頸，而這恰恰是一種保護。</p>

<blockquote>
  <p>「人類沒辦法在幾小時內吐出兩萬行 code。但一支被編排好的 agent 大軍沒有瓶頸、沒有疲累，那些看似無害的小錯會以不可持續的速度複利累積。你把自己移出了迴圈，根本不知道這些小錯已經長成了一隻怪物，等你感覺到痛，已經太遲了。」</p>
</blockquote>

<p>最後是 <strong>Matt Asay 在 <a href="https://www.infoworld.com/article/4176534/ai-still-needs-humans.html">InfoWorld</a></strong> 的提醒，正好打中 Fiona 那個「別盯著 AI%」的點: 對管理者來說，最糟糕的指標就是「AI 生成 code 的百分比」，真正該看的是缺陷有沒有變少、事故有沒有減少、客戶有沒有更開心。他總結得很到位: 「AI 並沒有消除工程紀律的必要，它只是提高了『沒有紀律』的代價。」</p>

<h2 id="所以呢">所以呢?</h2>

<p>把這一圈聲音疊在一起，會發現它們其實沒有互相矛盾，反而拼出了一個共識。</p>

<p>Fiona 在台上已經親口說了答案: 瓶頸搬到了「驗證、審查、安全」。只是到了 Boris 的 slogan 裡，後半句被吃掉，變成了爽快的「coding is solved」。但這兩件事是一體的: code 變便宜，不代表「做出好軟體」變便宜，它只是把成本從「打字」搬到了「判斷、驗證、長期維護」。而那恰好是最難自動化、最吃經驗的部分。</p>

<p>有意思的是，連把話說得最滿、喊出「coding is solved」的 Boris 自己都留了一手。訪談最後問他要不要把「在 X 和 Threads 上回覆使用者 bug 回報」這件事也自動化掉，他說: 「我已經自動化了，但我還是偏好自己來。」原因很簡單: 跟人互動、聽人說哪裡壞了，是他最喜歡的部分。你看，連他都選擇把這件事留在迴圈裡親自做，而那正是判斷與品味。</p>

<p>所以與其問「人類還要不要寫 code」，不如問一個更實際的問題: 當生成幾乎免費，你打算把省下來的時間拿去做什麼? 是塞進更多沒人看得懂、沒人 review 的 code，還是拿去想清楚到底該做什麼、把它做對?</p>

<p>Armin 在<a href="https://lucumr.pocoo.org/2026/3/20/some-things-just-take-time/">另一篇文章</a>裡有句話，小編覺得很適合收尾: 「沒有人能量產一棵五十歲的橡樹，也沒有人能用一個週末的衝刺，變出信任、品質或社群。」生成速度可以無限快，但有些東西，就是需要時間。</p>]]></content><author><name>ihower</name></author><category term="Coding" /><category term="Agent" /><category term="Industry" /><summary type="html"><![CDATA[Anthropic 的 Fiona Fung (帶 Claude Code 與 Cowork 工程團隊) 有一場演講 Running an AI-native engineering org，講的是當 coding 不再是瓶頸之後，他們把整個團隊的工作慣例 (norms) 砍掉重練的過程。小編覺得這是目前少數講得很具體的「AI-native 工程組織到底長什麼樣」第一手紀錄，蠻值得看。]]></summary></entry></feed>