我的代理程式看不見的那五個技能
我裝了 84 個技能,並理所當然地以為這 84 個都在運作。其中五個並沒有。swiftui、testing-philosophy、typeset、web-performance 與 update-shortcuts-guide 抵達模型時只有一個光禿禿的名稱,沒有附上 description,而它們在磁碟上的檔案裡寫著完全合格的 description。description 就是路由訊號,因此沒有 description 的技能根本無法被路由到。這五個技能永遠不可能自動啟用,卻沒有任何地方提醒過我。沒有錯誤,沒有警告,也沒有一行日誌。我是偶然發現它們的,而我之所以能證明原因,唯一的理由是修正動作在我眼前把它逆轉了過來。
{.answer-block}
TL;DR
- 技能的 description 會載入每一輪的上下文,並共用一份固定的字元預算。一旦超出,description 就會被默默丟掉。12
- 我裝了 84 個技能,其中 82 個帶有 description,合計 21,848 個字元。有五個抵達時名稱在、description 不在。在磁碟上,這五個各自有 206 到 336 個字元。
- 沒有任何檔案屬性能解釋為什麼是這五個被丟掉。description 長度、檔案大小、YAML 格式、
name與目錄名稱不一致、修改時間,在被丟掉與被保留的兩組之間全都重疊。 - 我把 74 條 description 重寫到合計 9,885 個字元。五個原本隱形的技能全部在工作階段中途、在同一段對話裡帶著完整的 description 回來了。假設、介入、確認。
- 確切的預算沒有文件說明,目前仍有爭議。Anthropic 有數個未關閉的 issue 回報說,這個比例是按固定的 200K 基準線計算的,忽略了 1M 的上下文擴充。34
- 讓它裝得下的重寫規則是:description 的唯一職責是回答什麼時候應該呼叫它。流程、理念與檔案路徑屬於本文,而本文只在被呼叫時才會載入。
一次沒有錯誤訊息的故障
大多數 harness 的 bug 會自報家門。掛鉤以非零狀態結束,MCP 伺服器拒絕啟動,工具呼叫回傳一段堆疊追蹤。description 預算什麼都不做。它只是默默把一份比磁碟上更短的清單端給模型,而所有後續症狀看起來都像模型的問題,而不是管線的問題。
我是靠讀自己的上下文視窗發現的,而不是靠讀檔案系統。掃過技能清單時,有五筆只有名稱,後面什麼都沒有。其他每一筆都是名稱加上一句話。於是我把檔案調了出來:
swiftui HAS 322 chars on disk -> DROPPED in context
testing-philosophy HAS 291 chars on disk -> DROPPED in context
typeset HAS 248 chars on disk -> DROPPED in context
web-performance HAS 206 chars on disk -> DROPPED in context
update-shortcuts-guide HAS 336 chars on disk -> DROPPED in context
後果比技能變慢或出錯更糟。代理程式是靠讀 description 來決定要不要呼叫一個技能的。抽掉 description,不是讓路由變差,而是把路由整個拿走。技能裝好了、有效,卻無法抵達。swiftui 是我的 iOS 26 模式技能,也就是說我跑過的每一次 Swift 工作階段,都是在沒有它的情況下盲飛。
Anthropic 有未關閉的 issue 描述了同樣的故障,其中一條的標題是「Skill description budget silently truncates routing information, causing skill routing failures」。2 可見這是一個已知的 bug,而不是本機設定有誤。知道這一點很有用,但它並不能幫我找出自己有哪些技能受到影響。
排除掉那些容易的答案
最誘人的做法是猜出機制然後動手修。我先去試著否證這些猜測,因為「有五個技能壞了」與「有五個技能因為這個原因壞了」是兩個非常不同的主張。
如果某個檔案層級的屬性標記了哪些技能會被丟掉,那麼被丟掉的一組應該在某個可測量的地方與被保留的一組不同。於是我做了比較:
| 屬性 | 被丟掉(5 個) | 被保留(77 個) |
|---|---|---|
| description 平均長度 | 280 個字元 | 266 個字元 |
| 平均檔案大小 | 9,106 位元組 | 7,608 位元組 |
| 區塊純量寫法的 YAML description | 5 個中有 2 個 | 77 個中有 30 個 |
name 與目錄名稱不同 |
5 個中有 1 個 | 77 個中有 4 個 |
| 修改日期 | 1 月至 7 月 | 1 月至 7 月 |
沒有任何東西把兩組分開。被丟掉的 description 不是最長的,檔案也沒有明顯更大,YAML 風格在兩組裡都是混著的,修改日期也涵蓋同一個區間。按字母順序排位的解釋同樣落空:排在 swiftui 之後的技能都保住了自己的 description。
到這一步,誠實的立場是:我有一個可重現的症狀,卻沒有機制。於是我就照這樣寫了下來,然後去找一個測試,而不是去找一套說法。
那個測試
如果起作用的是總預算,那麼無論我刪減哪些檔案,只要把總量降下來,被丟掉的 description 就應該恢復。這個預測可以否證,而且成本很低。
我重寫了 74 條 description,把總量從 21,848 個字元降到 9,885 個。五個此前隱形的技能帶著 description 回來了,就在同一段工作階段裡,也沒有重新啟動。
整個實驗就這些。一個預測,一次介入,一次確認。丟棄是總體積的函數,而不是單一檔案任何屬性的函數——這正好解釋了為什麼沒有任何逐檔案的屬性能區分這兩組。
一個不留痕跡的 bug 仍然留下了反事實。如果檢視故障現場找不到原因,那就改動一個變數,看看故障會不會跟著走。
我想把自己沒有確立的東西講清楚。我不知道確切的預算,也不打算發布一個自己拿不出出處的數字。官方文件沒有寫出這個上限。5 社群的測量把實務上的上限放在技能中繼資料總量 15,500 到 16,000 個字元附近,並指出每一筆大約有 109 個字元的額外開銷,來自 XML 標籤、技能名稱與 location 欄位,而這些都不會被單純的 description 字元數統計到。6 以 84 個技能來算,光是這部分開銷就約有 9,156 個字元。與此同時,Anthropic 的貢獻者回報說,預算比例是按固定的 200K 基準線計算的,忽略了 1M 的上下文擴充,因此同一台機器上的兩個工作階段可能拿到不同的預算。34
我第一次寫下這個發現時,聲稱自己的設定「超出預算 119%」。我拿一個未經驗證的假設(1M 視窗的 1%)去乘一個真實的測量值,造出了一個信心滿滿、底下卻什麼都沒有的數字。觀測到的事實站得住:21,848 個字元丟掉了五條 description,9,885 個字元一條也沒丟。那個百分比沒站住,而它本來就不該被寫出來。
description 的唯一職責是路由
砍掉 12,000 個字元聽起來很有破壞性。其實不是,因為那些 description 裡的絕大部分內容,本來就沒在做路由的工作。
以下是我的 jiro 技能當時掛出來的招牌,686 個字元:
面向程式碼品質與職業自豪感的匠人(Shokunin)手藝哲學。在實作功能、重構程式碼、撰寫測試、審查工作,或處理 FastAPI/Python、Swift/SwiftUI、HTMX 前端與基礎架構程式碼中任何不瑣碎的變更時啟用。內嵌三種核心理念:匠人(在看不見的細節上追求卓越)、款待(以手藝提供服務)、Rick Rubin(創造性的引導與淬煉)。核心決策關卡:Evidence Gate(拿出品質的證據,而不是對品質的感覺)。使用時機:建構功能、重構、測試、審查程式碼、修 bug,或任何在回報完成之前需要品質證據的工作。
其中大約 500 個字元是在解釋這個技能裡有什麼。沒有一句能幫忙決定要不要打開它。替換後的版本是 126 個字元:
面向程式碼品質的手藝與證據標準。在實作、重構、測試、審查或修 bug 時使用。
同樣的觸發詞,同樣的路由行為,成本只有五分之一。理念並沒有消失,它住在本文裡,而本文只在技能真正執行時才會載入。為它在每一輪付費,什麼也換不到。
這個模式在整套技能裡反覆出現。九個 update-*-guide 技能帶著 3,024 個字元幾乎一模一樣的樣板文字,講的都是掃描來源、同步副本、跑翻譯。壓縮到每個約 115 個字元之後,它們依然路由正確,因為區分它們的是更新哪一份指南,而不是它們共用的那條管線。
真正起作用的是三條規則:
- 留下觸發詞,砍掉解釋。 名稱、斜線指令,以及使用者真的會敲進去的詞,留著。對內部流程的描述,去掉。
- 檔案路徑屬於本文。 路徑幫不了模型判斷什麼時候該呼叫某樣東西。
- 共用的樣板文字是純粹的額外開銷。 如果九個技能說同一句話,那句話誰也區分不了。
第二筆稅:不被呼叫也在起作用的 description
刪減暴露出一筆更隱微的成本。我的 description 裡有 11 條、合計 3,808 個字元帶著祈使語氣:ALWAYS、NEVER、MUST、PROACTIVELY、BEFORE。distribute 說 NEVER,no-shortcuts 說 ALWAYS,git-custody 說 BEFORE。
無論那個技能會不會執行,這些詞都坐在每一輪的上下文裡。它們讀起來像指令,因為它們本來就是照指令寫的;而模型沒有可靠的辦法,一邊把 description 當成惰性的目錄文案,一邊把措辭完全相同的系統指令當成有拘束力的命令。
近期的研究為這個效應取了名字。《The Regression Tax》在兩個辦公室自動化基準與三套 harness 上測量了約 6,000 次執行,指認出 skill description osmosis(技能 description 滲透):一個技能僅僅因為存在於上下文中就改變了代理程式的行為,即使它從未被呼叫。1 它最主要的發現是,最好的技能之所以勝出,靠的是退化較少,而不是收益較多;技能在流程指引上過度投資,在事實依據與驗證上卻投資不足。
正式環境的證據比理論來得更早。Anthropic 在 v2.1.215 撤回了內建 /verify 與 /code-review 技能的自動啟用,改成只能明確呼叫。7 又過了兩個版本,/deep-research 也不再自我呼叫。8 這些都是重量級技能,未經請求的執行付出的代價超過了它們賺回來的東西——這正是廠商在實務環境中觀察到的滲透,而且是靠移除啟用、而不是靠重寫 description 來修正的。
所以一條過大的 description 要付兩次成本。它吃掉別的技能做路由所需的預算,又施加了沒人要求的行為壓力。兩筆成本都落在這個技能毫無貢獻的那些輪次上。
讓人不安的部分:本文可能同樣管不住
「把它挪到本文裡」是我剛給出的建議,而它帶著一個值得說出口的假設:代理程式在呼叫時載入的流程,真的能管住它的行為。新的基準研究顯示,這個假設沒有聽起來那麼牢靠。
HANDBOOK.md 測的正是這一點。65 項任務,篇幅 20 到 124 頁的政策文件,代理程式在模擬公司裡跨越電子郵件、聊天、行事曆與電子商務工作,以及 824 條程式化評分標準。30 種模型組態中最好的一種只通過了 36.2% 的試驗,大多數前沿組態低於 25%。9
被點名的失敗模式恰恰是這裡要緊的。代理程式會讓一個看起來合理的環境內請求推翻既定政策。它們執行了要求的檢查,然後做出與檢查結果相反的動作。它們在長跨度裡弄丟規則的細節。這些都不是檢索失敗,文件自始至終都在那裡。
所以我這條規則的誠實版本,比「description 負責路由,本文負責解釋」要窄。把流程搬出 description 依然是對的,因為這樣能收回別的技能做路由所需的預算,也能阻止未被呼叫的文字去引導行為。這兩點都是實實在在的收穫,而且都不依賴本文管得好不好。它換不到的,是對搬過去的流程會被遵守的信心。一份 124 頁的手冊與一段 3,000 字的 SKILL.md 本文,落在同一條曲線上。
實務上的讀法是:把本文長度當成成本,而不是免費的停車位。如果某條規則真的必須成立,那 description 是放它的錯誤位置,長本文也只是稍好一點點。強制應當屬於某個確定性的地方(一個掛鉤、一條權限規則、一個測試),而不是一段散文,指望模型一邊做別的事一邊把它記住。
稽核你自己的設定
從工作階段內部開始。執行 /context,它會回報是否有技能被排除。5 如果它標出了排除項,問題就已經確定,診斷到此為止。
我沒有從這裡開始,原因本身很有啟發:我那五個技能並沒有被排除,它們是名稱完好、description 被剝掉之後抵達的,這比整筆不見更安靜,也可能不會以同樣的方式浮現。所以無論如何都要拿檔案系統來核對一遍。這項檢查除了一個 shell 什麼工具都不需要:
python3 - <<'PY'
import os, re, glob
rows = []
for f in glob.glob(os.path.expanduser('~/.claude/skills/*/SKILL.md')):
name = os.path.basename(os.path.dirname(f))
fm = re.match(r'^---\s*\n(.*?)\n---\s*\n', open(f, encoding='utf-8', errors='replace').read(), re.S)
if not fm:
continue
d = re.search(r'^description:\s*(.*?)(?=\n[a-zA-Z_-]+:|\Z)', fm.group(1), re.S | re.M)
if not d:
continue
desc = ' '.join(d.group(1).split()).strip('"\'').lstrip('|').strip()
rows.append((len(desc), name))
rows.sort(reverse=True)
print(f'{len(rows)} skills, {sum(r[0] for r in rows)} description chars')
for length, name in rows[:15]:
print(f' {length:4d} {name}')
PY
然後把輸出和模型實際收到的內容做比對。兩者之間的落差,就是整個發現。如果某個技能在你的上下文裡只有名稱、後面沒有一句話,那它就是裝好了卻無法抵達。
從這次稽核可以帶出三個習慣:
為每一個新技能算預算,不只是長的那些。 每一筆固定的額外開銷都會跟著技能走,與 description 長度無關,所以第十個 90 個字元的技能,代價不只 90 個字元。
加完技能之後重新統計。 我沒辦法給你一個安全餘裕,因為上限沒有文件說明,而且據回報會隨比例的計算方式而變。34 一次實測勝過一個建立在假設之上、算出來的餘裕——我犯的正是這個錯。
動手刪減之前先做快照。 我大多數的技能目錄都沒有被 git 追蹤,還有七個是指向一個根本不是儲存庫的目錄的符號連結,所以 git add 以「beyond a symbolic link」為由拒絕了它們。我先把每一條原始 description 寫進了一個 JSON 檔案。沒有驗證過的版本控制不是備份。
重點整理
- 沒有 description 的技能不是變差了,而是無法抵達。 description 承載了全部的路由決策。
- 這個故障在構造上就是無聲的。 沒有錯誤,沒有警告,沒有日誌。先跑
/context看排除警告,再把你的上下文清單與檔案系統做比對,因為被剝掉的 description 比缺失的項目更安靜。 - 決定丟棄的是總體積,不是逐檔案的屬性。 單一檔案的任何屬性都沒能預測出哪些技能會丟掉 description。
- 檢視失效時,就去介入。 我沒能靠檢視故障現場找到機制。改變總量、看著故障被逆轉,一步就把它證明了。
- description 負責路由,本文負責解釋。 description 裡任何無助於判斷何時呼叫的內容,都要在每一輪付費,卻什麼也賺不到。
- description 裡的祈使句在未被呼叫時就作用於你。 ALWAYS 與 NEVER 會從目錄裡引導行為,這正是被測量到的滲透效應。1
- 不要發布你拿不出出處的百分比。 我自己第一版的寫法,就是拿真實的測量值乘上一個猜出來的預算,產出了一個信心滿滿而錯誤的數字。
常見問題
我的 Claude Code 技能為什麼不會啟用?
先看模型是不是真的看得見它的 description。技能的 description 會載入每一輪的上下文並共用一份固定的字元預算,一旦超出,description 就會被默默丟掉,沒有錯誤、沒有警告,也沒有一行日誌。我的 84 個技能裡有五個抵達模型時只剩名稱,而它們在磁碟上的檔案裡寫著完全合格的 description。沒有 description 的技能無法被路由到。12
Claude Code 的技能 description 預算是多少?
確切的預算沒有文件說明,目前仍有爭議,而我不會發布一個自己拿不出出處的數字。我測到的是:21,848 個字元的 description 丟掉了五條,9,885 個字元一條也沒丟。社群的測量把實務上的上限放在 15,500 到 16,000 個字元附近,並計入每一筆約 109 個字元的額外開銷;Anthropic 的貢獻者則回報說,預算比例是按固定的 200K 基準線計算的。346
我要怎麼稽核自己有哪些技能丟了 description?
先在工作階段裡跑 /context,它會回報是否有技能被排除。接著無論如何還是要拿檔案系統核對一遍,因為我那五個並沒有被排除:它們是名稱完好、description 被剝掉之後抵達的,這比缺失一整筆更安靜。把你各個 SKILL.md frontmatter 裡的 description 字元數加總起來,再把這份清單與上下文清單實際顯示的內容做比對。5
技能的 description 與本文各自該放什麼?
description 的唯一職責,是回答這個技能什麼時候該被呼叫。留下觸發詞、名稱,以及使用者真的會敲的斜線指令,把流程、理念與檔案路徑搬進本文——本文只在被呼叫時才會載入。我的 jiro description 從 686 個字元降到 126 個,路由行為完全一樣,因為其中大約 500 個字元只是在解釋這個技能裡有什麼。
即使技能從不執行,它的 description 會影響行為嗎?
會,這就是第二筆稅。我的 description 裡有 11 條帶著 ALWAYS、NEVER、MUST、PROACTIVELY 與 BEFORE,這些詞坐在每一輪的上下文裡,而且因為是照指令寫的,讀起來就像指令。《The Regression Tax》把這個效應命名為 skill description osmosis:一個技能僅僅因為存在於上下文中就改變了代理程式的行為,即使它從未被呼叫。1
參考資料
-
《The Regression Tax》,arXiv:2607.22520,2026 年 7 月 24 日。在兩個辦公室自動化基準與三套 harness 上進行了約 6,000 次執行。命名了三種退化模式:skill description osmosis(未被呼叫、僅因存在於上下文而產生的行為變化)、事實依據置換、驗證置換。主要發現:表現最好的技能主要靠退化較少、而不是收益較多來勝過其他技能。 ↩↩↩↩↩
-
Skill description budget silently truncates routing information, causing skill routing failures,anthropics/claude-code issue #64606。另見 Skill descriptions truncated due to context budget constraints,issue #56710。 ↩↩↩
-
skillListingBudgetFraction is calculated against a fixed ~200K baseline, not the model’s actual context window,anthropics/claude-code issue #57941。 ↩↩↩↩
-
Skill description budget uses base context, ignores [1m] extension,anthropics/claude-code issue #57168。 ↩↩↩↩
-
Extend Claude with skills,Claude Code 文件。已發布的文件沒有說明技能 description 的總字元預算。另見 Skills docs omit the 250-character cap for /skills descriptions,issue #40121。 ↩↩↩
-
Claude Code skill budget research。社群測量,把實務上的上限放在技能中繼資料總量 15,500 到 16,000 個字元附近,每一筆約有 109 個字元的額外開銷(XML 標籤約 85、技能名稱約 20、location 欄位約 4),並觀察到在所測的這些情形裡,被隱藏的是整筆項目,而不是逐筆被截斷。 ↩↩
-
Claude Code CHANGELOG,v2.1.215,2026 年 7 月:內建的
/verify與/code-review技能不再自我呼叫,需要明確呼叫。 ↩ -
Claude Code CHANGELOG,v2.1.218,2026 年 7 月 22 日:
/code-review以背景子代理程式的方式執行,/deep-research不再自我呼叫。 ↩ -
Liudas Panavas、Sebastian Minus、Bradley Monton、Derek Ray、Suhaas Garre、Sushant Mehta 與 Edwin Chen,《HANDBOOK.md: A Benchmark for Long-Context Agentic Instruction Following》,arXiv:2607.25398,2026 年 7 月 28 日投稿。涵蓋十家虛構公司五個領域(金融、醫療帳務、保險、物流、人力資源)的 65 項任務,配有專家撰寫的 20 到 124 頁標準作業程序與 824 條程式化評分標準。在評測的 30 種模型組態中,最好的一種通過了 36.2% 的試驗;大多數前沿組態仍低於 25%。被點名的失敗模式包括:讓看起來合理的環境內請求推翻既定政策、執行了要求的檢查卻做出與結果相反的動作,以及在長跨度裡弄丟規則細節。 ↩