PART 3 · 時機 ①
(時機 ① 在工具執行內)
工具呼叫內,有三個可以設計的地方:
確定性檢查參數與內容,
擋掉危險或無效的呼叫
對結果做品質檢查,
必要時就地修復或重試
tool response 不只回資料,還夾帶指示與 metadata,引導 Agent 的下一步
我們來看在工具執行內裡面可以做怎樣的回饋。在 Tool Call 裡面,單一的 Tool Call 裡面我們可以有三個可以設計的地方:
我們可以在執行工具前做驗證輸入、執行工具後做檢查結果做品質的檢查,然後另外我們可以設計它的工具的回傳值,是可以夾帶指引的。
寫程式習慣把回傳值當「資料」: 查到什麼回什麼,出錯就回傳 error code
這裡有個很重要的概念,就是說工具輸出不只是 function 的 output,是你寫給 Agent 的回饋。
因為對工程師來說我們蠻習慣就是那個 function 其實就是回傳資料,查到什麼就回傳什麼,如果有錯誤就回傳 error code。但其實對 Agent 來說,這個 tool 的 response 其實也是一個對話訊息,所以我們是可以在裡面夾帶指示的,甚至也可以夾帶更多的資訊的 metadata,來告訴跟暗示 Agent 下一步可以怎麼做。
使用者用自然語言問財務數據,
Agent 基於 DB schema 產生 SQL
用 execute_sql(sql_query) 工具查詢。
我們來具體的舉個例子,比如說做一個 Text-to-SQL 的 Agent。使用者可以用自然語言去問你的財務數據,所以我們的 Agent 會根據 DB schema 產生 SQL,然後 SQL 再去查資料庫。這樣的一個 Agent 最重要的工具就是叫做 execute SQL,我們會定義一個 function 叫做 execute SQL,它的參數就是 SQL query,所以大模型的任務就去產生那個 SQL。
這裏的前饋的部分是什麼?就是我們會在 System Prompt 裡面插入 table schema 的描述跟定義,以及各種 SQL 的限制。
import sqlglot
from sqlglot import exp
tree = sqlglot.parse_one(sql_query, dialect="postgres")
# 1. 只允許 SELECT(擋下來)
if not isinstance(tree, exp.Select):
return "只允許 SELECT 查詢"
# 2. 資料表白名單(擋下來)
for table in tree.find_all(exp.Table):
if table.name not in ALLOWED_TABLES:
return f"資料表 {table.name} 不在允許範圍,可用:{ALLOWED_TABLES}"
# 3. 沒 LIMIT 就補安全上限(補強)
if tree.args.get("limit") is None:
tree = tree.limit(200)
safe_sql = tree.sql(dialect="postgres") # 確保輸出 postgres 語法
這裡我更關注後饋怎麼做。雖然前饋你會寫如何產生 SQL ,但是模型產生 SQL 的問題是,它可能會幻覺不存在的表格或欄位,或是生成查詢以外的危險語法(通常我們是希望它只有查詢),或者是它忘記要分頁等等,或者不同資料庫的 SQL 語法不一樣,大模型可能很熟悉 SQLite 或 PostgreSQL,但是其實它可能不熟悉一些比較冷門的資料庫的語法,所以可能會很容易生錯的。
所以我們可以在工具內把大模型產生的 SQL 語法解析成語法樹,做確定性的檢查跟改寫。它可以檢查說只能允許 SELECT,只能允許我白名單的資料表跟白名單的欄位,然後它可以自動幫你修改這個 SQL 加上 LIMIT。因為我們不希望 SELECT * 全部,一次撈出幾萬筆的資料是不行的,所以我們會加上一個 LIMIT 限制,要求它一定要做分頁。
這裡還可以做用戶的權限檢查,在這邊補上它一定會有的某些條件,就是有些事情就是不需要靠大模型生的,我們可以用確定性的語法補上我們需要的限制條件。最後我們再輸出轉成合法的我們需要的資料庫的語法。
驗證沒過時,tool response 的品質決定 Agent 自我修正的能力
ERROR: column "revenue_growth"
does not exist
Agent 只能瞎猜重試,越改越歪。
欄位 revenue_growth 不存在。
financial_metrics table 可用欄位有:
revenue, revenue_yoy, gross_margin…
和 revenue_growth 最相似的欄位有 revenue_yoy, revenue_qoq
下一步直接被導正。
執行工具有錯誤的時候,我們也必須設計它錯誤的訊息。比如說有一個欄位不存在,左邊是不好的訊息,就是直接告訴它這個欄位不存在。那右邊我可以設計更好的錯誤訊息說這個欄位不存在,但是你查的這個 table 還有這些欄位,然後我還可以用相似性的搜尋跟它提示說跟這個最相似的欄位還有 ABC 可以選。
所以 Agent 透過這個錯誤訊息就可以更好的去引導它,下一次再做對的機會。
{"success": true} 或 {"count": 0}
把規模與範圍寫清楚:
更多資訊,Agent 才有依據判斷「這次結果合不合理」,是否要修正
甚至成功也可以設計。找不到零筆,但找不到零筆是真的沒有結果,還是查詢不正確?改了幾筆也不知道。所以其實就算沒有發生錯誤,我們也可以回傳更多的狀態資訊,總共影響了幾筆、動了哪些欄位等等,提供更多的資訊,那 Agent 才有能力去判斷說這次的結果合不合理、是否需要修正。
所以錯誤要設計,成功也可以設計。一個陽春的成功 flag 也可能是一個假完成的訊號。
各種純運算式(Computational)的檢查,回傳的是對應的固定訊息:
def collect_feedback(output, source):
# 純運算式檢查:規則直接判定,不靠 LLM,沒過就回寫死的固定訊息
msgs = []
if count_words(output) > 500:
msgs.append("摘要超過 500 字,請縮短")
if missing_required_terms(output):
msgs.append("漏掉必留的專有名詞,請補回")
if cosine_similarity(output, source) < 0.8:
msgs.append("與原文語意偏離,請貼近來源重寫")
return msgs
回事先寫死的固定字串:又快又穩又可重現。
若沒辦法用純運算式的檢查,也可用 LLM 當 judge,附上回饋的 reasoning 理由。
那這些工具回傳的引導訊息,剛才都是純運算式的檢查,所以它回傳的東西其實都是我們預先寫好的固定訊息。假設你有一個檢查是檢查字數一定要小於 500 字,假設超過 500 字的時候我們就有個回傳說「摘要超過 500 字請縮短」。所以你回傳的不是一個 error code,是一段引導 Agent 的訊息。這是左下角運算式的檢查,它是一個固定的訊息。
我們待會也會講到可以做成語意的,因為有些事情可能沒有辦法用完全的運算式來做的話,我們可以在工具裡面再塞一個大模型的 prompt 去做語意的回饋,比如說檢查品質好不好,然後在那個 prompt 裡面也會要求輸出一些分析的理由,在工具內用那個 prompt 得到結果之後再回傳。所以這是語意的 judge。
注意控制別塞滿 context。context 過了某個門檻,模型會變笨(context rot)
別忘了 Context Engineering 的整套策略,各種縮減 context 的技術:
硬性工具輸出上限、頭尾保留 (切頭切尾)、卸載到檔案 (只留預覽 + 路徑,有需要 Agent 再打開看)
關鍵字、向量檢索、reranker 等,只挑選相關的 context
龐大的中間過程交給 sub-agent 在獨立 context 跑,只回傳結論
另外工具內設計工具另外一個重點就是我們必須要非常有意識的去控制 context window,因為 context 如果太長的話模型就會變笨。這一塊其實是我們 Context Engineering 蠻大一部分都在講這個策略。
如果去看 Context Engineering 是有很多縮減 context 的技術,比如說 Coding Agent 最喜歡用的 Context Offloading 的技術,就是所有工具的輸出一定都要硬性限制輸出的上限,我記得 Claude Code 好像是兩萬五千個字元,他們讀檔進來的時候都會只保留頭尾,把中間卡掉。然後如果搜尋的東西他一定會先存到檔案,然後 Coding Agent 再去用搜尋的方式、grep 的方式搜出來,避免一次把大量的東西塞到工具的輸出裡面。
然後 Context Engineering 還有很大部分在講檢索的技術,關鍵字檢索、向量檢索的 branch,重點就是說只挑出需要的資訊回傳。
然後第三個 Context Engineering 會做的是 Sub-Agent,就是讓 Sub-Agent 去處理龐大的中間過程,最後只回傳結論。那這些都是有意識的去控制 context window。
知識庫問答 Agent 會配一個 search_knowledge(query) 工具 (關鍵字搜尋+向量搜尋 等)
工具不只回傳搜尋結果,我們還可以回傳以下資訊,回饋給 Agent 更多資訊:
回傳時標上來源和相關度分數,讓 Agent 有依據判斷哪些該引用、哪些該丟
好幾個 chunk 都來自同一份文件 → 暗示整份相關。可以指示 Agent 直接把整頁讀回來,比散落片段好用
回傳不只 top-k 搜尋結果,還夾帶整個資料集的統計
我們看第二個案例是 RAG,如果你做一個知識庫問答的 Agent,那你就會配工具叫做搜尋,搜尋的工具通常比如說這邊叫做 search knowledge,會有一個參數是 query,就是大模型產生一個查詢的字串。那在這工具裡面可能是關鍵字搜尋或向量搜尋。
那這邊我想要帶給各位就是這樣的搜尋工具,其實我們設計的時候不只可以回傳搜尋結果,我們還可以回傳更多東西。
第一個是我們可以回傳標註它的來源跟相關性的分數。
第二個是我們可以暗示它,就是如果你搜尋了好幾個 chunk 其實都來自同一份文件,你可以額外加一個指示說請 Agent 把整份文件讀出來就好。一份文件拆成十個 chunk,結果我們搜出來有六個 chunk 都出自這份文件,最好的理解方式是不是請 Agent 把整份文件都讀完,而不是只有讀那六個 chunk。
那第三個可以做的事情是我們可以在回傳 top-k 之外再夾帶整個資料集的統計數據。
<results query="API timeout issues">
<ticket id="LIN-1247" status="Done">…</ticket>
<ticket id="LIN-1189" status="Done">…</ticket>
</results>
<facets>
<status_facet> <value name="Done" count="6"/> <value name="Open" count="5"/> </status_facet>
</facets>
<system-instruction>
回傳的 2 筆全是 Done。facets 顯示另有 5 筆 Open 沒進 top-k,請改用 search(query, status="Open") 追查進行中的問題。
</system-instruction>
<facets> 揭露隱藏資料告訴 Agent:符合的不只你看到這 2 筆,還有 5 筆 Open 沒進 top-k
<system-instruction> 額外的指示直接在工具輸出裡告訴 Agent 下一步該怎麼搜
我們下面舉個例子。上面是我設計的工具回傳值,假設它搜出來兩筆,這可能是一個專案管理系統,搜出來兩筆是已經完成的 Done 的資料。那我下面其實還有一個欄位是告訴它總共 Done 的有六筆、總共 Open 的有五筆。
所以我不只告訴它我搜出來的那兩筆,我還告訴它統計的資訊。我下面還有一個額外的資料告訴它說,你搜出來的兩筆都是 Done,但是還有五筆是 Open 的,你可以修改你的搜尋條件再搜。
重點就是說我們可以除了回傳搜尋結果之外,我們還可以夾帶指示以及夾帶統計的資訊,幫助 Agent 去理解全貌跟決定他的下一步行為。
呼叫另一個模型做 review,把回饋當 tool response 帶回來。從主迴圈看,這仍是一次普通的 tool call。
@function_tool
async def consult_model(question, files, model):
"""遇到難題、想確認方向、或要 review 產出時呼叫,
交給另一個模型給獨立回饋。"""
...
例如: RAG Agent 生成答案後,用另一個模型檢查有沒有幻覺。
語意的 judge 就是在工具裡面我們呼叫另外一個模型來做 review。比較常見的例子可能是 RAG 的 Agent 生成答案之後,我們用另外一個模型檢查他有沒有幻覺。
這種做法在 coding agent 上也蠻常見
/codex:review: Claude 把本地 git 改動交給 Codex(GPT) 做 code review,並說明「只做 review、不准順手改」。一個寫 code、一個分析審查。
這是人工觸發,也可以寫進前饋規則,說明「在流程中何時、遇到哪類情況就呼叫」
這個案例其實在 Claude Code 裡面有一個 plugin 叫做 Codex plugin,就是你可以在 Claude Code 裡面裝這個 plugin,然後叫 Codex 幫你 code review,其實也是叫另外一個模型來做 review。
例如 SQL 語法驗證用 SQLGlot,又快又穩
細緻品質好不好沒有確定性 assert 可寫,
使用 LLM judge 補規則型檢查的不足
錯誤訊息要附「該怎麼辦」,不是只丟一個程式 error code。
工具層回饋在 tool call 內發生,要注意 latency,別拖慢整個迴圈。
總結一下工具層的 Harness 設計原則:
PART 4 · 時機 ②
(時機 ② 兩次 model request 之間,把訊息注入執行中的 Agent)
一輪 (Turn) 不是「一問一答」,而是「模型 request → tool calls → 模型 request → …」反覆,直到輸出答案。注入就發生在這個迴圈內部、還沒輸出答案前:
接著我們來看第二個時機點,就是在 agent 一輪之內,兩個 model request 之間,可以把訊息注入這個執行中的 Agent。這時機點在哪裡?所謂的一輪,其實內部是連續的工具呼叫,而在工具呼叫之間,我們有一個時機點可以插入訊息給 AI。
注意到這個時候 AI 還沒有回傳給你最後的答案,他還在執行過程,是要我們主動插進來訊息。
Agent 還在反覆呼叫工具、還沒輸出答案時,就把新訊息插進去,馬上影響它接下來的動作
使用者就主動輸入一段話介入,叫做 steering 轉向/引導。
在 Codex、Claude Code 這類互動式 coding agent 最常見,是內建功能。
例如背景工具程式跑完送回結果、外部事件 (webhook、告警) 要讓 agent 知道。
目前較少框架實作,少數像 Pydantic AI 做成通用機制。
這比較常見的現在有兩個用途。第一個是人中途去插進去的,最常見的是你在 Claude Code 或者是在 Codex 裡面,如果 AI 還在輸出,你開始打字,他就會加入一個 queue 的排程。但是 Codex 他不會馬上插入,他後面有一個小小的字叫做「引導」,所以你點了引導之後他才會用這個 steering 插進去,不然的話他那個只加入排程,他會等到全部最後輸出完之後才放進去。
那第二個用途是程式的注入,就是如果我們做一個跑背景的 job、背景的工具的時候,我們可以用這個方式在背景 job 工具執行完之後盡快的把工具結果插進來。
已經發出 tool calls 但還沒拿到結果,這時若添加 user message 回傳,API 會錯誤
assistant 發了 tool calls → 還沒等到所有工具結果 → 直接加 user message 回傳。下一個 request 送出就 400。
assistant 發了 tool calls → 每個 call 的結果都補齊 → 再加 user message → 下一個 request。
這裡有一個小小的注意,就是說它只能在 request 之間。AI 叫我們執行工具 ABC,我們一定要回傳工具的結果 ABC 才能夠插這個東西,你不能說工具執行沒有結果你就插入這個訊息,API 會跟你報錯。所以這是 API 後端的限制,OpenAI、Claude 都是這樣子的。
最常見的用途是人主動介入。steering 和 interrupt 都是 Codex、Claude Code 的內建功能。這跟 human-in-the-loop 不一樣:
你刻意設計一個等待點,讓 agent 跑到那裡停下來問你 (最典型: 執行某工具前先請使用者確認)。
是 agent 停下來問你。
agent 沒有等待點、也不打算停,是你在它還在執行時主動輸入 (通常因為「需求變了」)。
是你主動介入。
剛剛有提到一個用途是人中途插進去,那這個跟 human-in-the-loop 是不一樣的。通常我們講 human-in-the-loop 是我們刻意設計一個等待點,讓人類來回答,比如說你在工具裡面設一個等待點,有一個 UI 等用戶輸入才下一步,這個叫 human-in-the-loop。跟我現在做的 steering 是不一樣的,現在這個是 Agent 沒有等你的,他也沒有打算停的,是你要主動介入的。
訊息先排進佇列,等當前 tool calls 結果到齊、正要發下一個 model request 前才注入。
agent 沒中斷,只是下一步多了你的訊息,已建立的 context 全部保留。
取消正在執行的工具,沒答完的 tool call 硬塞 tool_result,內容是固定字串 aborted ,維持對話 API 合法。
那細分的話其實還有可以分兩種:不中斷的是 steering,他會等到這一批工具執行完拿到結果之後才插入。另外一種是真的強制中斷的,連工具我都不想要執行我就想要插進來,這種比較少見。
而且我剛才不是說工具沒有執行完不能插入嗎?那要怎麼辦?如果你真的要強制中斷的話,我們得塞一個假的工具回傳值叫做 aborted(放棄),你得在 API 的訊息裡面跟他說這個工具的執行結果是放棄的。塞了放棄之後你才能塞用戶的訊息進來,這是強制中斷。
用途常見有:
長時間工具在背景跑完,把結果送回 agent。
webhook、錯誤告警、聊天平台訊息主動送進來。
enqueue,兩種模式: asap(盡快插入,就是這篇的時機點)與 when_idle(整輪結束後才插入)。程式注入另外一個用途,假設你有一個背景的工具結果想要插進來,或是外部事件等等想要盡快插進來的話,他要怎麼做呢?
首先如果你想要做一個背景工具的話,你第一次 call 工具的時候,因為我們想要把它丟到背景去跑,所以第一次的 call 工具它的回傳值會是說這個東西已經被丟到背景去跑了,然後工具執行就結束了。
它分兩步:
tool call 後不等跑完,馬上回 tool_result,內容是固定的:
只能用 user message 插入:
注意不能用 tool_result 訊息了,因為 API 的 tool call 跟 tool result 是一對一配對的。
那第二步是當我的背景 job 跑完的時候,我要插進來的時候,這時候你已經不能用工具的回傳值,不能放在 tool result 的 message,因為工具的執行跟工具的結果那個訊息是一對一配對的。所以工具執行完的時候你不能再用 tool result,我們只能用這個時機點,用 user message 插進來,把工具的結果用 user message 的形式插進來,這是蠻特別的。
PART 5 · 時機 ③
(時機 ③ 單輪結束的 Goal 與 Outcomes)
我們看一下每輪結束的時機點。這個時機點會在於每一輪 AI 他想要呼叫的工具都呼叫完之後,他要輸出完最後的答案、文字答案之後的這個時機點,也就是第三個時機點。
給 Agent 一個目標:
讓 Agent 持續修正,直到通過為止。
這是 Codex 和 Claude Code 的內建功能。
不能因為「模型覺得大概做完了」
就算數。
完成與否,要對著停止條件驗證,
沒過就繼續做。
這個功能在最近在 Codex 跟 Claude Code 都是一個新的內建功能,它叫做 Goal,就是你可以用一個斜線 Goal,然後去設定它一個可以驗證的停止條件。如果它沒有滿足那個條件的話,它就會強迫模型再跑一輪。
所以我們會給 Agent 一個目標,描述我們希望的最終狀態、用什麼證據去驗證、有哪些限制需要遵守,然後讓 Agent 強迫去跑讓它直到這個條件通過為止。
好的 Goal 不一定是更長的 prompt,而是一份精簡的契約:
/goal <desired end state> ← 期望的最終狀態
verified by <specific evidence> ← 用什麼證據驗證
while preserving <constraints>. ← 同時要保住什麼
Use <allowed inputs, tools, boundaries>. ← 允許的工具與邊界
Between iterations, <how to choose the next best action>.
If blocked or no valid paths remain,
<what to report and what would unlock progress>.
讓 AI 幫你寫:任務夠清楚時,直接
Help me turn this into a strong /goal: <任務描述>
好的合約模板大概講這樣子,它算是一份精簡的契約,這出自 OpenAI 的 Cookbook。如果不會寫的話,其實現在都叫 AI 寫,你就跟他說請幫我把我的任務描述轉成一個 strong Goal,它就幫你轉成上面的形式。
沒有可靠的完成條件,
定義不出來?那問題不在工具,
在你還不知道自己要什麼。
這邊是不適合用 Goal 的時候,就是條件模糊的時候比較不適合,如果你能夠清楚地定義的時候會比較適合。
藏在 Goal 背後的,是交付物的轉變:
有各種不同實作,讓我幫你分析。
所以我們使用 Goal 的時候會有一個角色的轉變,變成說我們設計每個 turn 變成每個 run,你交付的不再只是一個 prompt,而是一份目標跟邊界的一個契約。因為我們會讓 Agent 持續去跑然後讓他得到最後的結果。所以思考單位變成從單一的 turn 變成一輪的工作的感覺。
由主模型自我審計,自己呼叫 update_goal 宣告
每輪結束,交給獨立的 Haiku 小模型裁決
由獨立的 grader agent 拿 rubric 評分標準,去檢查產出物做驗收
那關鍵就是他到底怎麼判斷東西做完了沒。所以這裡有三種不同的實作。我深入研究了三種不同的產品實作:Codex 的 Goal、Claude Code 的 Goal,以及第三個是 Claude Managed Agents 裡面有一個 Outcome 的功能。
這三種其實都看起來好像一樣,但其實實作上蠻不一樣的,我帶大家來解析一下。
Codex 沒有第二個模型在做判定,是自我審計
update_goal(complete)更新狀態update_goal(complete)第一個是 Codex Goal,它其實是沒有第二個模型在做判定的,它其實是自我審計的。這個 Agent loop 它其實有點像是一個確定性的狀態機,它有一個工具叫做 update_goal,就是模型認為它已經完成的時候就會呼叫這個工具 update_goal 把狀態改成完成。
所以當它判斷狀態是完成的時候它就脫離回圈回傳最後的答案。如果它判斷還沒有完成的時候,它會強迫注入一個叫做 continuation 的 prompt,就是如果它沒有完成它會強迫注入一個叫它繼續做事的 prompt。
這個繼續做事的 prompt 裡面: 會去要求說請再去找更多的證據來去證明我的目標已經完成了,請你再去找證據、請你再去做事情讓它下一輪能夠做完。所以如果它判定它沒有做完的時候,它其實是強迫硬塞一個 user message 就是 continuation prompt 塞進來讓它再跑下一輪。
在 Codex 裡面這個判定者就是主模型它自己,它判定的材料是 Agent 自己的完整的對話紀錄跟推理脈絡。
大概的流程就是從中間開始主 Agent 跑一輪之後,它會自我審計,如果它沒有達成目標的話它會再注入這個 continuation prompt 強迫它再跑下一輪,直到它最後更新它的狀態就出去了。這是 Codex。
判定權在主模型手上,Codex 用 prompt 要求以「當前 worktree 與外部狀態」去自查
update_goal 的 description 寫好寫滿:
Set status to
completeonly when the objective has actually been achieved and no required work remains.
要求模型逐項找證據:
"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"
每輪注入的 prompt 都一樣 (只有 Budget 數字不同)
那它怎麼防範它過早宣告完成呢?一個是透過它的 continuation prompt,一個是它的 tool description 也會寫好寫滿,就是說你必須要在目標完成的時候才能夠呼叫這個工具。
有些技術文章寫: Codex 的 Goal「用一個獨立小模型每回合評分」
沒有第二個 model、沒有額外的 grader
所以這個就是不能只看名字要看實作。有些坊間的技術文章可能會寫說 Codex Goal 用一個獨立的小模型每回合評分,看到獨立的小模型這邊文章我就會打算跳過了,因為它根本沒有在看技術的實作。很多 Harness 的技術細節就是不能靠幻覺想像是怎麼做的,還是要看一下它的文件,最好能看到它的 source code。
Claude Code 把判定交給另一個模型。當主迴圈(Opus)輸出答案時,harness 不交還控制權,而是發一個 request 把整段對話交給一個獨立小模型來審計 (類似 fork):
Based on the conversation transcript above,
has the following stopping condition been satisfied?
<stopping condition>
Answer based on transcript evidence only.
注意: 這只會看對話紀錄,審計是不會呼叫工具打開檔案、實際檢查 artifact 產出物
那第二個我們來看 Claude Code,也滿有趣的。它的判定是交給另外一個模型,通常我們主模型可能是一個 Opus。所以當它輸出答案的時候,整個 harness 還不會交還控制權,而是它會發另外一個 request,把整段對話交給一個獨立的小模型來做審計,有點像類似 fork 的概念。最後它覺得要判定要不要輸出最後答案的時候,它會 fork 出來,然後用一個小的 Haiku 模型來去判定。
那中間那一塊就是它判定的 prompt,就是說: 請基於這個對話的 transcript 然後以及我的停止條件,來去判斷說是不是有足夠的證據可以結束。
預設 Haiku,用另一份 system prompt
保留對話紀錄、工具呼叫紀錄,移除 thinking、移除整包 claudeMd 環境、tool metadata 等
回覆 {ok, reason, impossible},包括回饋理由,以及是否不可能辦到(此時應該放行,避免無窮迴圈卡死)。
目標完成,把最後答案輸出給你
擋下,把 Haiku 寫的 reason 注入主 agent,強迫跑下一輪
特別要注意的是它只會看對話紀錄的,這個審計是不會打開工具、不會用工具去打開檔案再去查東西的,它只會看傳進來的 transcript 對話紀錄而已。
所以精確來說是判定交給另外一個模型、另外一個 prompt、發一個 request 去檢查。不是另外一個 Agent 去檢查,不一樣。另外一個 Agent 的意思是說那個 Agent 有工具可以去查,不是,它只是一個 request 去查而已。
它查的東西是 transcript,是一個重組的副本,它有保留原先的整個對話串、工具的呼叫紀錄,但是裡面是沒有 thinking 推理脈絡的。就是原來的主 Agent 裡面的 thinking 區塊是被剝離掉的,跟剛才 Codex 不一樣。剛才 Codex 是自我審計的時候整個對話紀錄是包含 thinking 的,這個是 thinking 被剝除的。它也移除掉整個 Claude Code 的環境,就是它不能執行工具的,所以它不會有工具的列表等等。
那它最後的回傳值會回傳是不是 OK、以及原因,以及還有一個蠻有趣的是 impossible, 是不是這個目標不可能辦到。因為有可能這個目標不可能辦到嘛,那它就會無窮迴圈了,所以它也蠻有趣的設計一個 impossible 的判斷,如果 impossible 的話它就脫離迴圈了。
這是它的裁判的流程,從左上角主 Agent 進來,輸出答案的時候進到 stop hook 攔下來,然後用獨立的 Haiku 去判斷。如果判斷失敗的話就是它會再把那個 reason 注入進來,然後強迫它跑下一輪。
| Codex /goal | Claude Code /goal | |
|---|---|---|
| 回饋形式 | 重播同一份 continuation.md,只有 budget 數字在動 | Haiku 寫的針對性診斷,指出 transcript 缺了什麼 |
| 下一步依據 | 環境證據(測試失敗、compile error等)+自己保留的 reasoning | 外部裁判指出缺口 |
| 成本代價 | 每輪多累積一份 prompt,但會有 prompt cache | 每輪多一次 Haiku call,主 thread 只增加一小段 reason |
| 失效模式 | 模型有盲點時無外部視角打破,可能連錯好幾輪 | Haiku 誤判:會被自信的收尾語言騙過 |
所以這是這兩家的差別,它們在回饋的形式、下一步的依據跟成本代價跟失效模式都有所不同。
第三種最為獨立,獨立 Agent 拿著 rubric 操作 artifact 做檢查:
max_iterations 預設 3、最多 20# Outcome: 研究報告完成
- [ ] 涵蓋 7 項必查數據,各附來源
- [ ] 每個引用 [n] 連到有效 URL,
且原文逐字支持該主張
- [ ] 財務數字出自 10-K / 10-Q 正式文件
接下來看實作三。這三種實作,我是根據獨立性來區分的,而這個是最獨立的。
這個是真的有一個獨立的 Agent,拿著我的 rubric 的評分條件去做操作的。所以它是一個全新的 grader Agent,拿著全新的 context window,它只看它的評分標準跟最後的產出物。它完全不看 transcript,完全不看主 Agent 的對話紀錄,什麼都不看,它只檢查最後的產出物而已。它是一個獨立的 Agent,有工具可以去操作的,可以打開檔案去看、實際去跑跑看、開瀏覽器等等,就看你怎麼設計。所以這是最為獨立的。
所以這是獨立的流程圖,主 Agent 產出來一個 artifact,然後右邊獨立的 grader 再去評分它。
要 agent 評自己做的東西,它傾向自信地稱讚,哪怕在人眼中品質明顯普通。
而且 context 裡的錯誤推理會不斷累積,前面說服了自己「方向對」,後面就一路錯下去。
同一個 agent 較難對自己的產出下得了手,換一個全新 context 獨立評審,比較容易做出準確的 Judge。
相比自評的話如果我們要看準確性,獨立的當然是最準確的。因為我們都怕 Agent 會自我吹捧,所以問題就在擔心說是誰來批判。
如果是自己評自己就比較容易自我吹捧,如果是獨立評的話就是比較容易做出準確的 judge。就是對工程師來說,調出一個挑剔的獨立的 judge 是比讓它自我批判容易的。
學界研究也表示有蠻多 paper,就是自我評審的問題以及獨立評審的好處等等。
| Codex 的 Goal | Claude Code 的 Goal | Claude Managed Agents Outcomes | |
|---|---|---|---|
| 誰來判定 | 主模型自我審計 | 獨立的 Haiku | 全新 context 的 grader agent |
| harness 角色 | 不判斷,閒置時重播合約 prompt | 每輪結束送 transcript 裁決 | 自動配置 grader 評估迴圈 |
| 看什麼證據 | 自己 context 裡的一切(含 reasoning) | 刪減版 transcript | 只看 artifact,實際操作驗收 |
| 怎麼宣告完成 | 主模型呼叫 update_goal |
yes/no +診斷 reason | rubric 逐條 pass/fail |
| 沒完成時的回饋 | 沒有診斷,只重播同一份模板 | 一段針對性診斷,指出缺什麼 | 逐條列出缺失,最具體 |
所以這是三種實作的總比較。關鍵就是 Codex 它是自我審計,然後 Claude Code 中間那個是雖然是獨立的,但是它是讀刪減版的 transcript,右邊那個是完全獨立的 grader。所以獨立性來說是右邊大於中間大於 Codex。
獨立性換來可靠度,但要付出代價。差別不只是「單次評估多少錢」,也要看「錯誤多久才被發現」
| 獨立性/可靠度 | 單次評估成本 | 錯誤多久被發現 | |
|---|---|---|---|
| Codex 自我審計 | 低:主模型自評 | 趨近零:只查狀態 | 靠模型自己就先發現 |
| Claude Code Haiku | 中:獨立小模型讀 transcript | 小:約 1-2 秒 | 每 turn 一次 |
| Outcome grader | 高:獨立 agent 操作 artifact | 高:數十倍、約 8 分鐘 | 整個 iteration 做完才抓到 |
但這樣講起來好像 Codex 是最差的,看來獨立性好像很好。但是對工程來說一切都是有 tradeoff 的。你的獨立性帶來就是成本的代價,你獨立性可以有很好的可靠度,但是你要換來代價。代價是什麼?token 成本以及 latency,越獨立它的 latency 就越慢,以及花費的 token 數就越多。
所以當然我們評估的不只是單次評估多少錢,也要看錯誤多久會被發現。你越獨立的話其實整個錯誤會在整個迴圈的越尾端才會被發現。你如果越自我審計的話,Codex 其實就會自己可以比較早發現錯誤。所以這跟成本有關係。
這邊是我自己的一些推論,就是 Codex 的 Goal 為什麼不用獨立裁判呢?因為裁判其實有兩層,我們可以在 harness 層用獨立裁判,或者是完全的靠模型來做自我審計。
我個人的推測是 OpenAI 有特別加強它的 update_goal 的 post-training 模型訓練,一定有針對這件事情加強它自我審計的能力它才能夠表現得這麼好。它帶來的好處就是能夠比較節省 token 的總成本。這是我個人的推論。
調教難度較高。高度依賴模型的自律,一般模型容易過早宣告完成。
適合: 重視 Latency 和成本的中短任務。
折衷做法。小模型判 transcript,好實作、好評估(輸入輸出都是文字)。
適合:中程任務,在 transcript 中就能看到可信的證明。
效果最好,但成本最高、latency 是最大弱點。每次驗收要把 artifact 跑起來操作。
適合:長時間、以交付物為終點,價值高到值得花評估開銷。
所以對我們開發者來說,這幾種實作有什麼經驗的教訓呢?該怎麼選呢?
首先,自我審計的那種方式我認為調教難度是比較高的,因為它高度依賴模型的自律,我們又不是 OpenAI 可以訓練自己的模型,所以如果你自己做自己的專案然後要用自我審計的話,難度還是比較高的,它可能比較適合重視 latency 跟成本的中短任務。
claude code 的做法是我比較推薦的,就是算是折中的做法,用一個獨立的小模型去判它的 transcript,比較好實作也比較好評估。
完全獨立 verify 這種是完全獨立的,效果最好但是成本也最高,latency 會非常的慢。
需求: 按訪綱逐題訪問用戶
挑戰: 但什麼時候該換下一題?這題問夠了沒?
模型會偏向「覺得差不多了就想往下走」,跟第二篇的過早宣告完成是同一個問題,只是這裏是「這一題問完了沒」。
所以這邊有一個綜合的案例是我自己實作的,就是我在做一個使用者訪談的 Agent。需求就是它會按照訪綱逐題去訪問用戶,例如問說: 你覺得這網站你最常用的情境是什麼,第二個問題可能是你覺得這個品牌對你的印象是什麼,最後會有一個訪綱。
所以你如果設計 Agent 去做訪問用戶的話,這個挑戰就是什麼時候會換下一題、這一題問夠了沒。因為我們不希望一問一答然後就跳下一題,如果被訪問的人回答得太模糊的時候我們希望 AI 能夠追問。但問題是這個追問到底追問幾次或者是追問到什麼樣的程度才可以換下一題,以及我還有時間控制,整個訪問要 15 分鐘要做完,我要分配到 5 題裡面。所以這裡面需要一些 AI 的判斷。
agent 想跳下一題時,呼叫 switch_next_question_workflow 工具,裡面是一個獨立 Judge 審核:
@function_tool
async def switch_next_question_workflow(reason: str, user_requested_next: bool) -> str:
"""當你判斷目前這題已取得足夠答案、或使用者要求跳題時呼叫。"""
# 真正的判定在 server 端,工具只是把請求轉過去
...
我的做法是我借鑑了這兩個實作的兩個招數。第一個是我借鑑了 Claude Code 的做法,就是我提供一個換題的工具,在裡面我會做 Transcript Judge。就是每次 Agent 判斷他收集到足夠的資訊要換下一題的時候,我會要他去 call 這個工具叫做 switch_next_question。
在這個工具裡面我會用一個獨立的 prompt 去檢查 transcript 是否滿足條件可以跳到下一題。回答 yes 的話他才會換到下一題,如果回傳 no 的話我的指示會跟他說請你繼續聚焦在這一題,換個問法繼續問。
不同題目有不同的完成標準(rubric),用小模型根據逐字稿來判斷:
QUESTION_WORKFLOW = [
{ "title": "最近一次使用情境",
"completion_criteria":
"需含最近一次使用的具體情境,"
"至少提到使用的功能與當時任務。" },
{ "title": "替代方案",
"completion_criteria":
"需提到至少一個替代方案,"
"最好指出最常用者與原因。" },
# …其餘題目
]
You are a user interview quality reviewer.
Use only the transcript below. Do not infer
facts that are not in the transcript.
Current question: {question}
Completion criteria: {completion_criteria}
Transcript: {transcript}
Return exactly one character:
Y if complete. N if not.
所以這個 judge 看什麼?當然不同題就會有不同的 rubric。我們會去看訪談稿判斷不同題目會有不同的檢查標準,我們就會用不同的 prompt。
Judge 回 N 時,工具不是只回一個 false,而是回一段可行動的指令,順便附上這是第幾次被退:
N: 仍停留在第 1 題「最近一次使用情境」。這是第一次追問。
請換一種問法追問目前這題,不要切換題目。
如果 Judge 沒有通過,我會提示他要繼續追問,而且我還會提示他這是第幾次被退。我有設計說如果被退了三四次的話就算了,就放棄就跳下一題吧,不然他可能會卡無窮迴圈。
每輪推動 agent 往對的方向走: 「現在過幾分鐘了、第幾題了」的狀態也傳給 Agent
<time_control>已過 7 分鐘 / 共 15 分鐘</time_control>
<current_question>第 2 / 5 題: 題目....</current_question>
另外一個是借鑑了 Codex 的做法,每一輪動態注入訊息。我在每一輪都會注入一個 time_control 的訊息,動態的狀態告訴他說現在時間過了多久、總共現在時間已過了七分鐘、總共有十五分鐘、現在在第幾題。我會提示 Agent 說如果時間充裕的話就可以追問,如果時間快到了那就要趕快跳下一題了。所以透過這個動態注入的狀態,我們可以讓 Agent 感知到目前時間的情況。
所以這是整個流程圖。我借鑑了這兩種實作的方式。
PART 6 · 時機 ④
(時機 ④ 外層 Outer Loop)
接下來是 Outer Loop 最外層了,也就是剛才的時機點 3 結束、目標也達成了,接著最外層的迴圈就是展開一個新的 session、展開一個全新的 context window。
需要一個在單輪 agent 之外的工程迴圈,也就是 外層 harness
它決定何時觸發、每件事開獨立 agent、多條 agent 怎麼協調。
進度不靠 context 記憶,會寫到檔案系統共享。
為什麼需要這個外層迴圈呢?主要是因為單一的 context 能力有限,以及我們會展開一個全新的任務,或者是我們有一個外部的事件去觸發 Agent 做事。所以會有這種需求。
那共通的解法當然就是在整個每一輪的 Agent 之外,我們還會有一個外層的 harness 去控制它什麼時候去觸發 Agent 開始做事情,每件事是不是開一個獨立的 Agent 等等。然後這個外圈的觸發通常也蠻靠 filesystem 檔案系統的,因為我們會開新的 context window,所以那個進度跟狀態要怎麼維持,通常就會用到 filesystem,我們會把狀態放到 filesystem 上。
這一層最近在社群很熱門:
其實就是強調 Outer loop (Outer harness) 這一層,讓我們看具體實作案例,拆解給你看
這整個就是最近在社群上很熱門的 Loop Engineering 這個新詞。包括龍蝦爸爸有提到說你不應該再去提示 Agent,你應該設計提示 Agent 的迴圈等等。那這些其實都是在強調 Outer Loop 就是 Outer Harness 的這一層。
bash 蠻力重跑
每圈全新 context,同一份 prompt 反覆送進去直到做完
看板協調器
專案管理工具當控制平面,每張開啟的 ticket 配一個 agent
定時觸發
時間到就跑一輪;依 context 是否沿用,再分「獨立」與「heartbeat」兩種
我們來看看具體的案例。我這邊跟大家介紹三種:第一個是 Ralph 的暴力重跑、第二個是跟大家介紹一下 OpenAI Symphony、第三個是自動排程。讓我們分別來看一下。
while :; do
cat PROMPT.md | claude-code
done
COMPLETE 代表完成,就脫離無窮迴圈第一個是最笨也是最有名的 Loop: Ralph,就是你直接用一個 bash 的 while loop,然後讓它不斷的去讀這個 prompt 然後塞到我們的 Agent 就好。看起來很笨很簡單,暴力跑,每次都是重新的全新的 context window。
Ralph 拆成三個檔案:
完成的工作就是 commit,
新 agent 開圈先看歷史
每圈追加學到的事:
陷阱、模式、決策
任務清單與 passes 狀態:
每圈挑最高優先且未完成的
ralph 的每一圈:
讀 prd.json/progress.txt → 挑最高優先尚未完成的 story → 只實作這一個 → 跑 typecheck 和測試 → 過了 commit 標記完成 → learning 追加進 progress.txt → 下一圈。
那它什麼時候會完成呢?我們在這個 prompt 裡面會跟它說如果你認為全部任務都已經完成的話,你就輸入一個特殊的字串 COMPLETE,它就會跳出去,就這樣子很簡單。
那它是怎麼判斷目前有哪些任務以及做到哪裡、哪些算完成呢?它是靠外部的狀態,就是有三種檔案來去判斷。第一個它看 git history,第二個是它有一個叫做 progress.txt 它會去記錄學到的事情,然後還有一個列表是 prd.json 是任務清單跟目前的執行狀態。
所以這些都是外部的檔案,每次開新的 context window 就讀這些東西,判斷它要做什麼事情然後做,然後再跑,直到它認為全部都做完的時候就輸出一個特殊的字串再脫離這個迴圈。是蠻簡單的。
其實這是它一圈的樣子,重點就是它是一個全新的 context window、一個新的 Agent 在重跑,然後拿現在的外部狀態判斷。
名字叫 Ralph,但讀完 code,它的機制跟原版不同:
bash 外迴圈,每圈整個重來:全新 context、靠檔案傳遞進度。是「換新 context」的設計。
用 Stop hook 在同一個 session 內攔住結束,進行完成判定。
社群上也有一個 plugin 叫做 Claude Code Ralph 的 plugin,名字叫做一樣,但是如果看它的實作的話其實跟原版的不一樣。原版是每圈重來有新的 context window,但是這個 plugin 其實是用 stop hook 在同一個 session 內攔住結果來做判定的。所以如果看 source code 的話這個東西應該更像是 Codex 的 Goal 的那種自我審計的做法。
所以名字長一樣但是做法其實是不一樣的。我覺得對工程師來說,外行人會說一樣的名詞,但是工程師自己要知道裡面的實作是不一樣的。
像龍蝦爸爸 Peter 對這一招的批評滿多的,因為它每一圈都無狀態的重跑,然後那個 progress.txt 是軟性的筆記、比較缺乏結構化的記憶。它更像是一種弱模型的權宜之計,靠大量的 token 去硬做,可能把關不足,也有可能卡住一直耗 token。
所以我的心得是認為驗證這件事情其實在內層的 harness 時機就應該要做好,不能全部靠外圈的 harness 一直重跑。不然你其實很難在大範圍的外圈裡面做好驗證,就會浪費很多 token。
OpenAI 開源的 harness 外層系統(以Spec形式): 以任務為中心,將 Linear 當做控制平面,每張開啟的 ticket 都有一個 agent 在自己工作區跑(有新 ticket 就派工)
那接下來我們看第二個案例。第二個 Outer Harness 的案例是 OpenAI 有開源一個 Harness 的外層系統叫做 Symphony。它是把專案的管理系統變成一個 Agent 的控制系統。它用的專案管理叫做 Linear,就是那個專案看板。大家一定用過專案管理工具,裡面可能會有 Kanban 的看板,就是 Todo 有這些、正在進行有這些、完成有這些。
所以它把這個系統跟 Agent 是連動的,每開一張票就會有個 Agent 去做那張票的事情。就是用整個專案看板來當作狀態機去控制 Agent。Agent 如果碰到問題它就自己開 ticket,如果人去開一個 ticket 就會有個 Agent 去做事,做完事情之後它就會把做事的結果更新到那張票上面。所以人就會變成是管理工作而不是管理 code。
這是它大概的圖解流程,裡面就是專案管理的狀態,每張票你開下去它就會有個 Agent 去做事了。就是等於是 Agent 變成一個控制的平面。
額外一提就是它開源的這個不是開源程式碼,它開源的是 spec,滿有趣的。它不是程式碼給你,它是一個自然語言的 spec 的文字給你的。
設定觸發條件,條件一到就自動把一段 prompt 丟出去跑一輪。
Claude Code、Codex 都已內建(/loop、Routines、Automations),設定就能用,是最容易上手的。
例如
/loop 5m 檢查 deploy 狀態,失敗就修 ← 固定間隔
/loop 盯著 CI,紅了就處理 ← 動態間隔:模型自己決定
那第三種 Outer Harness 的案例就是自動排程,也算是最輕量跟最通用的觸發方式。就是我們設定觸發條件,時間一到它自動就會把我們的 prompt 丟進去開始跑。那在 Claude Code 跟 Codex 裡面都有內建這個功能,包括 Loop、Routine、Automation 等等,這些都是這些 Coding Agent 內建的功能。
每次開全新的 agent。乾淨 context、進度靠外部狀態。
例如 Codex 的 standalone automation 模式 、Claude Code 的 Routines 功能
把 prompt 打到同一個長駐 session。記憶連續、反應快,但 context 會持續變大。
例如 Codex 的 thread automation 模式、Claude Code 的 in-session /loop功能
我剛才雖然講說 Outer Harness 只開一個全新的 context window,也就是左邊的情況: 每次觸發都開一個全新的 context window 來做。但也有另外一種做法是沿用同一個 context,那個做法叫做 Heartbeat,其實是龍蝦開始發明這個名詞然後大家就開始用了。
當我們說 Heartbeat 排程的時候指的是沿用同一個 context window。以 Codex 來說就是你左上角可以去標籤把某一個對話串標上去,然後你在設定 Automation 的時候可以跟它說我是要再沿用同一個 context window。
因為很多時候你的排程任務可能是需要之前的記憶的,你想要那個記憶一直用下去,這時候你就會用 Heartbeat 的方式。所以這是兩種排程觸發,你要去決定到底你的 context 是要沿用還是開新的。
(圖解兩種模式)
| 管什麼 | 回答的問題 | |
|---|---|---|
| 外層 Loop | scheduling 排程 | 什麼時候跑、多久跑一次 |
| Goal | termination 終止 | 做到什麼程度才能停 |
排程內的 Agent 就直接用上 Goal 把事情做完
在排程內,把工作丟到某個 queue 就結束。另外有真正做事帶 goal 的 Agent 去執行
然後剛才這個 Loop——外層的 Loop 跟 Goal 是可以一起用的,這是分開的兩件事情。你可以排程的任務裡面是有 Goal 的,你可以把這兩個結合在一起。
那這有兩種做法:一個是你排程裡面的那個 Agent 就直接帶著 Goal 把事情做完;另外做法是你在排程裡面把這工作丟到某個 queue,然後你有另外一個 Agent 去從那個 queue 裡面拉出來做事。右下角的做法會是比較工程上比較 scale 的做法。
所以你這兩個東西加起來其實就會變成很多現在 Loop Engineering 的範例,就是自動的排程加上排程裡面帶有一個目標任務。
Steinberger 開源的 maintainer-orchestrator skill 每隔 5 分鐘醒來執行
github-project-triage 把進來的 queue 分三類:
看個案例,龍蝦爸爸就有開源他的那個自動維護 Repository 的 Skill。就是他設定每五分鐘會起來執行,他會先有一個 Skill 裡面會去拉出來有哪些事情發生了,那接著他有另外一個 Skill 去分類說這個事件屬於哪一類。就是自動的排程去幫他維護他的專案。
固定 cron、動態間隔,還是事件(webhook、新郵件、CI 失敗、看板多一張 ticket)?
要讀哪些 context 之外的狀態,才知道進度到哪、有沒有新工作?
例如 Ralph 讀 git/progress.txt、Symphony 讀專案看板等等
開 PR、寄一封簡報、丟進 triage 收件匣,還是安靜歸檔?
所以回歸到外層 Loop 核心就三個問題:怎麼被觸發、Agent 醒來之後要看什麼東西、做完事結果要送去哪裡。就這樣。

所以這個 Loop 一層疊一層。在 Shawn "swyx" 的電子報裡面他就畫一個蠻有趣的圖。有趣的是我覺得第一層也蠻有趣的,第一層叫做 Token 的 Loop,在大模型裡面 Token 也是不斷的預測下一個 Token,那個其實也是一個 Loop,他把它放在第一個 Loop。第二個 Loop 就是 Agent 的那個 Turn,第三個 Loop 是 Goal 的那個 Turn,那第四個是 Outer Harness 的那一個 Loop。第五個是他打了問號,就是我們也許下個月有新發明出來一個東西。
PART 7 · 進階
(進階:自我改進的 Harness)
連 harness 本身,都讓 agent 自己改。 讓 agent 根據自己跑出來的 trace 與 eval,回頭改自己的 harness,包括 system prompt, skills 甚至 code 。
自我改進不是讓 agent 想改什麼就改什麼
需要在 eval、trace、版本控制、regression test 的限制下,提出可驗證的變更。
不然,「自我改進」和「把 harness 改壞」就分不出來了。
那接下來是 Part 7、8、9,其實今天的重點最重要的東西就是剛才的那幾個時機點。那整個系列裡面 7、8、9 還是可以跟大家快速的分享一下。
進階的部分是做自我改進的 Harness,最近發現一個新的名字叫做 RSI: Recursive Self-Improvement,遞迴自我改進。其實就是在做這個自我改進的 Harness,就是我們把改 Harness 這件事情也交給 Agent。我們剛才講了那麼多就是寫 Harness 就是人去寫,那有沒有辦法讓 AI 幫我們自己去寫 Harness?就讓連 Harness 本身也讓 Agent 自己改。
可以改的東西可以改 Prompt,這最簡單的,改 Skill,那最重要再進一步就是我們連 Harness 的 code 也讓 Agent 來幫我們改。
這三種方法詳細在我的文章裡面會介紹到:
第一個是我們可以讀 Production 的 Trace,如果你上了 Production 的話我們根據用戶的實際的 Trace 從這裡面發掘出一些 insight,做錯誤的分析找到可以去改進的東西,然後再改我們的 code。
第二個方法就是用 Agent 當作優化器去改 code,如果我們有一個先做了一個 Evaluator 評估器的話,我們就可以讓 Agent 像爬坡一樣去改進我們的程式。那這個如果你有看 Karpathy 的話它叫做 Auto Research 就是這條思路。
然後第三個方法就是用 Agent 去幫我們改 Skill,也有蠻多文章是在講如何用 Self-Improvement 的方式去改 Skill。
關鍵是自我改進不是 Agent 想改什麼就改什麼,我們還是希望能夠有一些評估的機制,不然自我改進跟把 Harness 改壞你可能就分不出來了。
PART 8 · 收尾
Model-Harness-Fit
(收尾:會過期的 Harness)
模型每次變強,某些當初補強它的元件就不再有用,該被移除。
每次升級時可以檢查之前的 prompt 約束、workflow 還有必要嗎?
Terminal-Bench 2.0 排行榜排的不是「模型」,而是「harness + 模型」的配對。同一個模型配不同 harness,分數差好幾個百分點,差距甚至大過一個模型世代的升級。
Part 8 會跟大家分享一個概念是會過期的 Harness。這個副標就是 Model-Harness-Fit,指的意思是沒有一個 Harness 適用所有的模型,同一個 Harness 換不同模型效果就不一樣。
比如說現在一些新的 Coding Agent 的排行榜上面,已經不直接排模型了,而是排這個模型搭配哪一個 Harness 的排名。因為就算是同一個模型它搭不同的 Harness,它的百分比差距可能不是差幾個百分點而是差十幾個百分點。
一般來說比較小的模型它可能會比較需要精巧複雜的 Harness,比較大的模型它比較聰明,它可能需要的 Harness 就比較輕薄。所以隨著模型的智能升級變聰明,Harness 可能就會有過期的現象,就是當初我們補強它的一些元件可能就必須被移除掉、不再需要了。
各種 harness 做法與補強措施
Bitter Lesson: 能隨算力擴大的通用方法,終究勝過手工規則。很多會隨更強的模型逐步不再需要。
定義「什麼叫做好」+ 驗證
就算模型強到能完美自我驗證,它驗證的,仍然是一個「由你定義的目標」。定義什麼是「好」,是無論模型多強都得自己做的核心。
Harness 應該設計得容易替換元件、更容易測量性能: 模型一變強,你才拆得掉。
所以我們在設計 Harness 的時候就需要考慮到模型升級的情況。那有一些是不會過期的情況,就是定義什麼叫做好,以及如何驗證是不會過期的。
所以我們 Agent 應該設計得容易去替換元件、更容易去測量它的性能,這樣模型如果之後升級變強,我們才有辦法去拆掉 Harness 裡面不需要的東西。
PART 9 · 動手做
(番外:自建 Agent 的框架選型)
起點高: 一開始就有能跑的 agent,但相對不好改。
優點: 馬上能跑;harness 原廠針對自家模型調過。
缺點: 會帶用不到的 context/工具;可控性/可維護性受限;常綁特定供應商。
起點低: 要自己接的多,但彈性與可控性好,能一步步朝想要的設計走。
優點: 完全可控,貼著任務分布拿掉無關 context/工具;model 無關、好換供應商、好控成本。
缺點: 前期工程量較大。
那這個 Part 跟大家分享自建 Agent 的框架選型,就是該從哪個框架開始。大致上可以分兩個路線:一個是從功能完整的 Deep Agent 開始,另外一個是從基礎開始構建。
從完整的 Deep Agent 開始指的是你從 Codex SDK 開始、從 Codex App Server 開始、從 Claude Agent SDK 開始,或是 LangChain 它有一個 Deep Agent。這種是我歸類在從 Deep Agent 路線,這一種就是有完整的內建功能都有的。但缺點就是它會帶有很多用不到的功能或是 context,可控性跟維護性也會比較差因為太多內建的東西了,通常也會綁特定的供應商。
比如說 Codex 的話就是用 OpenAI,它最近有說可以換其他模型,但我個人認為因為模型訓練的時候是會針對 Harness 做特別訓練的。比如說修改工具這件事,Claude 的修改工具跟 GPT 的修改工具其實是不一樣的,所以他們在模型訓練的時候是有針對自家熟悉的工具做 post-training 的。所以你如果隨便換一套其實效果不會比較好。
路線二是從基礎構建,你會獲得更好的可控性,可以把 成本 / latency 控制得更好。
這頁是一些框架舉例。
特定情境,或使用者多 → 從基礎構建。貼近單一任務分布的 vertical agent 更有效率: token 成本低、延遲低,也能換供應商、精算成本。
要打造團隊用的 harness 與 outer loop → 從現成 deep agent。
要的本來就是強的通用開發 agent,原廠已調好內層,先有能跑的東西、疊上 outer loop 最快。
要讓使用者帶自己的 ChatGPT 帳號 → 看 Codex SDK 或 Codex app server。
支援訂閱帳號登入,不必 API key 計費;Claude Agent SDK 第三方只能用 API key。
到底該選哪個呢?這裡簡單跟大家分析。如果你是特定的情境、規模比較大的、用戶比較多的,我會建議你從基礎開始建,你才可以更有效率的去控制你的 token 跟 latency。
那如果你是團隊內要自用的,我會建議你從 Deep Agent 開始,就是團隊內部自用的這種你直接拿 Codex SDK 或是 Claude Agent SDK 來做是比較快的。
另外還可以提的就是如果你想要讓使用者帶著自己的 ChatGPT 帳號、花他自己的 token 的話,這種你可以特別考慮用 Codex 的方案,因為 OpenAI 允許這條路線。
CONCLUSION
Takeaways
(此頁講者未有對應逐字稿)
所以總結今天跟大家講了很多,花比較多時間講在不同的時機點可以去插入我們想要去駕馭 Agent 的回饋。每一篇都在我的 blog 上面還有完整的內容,歡迎大家再去看。
那今天的演講就到這邊了,謝謝大家。