← 返回首頁

如何真正阻止你的 Agent 重複犯錯

Garry Tan
Garry Tan
@garrytan
1,092🔁 104
𝕏 (Twitter)🔥🔥🔥

如何真正阻止你的 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 卻做了這些事:

  1. 呼叫即時行事曆 API → 被阻擋(時間太久遠)。

  2. 嘗試搜尋電子郵件 → 結果雜亂,沒有定論。

  3. 用不同的參數再次嘗試行事曆 API → 依然被阻擋。

  4. 五分鐘後,它搜尋了我的本地知識庫,瞬間就找到了。

答案一直都在我自己的資料裡。從 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 的一部分執行。它檢查三件事:

  1. 每個帶有 SKILL.md 的 skill 目錄在 resolver 中都有對應的條目。

  2. skill 引用的每個腳本確實可呼叫(檔案存在,匯出了正確的函數)。

  3. 沒有兩個 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