如何真正阻止你的 Agent 重複犯錯
如何真正阻止你的 Agent 重複犯錯
LangChain 募資了 1.6 億美元。經歷了三年的開發。估值高達十億美元。他們的測試平台 LangSmith 確實非常精良:具備軌跡評估(trajectory evals)、追蹤到資料集的管線(trace-to-dataset pipelines)、LLM-as-judge(以 LLM 作為評審)、回歸測試套件(regression suites),以及針對工具的單元測試框架。他們確實擁有這些組件。該給予的肯定還是要給。
但組件並不等於一套實踐方法。
LangChain 提供了測試工具,但它從未告訴你該測試什麼、測試的順序為何,或是何時才算測試完成。
目前並沒有一套具備明確觀點的流程來規範以下順序:
發生了這個錯誤
現在編寫一個 skill
現在編寫確定性的程式碼
現在編寫單元測試
現在編寫 LLM 評估
現在加入一個 resolver 觸發器
現在評估該 resolver
現在審查是否有重複項目
現在進行冒煙測試(smoke test)
現在正確地歸檔
這個循環並不存在。你必須從零散的原始組件中自己發明它。許多 AI 使用者甚至根本不測試他們的 Agent,因為他們選擇的框架就像給了他們一張健身房會員卡,卻沒有提供任何訓練計畫。
大多數 AI Agent 的「可靠性」都只是憑感覺(vibes-based)。調整 Prompt。寫更長的系統訊息。唸著「請不要產生幻覺」的咒語。一旦對話變得複雜,這些東西立刻就會失效。那些募資數億美元來解決此問題的框架,只給了你監控儀表板和單元測試輔助工具,然後跟你說「祝你好運」。
我的 Agent 這週搞砸了兩次。這兩次失敗都不該再發生。不是因為我好言相求,而是因為我將每一次失敗都轉化為永久性的結構性修復:一個擁有測試的 skill,並且每天都會執行,永遠有效。
我將這種實踐稱為「skillify」。一旦你使用它,你的 Agent 就不會再重複犯同樣的錯誤。以下是它的運作方式。
失敗 1:資料庫裡已經有的行程
我問我的 OpenClaw 關於一次近十年前的商務旅行,那件事埋在行事曆歷史的某個角落。這是一個簡單的問題,應該一秒就能解決。
結果 Agent 卻做了這些事:
呼叫即時行事曆 API → 被阻擋(時間太久遠)。
嘗試搜尋電子郵件 → 結果雜亂,沒有定論。
用不同的參數再次嘗試行事曆 API → 依然被阻擋。
五分鐘後,它搜尋了我的本地知識庫,瞬間就找到了。
答案一直都在我自己的資料裡。從 2013 年到 2026 年,總共 3,146 個行事曆檔案。已經索引完成。已經在本地。只需一個 grep 指令。
Agent 只是沒有先去那裡找。
在我一直在寫的框架中(輕量級 harness、重量級 skills),判斷需要判斷力的工作與需要精確性的工作之間有一個關鍵區別。我稱它們為潛在(latent)與確定性(deterministic)。行事曆 grep 是確定性的。相同的輸入,每次都會得到相同的輸出。不需要模型。但 Agent 還是把它放在潛在空間(latent space)裡處理,啟動推理、進行 API 呼叫、解釋結果,而其實一個三行的腳本就能瞬間回傳答案。
這就是 Bug。不是答案錯了,而是處理的方向錯了。
修復:calendar-recall(步驟 1 + 2)
在輕量級 harness / 重量級 skills 的架構中,一個 skill 是一份 Markdown 程序,用來教導模型如何處理任務。不是告訴它做什麼(使用者提供「做什麼」),skill 提供的是過程。把它想像成一個方法呼叫(method call):相同的程序,根據你傳入的內容,會有截然不同的輸出。
這是從這次失敗中產生的 skill:
name: calendar-recall
description: "以大腦為優先的歷史行事曆查詢。在針對任何非未來或非過去 48 小時內的事件呼叫任何即時 API 之前,務必使用此功能。"
以及內部的硬性規則:
即時行事曆 API 僅適用於「未來」或「過去 48 小時內」的事件。所有歷史資料都必須先透過本地知識庫查詢。
讓這一切運作的關鍵在於:Agent 自己編寫了這個確定性的腳本。skill 檔案(Markdown 格式,存在於潛在空間中)告訴 Agent 如何解決這個問題。Agent 閱讀了該 skill,理解到行事曆搜尋屬於確定性工作,並產生了一個腳本來處理它:
$ node scripts/calendar-recall.mjs search "Singapore"
找到 2 個符合的日期:
── 2016-05-07 ──
飛往新加坡,Mandarin Oriental 飯店入住
── 2016-05-08 ──
與投資人在 Fullerton Hotel 午餐
程式碼在 100 毫秒內執行完畢(大部分時間是 Bun 啟動,實際 grep 時間不到一毫秒)。零 LLM 呼叫。零網路請求。純粹本地檔案。
這就是讓整個架構運作的循環:潛在空間建構了確定性工具,然後確定性工具限制了潛在空間。Agent 使用判斷力(潛在)編寫了 calendar-recall.mjs。現在,這個 skill 強制 Agent 執行該腳本,而不是對行事曆資料進行推理。模型的智慧創造了限制,防止模型變得愚蠢。
舊的失敗路徑在結構上變得無法觸及。skill 說「先搜尋本地」。腳本執行搜尋。Agent 再也沒有機會自作聰明或再次出錯。
失敗 2:「28 分鐘」(再次執行步驟 1 + 2)
同一天。Agent 說:「你的下一個會議在 28 分鐘後。」
事實:還有 88 分鐘。Agent 在腦中進行了 UTC 轉 PT 的時區運算,結果剛好差了一個小時。
問題是,已經有一個腳本(context-now.mjs)可以輸出這個結果:
{
"now": "2026-04-21T07:38:12-07:00",
"upcomingEvents": [{
"summary": "App Ops Sprint Planning",
"minutesUntil": 88
}]
}
程式碼在約 50 毫秒內執行完畢。零歧義。Agent 只是沒去執行它。
情況與之前一樣:確定性工作(減去時間戳記)在潛在空間中完成。模型在進行心算,而腳本明明就有答案。
修復:context-now,這個 skill:
name: context-now
description: "隨時開啟的紀律:在做出任何與時間相關的聲明之前,務必執行 context-now.mjs。永遠不要在腦中進行 UTC 轉 PT 的轉換。"
以下是使用與不使用這些簡單 skills 的前後對比:

Skillify:拯救你理智的模式
兩次失敗。相同的形式。Agent 擁有正確的工具,卻選擇了自作聰明而不是遵守紀律。錯誤的事情發生在錯誤的機器空間裡。
在一般的 AI 設定中,AI 會道歉,承諾改進,兩週後同樣的事情又會在不同的查詢或不同的時區再次發生。Agent 對這個 Bug 沒有記憶,沒有測試,沒有任何東西能阻止它再次發生。
Skillify 就是修復方案。每一次失敗都變成一個 skill。每一個 skill 都有測試。這個 Bug 在結構上變得不可能重複。
這是我在失敗被升級為 skill 時使用的 10 項檢查清單:
□ 1. SKILL.md — 合約(名稱、觸發條件、規則)
□ 2. 確定性程式碼 — scripts/*.mjs(程式碼能做的事,就不要用 LLM)
□ 3. 單元測試 — vitest
□ 4. 整合測試 — 即時端點
□ 5. LLM 評估 — 品質 + 正確性
□ 6. Resolver 觸發器 — AGENTS.md 中的條目
□ 7. Resolver 評估 — 驗證觸發器是否真的能路由
□ 8. 檢查可解析性 + DRY 審查
□ 9. E2E 冒煙測試
□ 10. 大腦歸檔規則
沒有通過這十項檢查的功能,就不是 skill。那只是今天剛好能運作的程式碼。
上述兩次失敗已經走過了步驟 1 和 2:編寫 SKILL.md(合約),然後編寫確定性程式碼(Agent 建立並使用的腳本)。但在我說明其餘八個步驟之前,我想向你展示 skillify 在日常使用中的樣子,因為它不僅僅是對失敗的回應。它已經變成了一個動詞。
Skillify 作為動詞
對我來說,在建構我的 OpenClaw(以及 GBrain)時,這份清單最初是一個失敗回應協議。後來它變成了我建構一切的方式。
這是我實際的工作流程。我用自然語言與我的 Agent 對話。我們在對話中一起建構東西。我嘗試它。它成功了。然後我說了一個詞:
Garry: 太棒了,成功了。你能把它記住作為一個 webhook skill 並 skillify 它嗎?下次我們需要做一些 webhooks 時就能用上。為什麼這件事這麼難搞定?總之現在好了。順便把它 DRY(不重複)一下。
那是一個 OAuth webhook 整合。我們花了一個小時才讓它運作。然後「skillify it」將這次臨時的對話轉化為一個持久的 skill,包含測試、resolver 條目和文件。下次我需要 webhook 時,這個 skill 就存在了。Agent 會閱讀它。那一個小時辛苦得來的知識就永久保存了。
另一個例子。我們發現我們的容器在某些任務中需要無頭瀏覽器(headless browser),而在我的桌面上則需要有頭瀏覽器(headed browser):
Garry: 太棒了!所以我們應該把它記住作為一個 skill,每當 openclaw 中的任何東西需要無頭瀏覽器時就使用它!另外也要知道,如果我們需要有頭瀏覽器,我們應該要求使用者執行 gstack browser 並給我們一個 pair-agent 代碼。Skillify 它!
一條訊息。Agent 編寫了 skills/browser/SKILL.md,包含決策樹、確定性腳本、測試。現在,未來每一個需要瀏覽器的對話都會自動路由到正確的工具。
或者這個。我注意到 Agent 一直傳送 ngrok 連結給我,卻沒有檢查它們是否真的能用:
Garry: 我們能做一個 skill,規定每當你傳送連結給我時,你必須自己 curl 一下,確保端點是開啟的且通道運作正常嗎?Skillify 它!
或是差點讓我錯過會議的行事曆重複預約:
Garry: 這裡有一個我需要你寫的常規 skill。這是行事曆檢查 skill。明天我有一個 11 點的重複預約。寫一個 skill,讓它以確定性的方式檢查這類事情。
一句話。程式碼、skill、測試、resolver 條目、可達性審查。整個 10 步驟檢查清單一氣呵成。我的 OpenClaw 知道了,執行了,現在這已經成為常規。我已經這樣做了幾十次。沒有它我無法生活。
模式總是相同的:在對話中進行原型設計,看到它運作,說「skillify」,原型就變成了永久的基礎設施。我不寫規格書。我不開工單。我與我的 Agent 對話,我們一起解決問題,然後解決方案就變成了 Agent 可以永遠使用的 skill,而不需要我參與。
這就是 1.6 億美元框架資金所錯失的東西。不是測試原始組件。不是評估工具。而是工作流程。當人類說「那樣行得通,現在把它永久化」的那一刻,系統確切地知道「永久化」意味著什麼:SKILL.md、確定性程式碼、單元測試、整合測試、LLM 評估、resolver 觸發器、resolver 評估、DRY 審查、冒煙測試、大腦歸檔。十個步驟。一個詞。
以下是其餘八個步驟在實踐中的樣子。
步驟 3:單元測試
經典的 vitest。確定性函數,確定性斷言。calendar-recall.mjs 匯出了純函數,如 parseEventLine、eventMatchesKeyword、searchKeyword、formatJson。每一個都針對 fixture 資料進行測試:臨時目錄中的合成行事曆檔案、已知的輸入、已知的輸出。
這些測試能捕捉到的 Bug 類型:parseEventLine 默默地丟棄了位置欄位中包含 Unicode 字元的事件。dateFromPath 對閏年日期回傳 null。formatJson 在只有一個人時省略了 attendees 陣列。很小、很無聊,但很關鍵。如果腳本產生了錯誤的輸出,skill 就會產生錯誤的答案,而 Agent 會自信地告訴我錯誤的資訊。
對於 context-now,單元測試驗證了時區格式化、安靜時間偵測,以及跨夏令時邊界的 minutesUntil 計算。一個測試輸入了夏令時轉換前 3 分鐘的時間,並驗證輸出不會跳動 60 分鐘。這正是導致「28 分鐘」失敗的 Bug。現在它在結構上已經不可能發生了。
我在 5 個套件中擁有 179 個單元測試。它們在 2 秒內執行完畢。
步驟 4:整合測試
這些測試會呼叫即時端點和真實資料。calendar-recall.mjs 是否真的能在真實的大腦儲存庫中找到事件,而不僅僅是測試 fixture?當行事曆快取過期或缺失時,context-now.mjs 是否會產生有效的 JSON?整合測試能捕捉到單元測試因為 fixture 資料太乾淨而錯過的 Bug。真實資料會有格式錯誤的事件行、缺失的時區欄位、帶有 Windows 換行符的行事曆檔案、跨越午夜的事件。
規則:如果你發現自己正在手動檢查腳本在真實資料上是否做了正確的事,那麼該檢查就應該是一個整合測試。
步驟 5:LLM 評估
這就是有趣的地方了。有些輸出需要判斷力來評估。「這個行事曆摘要是否有用?」不是腳本可以回答的「是/否」問題。所以我使用 LLM-as-judge:一個模型根據評分標準評估另一個模型的輸出。
對於 context-now,每天執行 35 個評估。其中一個會傳送給 Agent 一則訊息,例如「嘿,我的飛機大約 45 分鐘後起飛,我趕得上去 SFO 嗎?」,並檢查 Agent 在回答前是否執行了 context-now.mjs,還是試圖在腦中進行計算。如果 Agent 上鉤並自己計算時間,評估就會失敗。
另一個評估給 Agent 一個 UTC 時間戳記,並問「那對我來說是什麼時間?」。正確的行為是執行腳本並引用結果。錯誤的行為是進行心算。評估同時捕捉了錯誤的答案和錯誤的過程,因為即使心算這次剛好對了,下次也會錯。
我發現最誠實的評估啟發式方法:搜尋你的對話歷史,找出你說「fucking shit」或「wtf」的時候。那些就是你缺失的測試案例。
步驟 6:Resolver 觸發器
Resolver 是一個內容路由表:當任務類型 X 出現時,載入 skill Y。我曾在這裡詳細寫過關於 resolvers 的內容。每個 skill 都需要在 AGENTS.md 中有一個觸發條目,該檔案負責教導 Agent 存在哪些 skills 以及何時使用它們。
Resolver 觸發器只是 Markdown 表格中的行:

這個步驟捕捉到的 Bug:你寫了一個新的 skill,但忘記將它加入 resolver。skill 存在。能力存在。但系統無法觸及它。這就像員工名單裡有一位外科醫生,但醫院目錄裡卻沒列出他。這比根本沒有這個 skill 還糟,因為你會以為系統可以處理它。
步驟 7:Resolver 評估
這是大多數人完全忽略的層面。Resolver 觸發器說「這個短語應該路由到這個 skill」。Resolver 評估則測試它是否真的做到了。
我的 resolver 評估套件有 50 多個像這樣的測試案例:
{ intent: '檢查我的簽名', expectedSkill: 'executive-assistant' },
{ intent: 'Pedro Franceschi 是誰', expectedSkill: 'brain-ops' },
{ intent: '儲存這篇文章', expectedSkill: 'idea-ingest' },
{ intent: '我的會議是什麼時候', expectedSkill: 'context-now' },
{ intent: '尋找我 2016 年的旅行', expectedSkill: 'calendar-recall' },
兩種失敗模式。偽陰性(False negative):skill 應該觸發但沒有,因為觸發描述錯誤或缺失。偽陽性(False positive):錯誤的 skill 被觸發,因為兩個觸發器重疊了。「我明天行事曆上有什麼」應該路由到 calendar-check,而不是 calendar-recall,也不是 google-calendar。三個 skills,三個不同的時間領域,一個可能同時符合三者的短語。Resolver 評估會在使用者遇到問題前捕捉到這種歧義。
我將這些評估作為確定性結構測試(AGENTS.md 表格是否包含正確的映射?)以及 LLM 路由測試(給定這個意圖,模型是否真的選擇了正確的 skill?)來執行。兩個層面都很重要。表格可能是正確的,但模型仍可能路由錯誤,因為觸發描述太模糊。
步驟 8:檢查可解析性 + DRY 審查
經過一個月的建構,我有了 40 多個 skills。有些是為了回應特定事件而建立的,有些是由執行 cron 的子 Agent 產生的。沒有人維護 resolver 表格。Skills 不斷誕生卻沒有註冊。
所以我建構了 check-resolvable。一個 meta-test,它會遍歷整個鏈條:AGENTS.md resolver → SKILL.md → script/cron。如果存在一個能執行有用工作的腳本,但從 resolver 沒有路徑可以到達,它就是不可觸及的。LLM 永遠不會知道要使用它。
第一次執行發現 40 多個 skills 中有 6 個不可觸及。系統 15% 的能力處於黑暗中。
一個沒人能透過詢問航班來呼叫的航班追蹤器。
一個只在 cron 上執行但無法手動觸發的內容創意產生器。
一個存在於 skills 目錄中但根本沒列在 resolver 裡的引用修復器。
一小時內修復。只需在 AGENTS.md 中加入觸發條目。現在 check-resolvable 每週作為 gbrain doctor 的一部分執行。它檢查三件事:
每個帶有 SKILL.md 的 skill 目錄在 resolver 中都有對應的條目。
skill 引用的每個腳本確實可呼叫(檔案存在,匯出了正確的函數)。
沒有兩個 skills 具有會導致歧義路由的重疊觸發描述。
DRY 審查與它同時執行。如果你不小心,最後會得到十五個功能差不多的 skills,而 resolver 會隨機挑選一個。對於 calendar-recall:

同一領域有四個 skills。零重疊。每個都有自己的車道。那個矩陣不是為了這篇文章畫的圖表。它存在於 SKILL.md 內部,審查腳本會解析它。建立一個會侵佔他人車道的第六個行事曆 skill,審查就會在 skill 發布前失敗。
步驟 9:E2E 冒煙測試
完整的管線,端到端(End to End)。
問 Agent「我什麼時候去過新加坡?」並驗證它執行了 calendar-recall.mjs,得到了正確答案,並正確格式化。
問「我的下一個會議是什麼時候?」並驗證它執行了 context-now.mjs,而不是進行心算。
冒煙測試是最後一道防線。其他一切都可能通過,但如果組件沒有連接起來,系統仍然會失敗。skill 可能是正確的,腳本可能是正確的,resolver 可能是正確的,但 Agent 仍然可能選擇忽略這一切並隨意發揮。冒煙測試能捕捉到這一點。
步驟 10:大腦歸檔規則
每個寫入知識庫的 skill 都需要知道東西該放在哪裡。人放在 people/。公司放在 companies/。政策分析放在 civic/。我發現 13 個寫入大腦的 skills 中有 10 個歸檔到了錯誤的目錄,因為它們各自硬編碼了自己的路徑,而不是諮詢 resolver。
歸檔規則文件編目了常見的歸檔錯誤模式。來源 vs. 原件。人 vs. 公司(當某人本身就是一家公司時)。skill 在建立任何頁面之前會先閱讀規則。從那之後再也沒有發生過歸檔錯誤。
GBrain:Skillify 的居住地,你應該從我的 GBrain Skill Pack 採用它
Skillify 模式並非 OpenClaw 或任何特定 harness 所特有。它內建於 GBrain 中。GBrain 是我編寫的開源知識引擎,位於你使用的任何 harness 下方。它管理你的大腦儲存庫,執行你的評估,並強制執行使 skills 持久化的品質門檻。
GBrain SkillPack 是一個可攜式的 skills、resolver 觸發器、確定性腳本和測試的集合,你只需要求 OpenClaw/Hermes Agent 安裝,就能將其安裝到任何 Agent 設定中 — 包括完整的 10 步驟 skillify 輸出,封裝後你可以將其放入你的 OpenClaw/Hermes Agent 中,它就能直接運作。
之前的 skillify 檢查清單不是建議。那是 gbrain doctor 實際檢查的內容。
gbrain doctor --fix 會自動修復 DRY 違規,用慣例引用取代重複的區塊,所有這些都受到 git 工作樹檢查的保護,因此不會有任何東西被破壞。
為什麼 Hermes Agent 本身是不夠的
來自 Nous Research 的 Hermes Agent 做了一件真正偉大的事:它有一個 skill_manage 工具,讓 Agent 本身可以根據所學內容建立、修補和刪除 skills。當 Agent 完成複雜任務或從錯誤中恢復時,它會提出一個 skill 並將其寫入磁碟。那是 Agent 自己賺來的程序性記憶。漸進式揭露(先載入 skill 索引,只有在選擇時才拉取完整的 SKILL.md)。有界記憶(MEMORY.md 上限為 2,200 字元)。條件式啟用(當所需工具不可用時,skills 會自動隱藏)。聰明的設計。
但 Hermes 不測試它的 skills。確定性程式碼上沒有單元測試。沒有驗證路由的 resolver 評估。沒有尋找黑暗 skills 的 check-resolvable。沒有捕捉重複項的 DRY 審查。沒有在出現漂移時變紅的每日健康檢查。
我在任何未經測試的 skill 系統中觀察到的累積失敗模式:
Agent 在週一建立了 deploy-k8s。週四它從另一個對話中建立了 kubernetes-deploy。兩者都存在。兩者都在相似的短語上觸發。路由歧義,沒人注意到,直到錯誤的那個在錯誤的時間觸發。
skill 編寫時運作完美。六週後上游 API 改變了形狀。skill 默默地回傳垃圾資訊,直到人類發現它。
一個自主建立的 skill 有一個從未匹配的弱觸發器。它變成了一個孤兒,吃掉索引 token,從不執行,慢慢腐爛。
這就是軟體工程在 2005 年解決的「沒有測試,任何程式庫都會腐爛」的問題。Agent skills 也不例外。Hermes 處理建立過程非常漂亮。GBrain 處理驗證過程。你需要兩者兼備。
大想法
在一個健康的軟體工程團隊中,每個 Bug 都會得到一個測試。該測試永遠存在。Bug 在結構上變得不可能再次發生。AI Agent 也應該以同樣的方式運作。
每一次失敗都變成一個 skill。每一個 skill 都有評估。每一個評估每天執行。Agent 的判斷力會永久提升,而不僅僅是針對當前對話,也不僅僅是在 context window 持續期間。
旅行失敗不會再發生。時區失敗不會再發生。當下一次失敗出現時(而且一定會,因為這是一場對抗熵和品味的對抗性遊戲),它也會被 skillify。
一年後與我共事的 Agent 將會被它前一年犯下的每一個錯誤所塑造。這不是錦上添花。這是整個論點。
煮沸海洋。讓你的 Agent 做點什麼,然後 skillify 它。你每天都這樣做,你就會擁有一個該死聰明的 OpenClaw,能做所有你想要它做的事。
或者你可以直接載入 GBrain,使用我已經寫好的所有程式碼,並更快地跳到你自己的鋼鐵人 Jarvis。
--
GStack 加速 Claude Code github.com/garrytan/gstack
GBrain 在 OpenClaw/Hermes Agent 中建構你自己的鋼鐵人 Jarvis github.com/garrytan/gbrain
