RUNLOG:讓 Skill 自己進化的失敗軌跡消化機制


寫 Claude Code Skill 的文章滿坑滿谷。Skill 寫壞了怎麼修的文章一篇都找不到。

這件事其實比「怎麼寫」重要得多。Skill 是你把工作流程固化成文字的產物,它第一版一定不對——不是語法錯,是判斷規則不夠:某個步驟該查三個地方它只查了兩個、某個結論不該下全稱它下了、某個 probe 腳本在零命中時你分不出「真的沒有」還是「搜錯了」。

這些失誤有個共同特徵:當下你會手動補救,然後就忘了。 下次同一支 skill 跑起來,同樣的地方再摔一次。

RUNLOG 是為了解決這件事。這篇講它的完整機制,以及我踩出來的五條鐵則——其中三條是關於如何不讓規則膨脹成沒人讀的長文


核心觀念:RUNLOG 是原料,產物是 Skill 本體被改好

先講最容易搞錯的一點。

RUNLOG 是一個 markdown 檔,放在 skill 目錄旁邊,記錄「這支 skill 每一次沒一次到位的地方」。但它不是日記,也不是給人看的成長紀錄

⚠️ 產物是 SKILL.md / probe 腳本 / 雷區文件被改好,不是這個檔變漂亮。

這句話寫在我每一份 RUNLOG 的檔頭。因為誘惑實在太大——把失敗寫得很完整、分類很整齊,會產生一種「我處理過了」的錯覺。但如果 SKILL.md 一個字都沒動,下次跑起來的行為完全一樣

所以整套機制只有一個成功判準:

消化一筆 = 有一個檔案因此被改。


RUNLOG 長什麼樣

格式刻意壓到一行一筆:

- [<來源單號>] <哪一步出錯> — <漏了什麼/誤判什麼> — <當下怎麼補救>  [未消化 | 已消化→改了X]

真實的一筆長這樣(節錄):

Step 7 範本 ── 用 openpyxl 產「給舊版 PHPExcel 吃」的 xlsx 範本 = 靜默全毀:openpyxl 3.1 把字串寫成 t="inlineStr"(且不產 sharedStrings.xml),PHPExcel 1.8 的 Excel2007 reader 讀不到 inlineStr,於是範本的表頭 25 欄、整頁「填表說明」、統計表的男/女/小計列全部消失,但 load/save 不報任何錯,sheet 名稱/隱藏狀態/欄寬都正常 → 表象是「範本沒問題」。要到解析產出才發現。修法:範本改用 PHPExcel 自己 load 客戶原檔 → 清資料 → save,讓範本天生就是 PHPExcel 讀得回來的形狀。 → 教訓:凡是要餵給舊版 PHPExcel 的範本檔,一律用 PHPExcel 自產或自己 round-trip 一次並解析驗證;驗收條件是「解析產出時標題列/靜態頁有值」,不是「檔案打得開」。 [已消化→pitfalls.md §10 + SKILL.md Step 4 觸發清單 J]

一筆裡有四個要素:在哪一步摔的、根因是什麼、當下怎麼救、最後改了什麼

前三個是原料,第四個是產物憑證。沒有第四個,這筆就掛著 [未消化]

目前這支 skill 的 RUNLOG 累積到 79 筆,其中 73 筆已消化、6 筆掛著未消化。那 6 筆不是懶得處理,是故意留著的——原因下面說。


鐵則一:先分群再歸納,別用 n=1 過度擬合

這是整套機制最重要的一條。

你剛摔完一跤,最想做的事是立刻在 SKILL.md 加一條「以後要注意 XXX」。這通常是錯的。

因為單一案例分不出「這是系統性缺陷」還是「這次剛好倒楣」。用 n=1 去改規則,你會得到一份針對歷史特例過度擬合的長文——每條規則都是真的,但合起來沒人讀得完,而且沒有哪一條真的防到下一次。

所以條目分兩型:

型別判準處理
fix-now根因確定、系統性、改一行就根除單筆也立刻消化
累積型(判斷失誤)要靠多個案例才看得出 pattern≥2 筆同族才歸納成規則

fix-now 的典型例子:「probe 腳本執行超過某個時間會被自動背景化,而我沒去撈結果就當它沒命中」。這是工具的確定行為,改一行文件就根除,沒有累積的必要。

累積型的典型例子:「這次根因判錯了,因為我只看了 A 層沒看 B 層」。這種只發生一次,你不知道是流程缺陷還是那天不夠仔細。

孤例的處理方式是:維持 [未消化],並明講「待更多案例」。這就是那 6 筆的來歷。它們不是待辦,是證據不足的觀察


鐵則二:最小持久改動——這條防的是 SKILL.md 肥胖

消化一筆的產出應該是:一條 rubric、一條小抄、一個 probe case。不是重寫一段。

為什麼這麼小氣?因為 SKILL.md 有一個很多人沒意識到的成本:

SKILL.md 每次請求都整份重送,是唯一「不做事也要付錢」的成本。

我實測過一個數字:某次工作的靜態前置約 45k tokens,而那段工作發了 205 次請求。那份文件被完整重送了 205 次。

所以我在盤點腳本的輸出尾段加了一個觸發器:

--all 掃描時會印出各 SKILL.md 的常駐大小。超過 20KB 的,本輪就多做一群「瘦身」。 這一群不需要 ≥2 筆同族,它是 fix-now:規則一直都在,缺的只是觸發器。

這是我覺得整套機制裡最有價值的設計:把「文件該瘦身了」變成一個由數字自動觸發的工作項,而不是靠人偶爾想起來。

案例細節不進 SKILL.md

第二個防膨脹的規則:規則後面不要掛案例、不要掛單號

錯誤示範:

- 改共用函式前先確認 repo 裡有幾份副本(issue-042 那次 PHPExcel 有三份,
  只改一份導致另外兩份的報表照壞,客戶回報後才發現……)

正確寫法:

- 改共用函式前先 md5 確認 repo 裡有幾份副本。
  踩過的形狀:同一份第三方函式庫被複製三份,只改一份等於沒改。

差別有三:

  1. 規則要能獨立讀懂。掛著 (issue-N) 的規則,讀的人得去翻單才知道在講什麼——而未來讀它的多半是 AI,它翻不到。
  2. 「踩過的形狀」是通則敘述,保留了教訓的形狀但去掉了案例的長度。
  3. 細節各有其家:失敗全文留 RUNLOG、領域事實進雷區文件、指令雷進記憶。複述別處已有的內容 = 同一份真相三個版本,遲早不同步。

鐵則三:搬走不等於刪掉

SKILL.md 瘦身時最容易犯的錯,是把一整段判準搬去附屬文件,然後在原處留一行:

- 詳見 templates.md 的「Step 4B」一節

這等於實質刪除。 因為下次執行時,AI 讀到這行有兩種反應:跳過,或是把整份 templates.md 讀進來——後者比不搬還糟,你省下的 token 一次還回去。

正確的作法是:留下的那一行必須是祈使句的觸發條件 + 可以直接跑的抽節指令

- 命中「跨模組共用元件」→ `python section.py templates.md "Step 4B"`

這行有三個性質:

  • 祈使句:明確告訴執行者「什麼情況下要做什麼」,而不是「有個東西在那裡」
  • 可執行:不需要判斷怎麼取,直接跑
  • 精準:只抽出那一節,不是整檔

還有一個實務陷阱:搬完要用那個指令實跑一次,確認命中而且只命中一節。 節名互為前綴時會一起命中——例如 Step 4BStep 4B0,抽節腳本會把兩節都吐出來。


鐵則四:改 skill 前先給使用者看提案

這條是流程性的,但很重要:SKILL.md 會影響未來每一次執行的行為,這不是可以自作主張的事。

所以完整流程停在中間:

Step 1  解析 RUNLOG(跑 parse 腳本,看計數、門檻、分群線索)
Step 2  分群 + 判型(主 agent 親手,不委派)
Step 3  草擬提案(明確到「改哪個檔、加哪一句、放哪一段」)
Step 4  ⏸ 給使用者確認 —— 停在這裡
Step 5  套用 + 標記
Step 6  回報

Step 2 特別標了「主 agent 親手」。分群是這整套裡唯一需要真正判斷力的一步,把它委派給子 agent 會得到一份看起來合理、實際上按表面症狀分類的結果——而真正有用的分群是按根因分

反事實診斷:分群的檢驗方法

Step 2 有一個很好用的技巧。對每個要消化的群,寫一句反事實診斷

「當初若有 X 這條規則,這幾筆會不會就不發生?」

X 就是你要加的那條規則。

這個問法的價值在於它會自動篩掉沒用的規則。如果你寫不出一條讓那幾筆都不發生的 X,代表你的分群是錯的——那幾筆的根因其實不一樣,該拆開。


鐵則五:消化 = 真的改了東西

最後一條是驗收條件,前面說過但值得重複:

只換標記卻沒動 SKILL.md / probe,不算消化,禁止

而且對 probe 腳本的改動有額外要求:用當初出事的輸入重跑驗證,再宣告消化。

這聽起來理所當然,但很容易跳過——因為改完腳本你「覺得」它會對。當初出事的輸入是免費的迴歸測試,不用白不用。

還有一個順手要做的動作:回收既有膨脹。動到某一段時,如果附近有前幾輪留下的案例長敘事,順手壓成一條通則。這樣文件才會在「每輪加一條」的同時維持穩定大小。

回報時附上改前/改後的 bytes,證明這輪沒有淨膨脹


RUNLOG 與產物的對照表

一個實務細節:不同 RUNLOG 的「產物」不一樣,要先講清楚,否則消化時會不知道該改哪。

RUNLOG產物(要改的檔)
調查型 skill 的 RUNLOG該 skill 的 SKILL.mdtemplates.md、各支 probe 腳本
審查型 skill 的 RUNLOG專案的雷區文件(漏報補這裡,審查器自動跟上)、SKILL.md、helper 腳本
repo 層級的 RUNLOG.claude/commands/*.md、各支 pre-commit 檢查腳本

第二列有個很漂亮的性質:那支審查 skill 執行時會直接讀專案的雷區文件組 prompt。所以「審查器漏抓了某類問題」的消化動作是——在雷區文件補一條,下次執行自動跟上,SKILL.md 一個字都不用動。

當你的工具是讀資料驅動的,消化成本會低一個數量級。 這值得在設計 skill 時就先考慮。


實際跑起來是什麼感覺

盤點:

python parse_runlog.py --all        # 掃所有 RUNLOG,看誰達門檻
python parse_runlog.py <>        # 細看某一支

輸出會給你:計數、是否達門檻、分群線索、逐條未消化全文,以及各 SKILL.md 的常駐大小。

(一個小細節:解析腳本要排除被 ``` 圍起來的格式範例,否則檔頭的格式說明會被算成一筆條目。這種小坑本身就是第一批進 RUNLOG 的東西。)

然後是分群、提案、確認、套用。一輪大概處理 5–8 筆,產出 2–3 條規則。

73 筆消化下來,SKILL.md 從沒超過 20KB。 這不是因為規則少,是因為每輪都在做「加一條、壓一段」的平衡。


為什麼這件事值得做

回到最開頭的問題:為什麼要這麼麻煩?

因為 AI agent 的失誤有很強的重複性。它不會累,但也不會學——你這次手動補救的那個判斷,下次同樣情境它還是會漏掉,除非你把那個判斷寫進它每次都會讀的地方。

而「寫進它每次都會讀的地方」這件事有成本上限(token)、有品質上限(規則太多等於沒規則)。所以你需要一套機制,決定什麼值得寫進去、寫多短、什麼時候該把舊的壓掉

RUNLOG 就是那套機制。它把「AI 又出錯了」從一個令人沮喪的事件,變成一筆有明確處理流程的原料。

一句話總結:

親手讀失敗軌跡做錯誤分析,才校準得了工具。


相關文章: