PART 3 · 時機 ①
工具呼叫內,有三個可以設計的地方:
確定性檢查參數與內容,
擋掉危險或無效的呼叫
對結果做品質檢查,
必要時就地修復或重試
tool response 不只回資料,還夾帶指示與 metadata,引導 Agent 的下一步
寫程式習慣把回傳值當「資料」: 查到什麼回什麼,出錯就回傳 error code
使用者用自然語言問財務數據,
Agent 基於 DB schema 產生 SQL
用 execute_sql(sql_query) 工具查詢。
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 語法
驗證沒過時,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
下一步直接被導正。
{"success": true} 或 {"count": 0}
把規模與範圍寫清楚:
更多資訊,Agent 才有依據判斷「這次結果合不合理」,是否要修正
各種純運算式(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 理由。
注意控制別塞滿 context。context 過了某個門檻,模型會變笨(context rot)
別忘了 Context Engineering 的整套策略,各種縮減 context 的技術:
硬性工具輸出上限、頭尾保留 (切頭切尾)、卸載到檔案 (只留預覽 + 路徑,有需要 Agent 再打開看)
關鍵字、向量檢索、reranker 等,只挑選相關的 context
龐大的中間過程交給 sub-agent 在獨立 context 跑,只回傳結論
知識庫問答 Agent 會配一個 search_knowledge(query) 工具 (關鍵字搜尋+向量搜尋 等)
工具不只回傳搜尋結果,我們還可以回傳以下資訊,回饋給 Agent 更多資訊:
回傳時標上來源和相關度分數,讓 Agent 有依據判斷哪些該引用、哪些該丟
好幾個 chunk 都來自同一份文件 → 暗示整份相關。可以指示 Agent 直接把整頁讀回來,比散落片段好用
回傳不只 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 下一步該怎麼搜
呼叫另一個模型做 review,把回饋當 tool response 帶回來。從主迴圈看,這仍是一次普通的 tool call。
@function_tool
async def consult_model(question, files, model):
"""遇到難題、想確認方向、或要 review 產出時呼叫,
交給另一個模型給獨立回饋。"""
...
例如: RAG Agent 生成答案後,用另一個模型檢查有沒有幻覺。
這種做法在 coding agent 上也蠻常見
/codex:review: Claude 把本地 git 改動交給 Codex(GPT) 做 code review,並說明「只做 review、不准順手改」。一個寫 code、一個分析審查。
這是人工觸發,也可以寫進前饋規則,說明「在流程中何時、遇到哪類情況就呼叫」
例如 SQL 語法驗證用 SQLGlot,又快又穩
細緻品質好不好沒有確定性 assert 可寫,
使用 LLM judge 補規則型檢查的不足
錯誤訊息要附「該怎麼辦」,不是只丟一個程式 error code。
工具層回饋在 tool call 內發生,要注意 latency,別拖慢整個迴圈。
PART 4 · 時機 ②
一輪 (Turn) 不是「一問一答」,而是「模型 request → tool calls → 模型 request → …」反覆,直到輸出答案。注入就發生在這個迴圈內部、還沒輸出答案前:
Agent 還在反覆呼叫工具、還沒輸出答案時,就把新訊息插進去,馬上影響它接下來的動作
使用者就主動輸入一段話介入,叫做 steering 轉向/引導。
在 Codex、Claude Code 這類互動式 coding agent 最常見,是內建功能。
例如背景工具程式跑完送回結果、外部事件 (webhook、告警) 要讓 agent 知道。
目前較少框架實作,少數像 Pydantic AI 做成通用機制。
已經發出 tool calls 但還沒拿到結果,這時若添加 user message 回傳,API 會錯誤
assistant 發了 tool calls → 還沒等到所有工具結果 → 直接加 user message 回傳。下一個 request 送出就 400。
assistant 發了 tool calls → 每個 call 的結果都補齊 → 再加 user message → 下一個 request。
最常見的用途是人主動介入。steering 和 interrupt 都是 Codex、Claude Code 的內建功能。這跟 human-in-the-loop 不一樣:
你刻意設計一個等待點,讓 agent 跑到那裡停下來問你 (最典型: 執行某工具前先請使用者確認)。
是 agent 停下來問你。
agent 沒有等待點、也不打算停,是你在它還在執行時主動輸入 (通常因為「需求變了」)。
是你主動介入。
訊息先排進佇列,等當前 tool calls 結果到齊、正要發下一個 model request 前才注入。
agent 沒中斷,只是下一步多了你的訊息,已建立的 context 全部保留。
取消正在執行的工具,沒答完的 tool call 硬塞 tool_result,內容是固定字串 aborted ,維持對話 API 合法。
用途常見有:
長時間工具在背景跑完,把結果送回 agent。
webhook、錯誤告警、聊天平台訊息主動送進來。
enqueue,兩種模式: asap(盡快插入,就是這篇的時機點)與 when_idle(整輪結束後才插入)。它分兩步:
tool call 後不等跑完,馬上回 tool_result,內容是固定的:
只能用 user message 插入:
注意不能用 tool_result 訊息了,因為 API 的 tool call 跟 tool result 是一對一配對的。
PART 5 · 時機 ③
給 Agent 一個目標:
讓 Agent 持續修正,直到通過為止。
這是 Codex 和 Claude Code 的內建功能。
不能因為「模型覺得大概做完了」
就算數。
完成與否,要對著停止條件驗證,
沒過就繼續做。
好的 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: <任務描述>
沒有可靠的完成條件,
定義不出來?那問題不在工具,
在你還不知道自己要什麼。
藏在 Goal 背後的,是交付物的轉變:
有各種不同實作,讓我幫你分析。
由主模型自我審計,自己呼叫 update_goal 宣告
每輪結束,交給獨立的 Haiku 小模型裁決
由獨立的 grader agent 拿 rubric 評分標準,去檢查產出物做驗收
Codex 沒有第二個模型在做判定,是自我審計
update_goal(complete)更新狀態update_goal(complete)判定權在主模型手上,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 數字不同)
有些技術文章寫: Codex 的 Goal「用一個獨立小模型每回合評分」
沒有第二個 model、沒有額外的 grader
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 產出物
預設 Haiku,用另一份 system prompt
保留對話紀錄、工具呼叫紀錄,移除 thinking、移除整包 claudeMd 環境、tool metadata 等
回覆 {ok, reason, impossible},包括回饋理由,以及是否不可能辦到(此時應該放行,避免無窮迴圈卡死)。
目標完成,把最後答案輸出給你
擋下,把 Haiku 寫的 reason 注入主 agent,強迫跑下一輪
| 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 評自己做的東西,它傾向自信地稱讚,哪怕在人眼中品質明顯普通。
而且 context 裡的錯誤推理會不斷累積,前面說服了自己「方向對」,後面就一路錯下去。
同一個 agent 較難對自己的產出下得了手,換一個全新 context 獨立評審,比較容易做出準確的 Judge。
| 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 Haiku | 中:獨立小模型讀 transcript | 小:約 1-2 秒 | 每 turn 一次 |
| Outcome grader | 高:獨立 agent 操作 artifact | 高:數十倍、約 8 分鐘 | 整個 iteration 做完才抓到 |
調教難度較高。高度依賴模型的自律,一般模型容易過早宣告完成。
適合: 重視 Latency 和成本的中短任務。
折衷做法。小模型判 transcript,好實作、好評估(輸入輸出都是文字)。
適合:中程任務,在 transcript 中就能看到可信的證明。
效果最好,但成本最高、latency 是最大弱點。每次驗收要把 artifact 跑起來操作。
適合:長時間、以交付物為終點,價值高到值得花評估開銷。
需求: 按訪綱逐題訪問用戶
挑戰: 但什麼時候該換下一題?這題問夠了沒?
模型會偏向「覺得差不多了就想往下走」,跟第二篇的過早宣告完成是同一個問題,只是這裏是「這一題問完了沒」。
agent 想跳下一題時,呼叫 switch_next_question_workflow 工具,裡面是一個獨立 Judge 審核:
@function_tool
async def switch_next_question_workflow(reason: str, user_requested_next: bool) -> str:
"""當你判斷目前這題已取得足夠答案、或使用者要求跳題時呼叫。"""
# 真正的判定在 server 端,工具只是把請求轉過去
...
不同題目有不同的完成標準(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 回 N 時,工具不是只回一個 false,而是回一段可行動的指令,順便附上這是第幾次被退:
N: 仍停留在第 1 題「最近一次使用情境」。這是第一次追問。
請換一種問法追問目前這題,不要切換題目。
每輪推動 agent 往對的方向走: 「現在過幾分鐘了、第幾題了」的狀態也傳給 Agent
<time_control>已過 7 分鐘 / 共 15 分鐘</time_control>
<current_question>第 2 / 5 題: 題目....</current_question>
PART 6 · 時機 ④
需要一個在單輪 agent 之外的工程迴圈,也就是 外層 harness
它決定何時觸發、每件事開獨立 agent、多條 agent 怎麼協調。
進度不靠 context 記憶,會寫到檔案系統共享。
這一層最近在社群很熱門:
其實就是強調 Outer loop (Outer harness) 這一層,讓我們看具體實作案例,拆解給你看
bash 蠻力重跑
每圈全新 context,同一份 prompt 反覆送進去直到做完
看板協調器
專案管理工具當控制平面,每張開啟的 ticket 配一個 agent
定時觸發
時間到就跑一輪;依 context 是否沿用,再分「獨立」與「heartbeat」兩種
while :; do
cat PROMPT.md | claude-code
done
COMPLETE 代表完成,就脫離無窮迴圈Ralph 拆成三個檔案:
完成的工作就是 commit,
新 agent 開圈先看歷史
每圈追加學到的事:
陷阱、模式、決策
任務清單與 passes 狀態:
每圈挑最高優先且未完成的
ralph 的每一圈:
讀 prd.json/progress.txt → 挑最高優先尚未完成的 story → 只實作這一個 → 跑 typecheck 和測試 → 過了 commit 標記完成 → learning 追加進 progress.txt → 下一圈。
名字叫 Ralph,但讀完 code,它的機制跟原版不同:
bash 外迴圈,每圈整個重來:全新 context、靠檔案傳遞進度。是「換新 context」的設計。
用 Stop hook 在同一個 session 內攔住結束,進行完成判定。
OpenAI 開源的 harness 外層系統(以Spec形式): 以任務為中心,將 Linear 當做控制平面,每張開啟的 ticket 都有一個 agent 在自己工作區跑(有新 ticket 就派工)
設定觸發條件,條件一到就自動把一段 prompt 丟出去跑一輪。
Claude Code、Codex 都已內建(/loop、Routines、Automations),設定就能用,是最容易上手的。
例如
/loop 5m 檢查 deploy 狀態,失敗就修 ← 固定間隔
/loop 盯著 CI,紅了就處理 ← 動態間隔:模型自己決定
每次開全新的 agent。乾淨 context、進度靠外部狀態。
例如 Codex 的 standalone automation 模式 、Claude Code 的 Routines 功能
把 prompt 打到同一個長駐 session。記憶連續、反應快,但 context 會持續變大。
例如 Codex 的 thread automation 模式、Claude Code 的 in-session /loop功能
| 管什麼 | 回答的問題 | |
|---|---|---|
| 外層 Loop | scheduling 排程 | 什麼時候跑、多久跑一次 |
| Goal | termination 終止 | 做到什麼程度才能停 |
排程內的 Agent 就直接用上 Goal 把事情做完
在排程內,把工作丟到某個 queue 就結束。另外有真正做事帶 goal 的 Agent 去執行
Steinberger 開源的 maintainer-orchestrator skill 每隔 5 分鐘醒來執行
github-project-triage 把進來的 queue 分三類:
固定 cron、動態間隔,還是事件(webhook、新郵件、CI 失敗、看板多一張 ticket)?
要讀哪些 context 之外的狀態,才知道進度到哪、有沒有新工作?
例如 Ralph 讀 git/progress.txt、Symphony 讀專案看板等等
開 PR、寄一封簡報、丟進 triage 收件匣,還是安靜歸檔?

PART 7 · 進階
連 harness 本身,都讓 agent 自己改。 讓 agent 根據自己跑出來的 trace 與 eval,回頭改自己的 harness,包括 system prompt, skills 甚至 code 。
自我改進不是讓 agent 想改什麼就改什麼
需要在 eval、trace、版本控制、regression test 的限制下,提出可驗證的變更。
不然,「自我改進」和「把 harness 改壞」就分不出來了。
PART 8 · 收尾
Model-Harness-Fit
模型每次變強,某些當初補強它的元件就不再有用,該被移除。
每次升級時可以檢查之前的 prompt 約束、workflow 還有必要嗎?
Terminal-Bench 2.0 排行榜排的不是「模型」,而是「harness + 模型」的配對。同一個模型配不同 harness,分數差好幾個百分點,差距甚至大過一個模型世代的升級。
各種 harness 做法與補強措施
Bitter Lesson: 能隨算力擴大的通用方法,終究勝過手工規則。很多會隨更強的模型逐步不再需要。
定義「什麼叫做好」+ 驗證
就算模型強到能完美自我驗證,它驗證的,仍然是一個「由你定義的目標」。定義什麼是「好」,是無論模型多強都得自己做的核心。
Harness 應該設計得容易替換元件、更容易測量性能: 模型一變強,你才拆得掉。
PART 9 · 動手做
起點高: 一開始就有能跑的 agent,但相對不好改。
優點: 馬上能跑;harness 原廠針對自家模型調過。
缺點: 會帶用不到的 context/工具;可控性/可維護性受限;常綁特定供應商。
起點低: 要自己接的多,但彈性與可控性好,能一步步朝想要的設計走。
優點: 完全可控,貼著任務分布拿掉無關 context/工具;model 無關、好換供應商、好控成本。
缺點: 前期工程量較大。
特定情境,或使用者多 → 從基礎構建。貼近單一任務分布的 vertical agent 更有效率: token 成本低、延遲低,也能換供應商、精算成本。
要打造團隊用的 harness 與 outer loop → 從現成 deep agent。
要的本來就是強的通用開發 agent,原廠已調好內層,先有能跑的東西、疊上 outer loop 最快。
要讓使用者帶自己的 ChatGPT 帳號 → 看 Codex SDK 或 Codex app server。
支援訂閱帳號登入,不必 API key 計費;Claude Agent SDK 第三方只能用 API key。
CONCLUSION
Takeaways
Thank You