Agent Skills:
把 SOP 打包成 AI 的技能
主線〈AI Agent 與 MCP〉的生態系那一節,Agent Skills 只佔了一段:一份 SKILL.md, 平常只露 name 和 description 兩行。這一課把它拆開來量、拿去實測。 先玩一個真的實驗——同一個 skill、同一份 SOP,只改 description 那一句話,模型還叫不叫得到它?
實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西,也不用任何帳號或金鑰)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。每個實驗都有選單與滑桿可以拉,拉完立刻重算。
Skill 就是一個資料夾
沒有 SDK、沒有伺服器、沒有要註冊的 API——就是檔案。下面是本課實驗用的玩具 skill: 幫一個四人小團隊寫週報,工時要從 timesheet.csv 加總(description 是節錄,全文在實驗場 3️⃣)。
frontmatter 的規則是開放標準(agentskills.io 規格,2026-09-24 查):
| 欄位 | 必填 | 規則 |
|---|---|---|
| name | 是 | 1–64 字;小寫英數與連字號;不能以連字號開頭/結尾、不能連續兩個連字號;要跟資料夾同名 |
| description | 是 | 1–1,024 字;要寫「做什麼」和「什麼時候用」 |
| license | 否 | 授權名稱或附帶的授權檔 |
| compatibility | 否 | ≤500 字;需要什麼環境(產品、系統套件、網路) |
| metadata | 否 | 任意字串鍵值(作者、版本…) |
| allowed-tools | 否 | 預先核准的工具清單(實驗性,各家支援程度不同) |
真實世界的 skill 長什麼樣?做出這堂課的就是一個:本站 repo 裡的 make-lesson, SKILL.md 之外還有 2 份 references(工程底線、網站規範)、9 支 scripts、8 個範本。 實驗場 1️⃣ 把它和另外 8 個 skill 一個檔一個檔攤開,量給你看每層多少 token。
三層載入:用到才付錢
為什麼要這麼麻煩?回想主線講過的兩件事:上下文有上限,而且 API 是無狀態的——每一輪都要把整包上下文重送一次。 塞進 system prompt 的每一個字,整場對話每一輪都在付錢。用本 repo 的 skill 實際量一次(token 用 tiktoken o200k_base,2026-09-24 的檔案快照):
| 層級 | 什麼時候進上下文 | 規格建議 | make-lesson 實測 | 本 repo 9 個 skill 合計 |
|---|---|---|---|---|
| Level 1 | 一直都在(啟動就載入) | 約 100 tokens/個 | 156 | 561 |
| Level 2 | 判斷用得上才讀 SKILL.md | < 5,000 tokens、< 500 行 | 4,189 | 20,695 |
| Level 3 | 做到那一步才讀/跑 | 不限(沒讀就不花) | 42,028 | 51,717 |
Level 1 的 561 只算 8 個能被模型自己叫的 skill(第 9 個 grill-me 設成只能手動叫,不進清單)。
換成 Claude 自己的 tokenizer 呢?我們在一個玩具專案裡用 Claude Code 2.1.281+claude-haiku-4-5 實測, 只改 .claude/skills/ 裝了什麼,讀 API 回報的 input tokens: 沒裝 skill 時每輪 21,012;裝進 make-lesson +198、publish-videos +212、九個全裝 +468; 而一旦真的叫了 make-lesson,下一輪一口氣 +5,376——那就是 Level 2 進場。
便宜不等於免費。Claude Code 把所有 skill 的描述排成一張清單,這張清單有字元預算(文件寫「context window 的 1%」)。 九個全裝那一次,debug log 當場跳出:
21 個=Claude Code 內建的 13 個+我們 8 個。超過預算,最少用的 skill 描述會被截短、甚至只剩名字—— 而描述正是模型決定要不要叫它的唯一依據(下一節)。skill 不是裝越多越好,用不到的就關掉。
description 是 skill 唯一的招牌
開場那個實驗的設計照 agentskills.io 的建議:12 句 prompt,6 句該觸發(有直說「週報」的、沒說的、英文的), 6 句是「差一點」——跟 timesheet 或主管沾邊、但要的不是週報(改一筆工時、寫請假信、寫轉 Excel 的程式)。 每句跑 3 次,看模型有沒有載入 skill。Claude 那組結果(claude-haiku-4-5,2026-09-24,每格 18 次):
| description | 該觸發(18 次) | 誤觸發(18 次) | 觀察 |
|---|---|---|---|
| 模糊版「協助處理報告。」 | 12 | 0 | 英文的 status update 3 次全沒叫到;「主管要本週工作摘要」只叫到 1 次——另外兩次它自己讀 CSV、自己寫 |
| 精準版(上一節那段) | 18 | 0 | 每一句該觸發的都是第一步就叫 skill |
| 太寬版「跟 timesheet、工時、主管或撰寫文件有任何關係就用」 | 18 | 1 | 請假信那句被叫了 1 次;Claude 其實很保守 |
沒叫到 skill 的那幾次,haiku 自己寫的週報裡「總工時」分別寫了 128.5、141、138 小時—— 正確答案是 141。格式也每次都不一樣。skill 沒被叫到的代價不只是「沒用到」,而是SOP 整個沒發生。
把開場 hero 的模型切到 qwen3.5-2b(開源 2B 小模型,接在本課的最小 skill loader 上): 同樣 12 句,模糊版和太寬版是每一句都叫,連「什麼是 OKR」都 3/3;精準版也還有 10/18 次誤觸發。 觸發品質是 description 和模型能力一起決定的——換 agent、換模型,觸發測試要重跑。
寫 description 的四條原則(agentskills.io〈Optimizing skill descriptions〉):
- 用祈使句告訴 agent 何時動手:「當使用者…時使用」,而不是「這個技能可以…」。
- 寫使用者想達成什麼,不是內部怎麼實作。
- 主動一點:把沒說出關鍵字的說法也列進去(「即使他沒說出『週報』兩個字」、status report、進度彙整)。
- 畫邊界:加一句「不用於…」,擋掉差一點的請求;總長守在 1,024 字內、重點放最前面(清單超預算時後面會被截掉)。
另一個實測細節:「timesheet.csv 每個欄位代表什麼意思?」三個版本都沒觸發——agent 通常只在任務需要額外知識或流程時才去翻 skill, 自己讀一下檔就能答的事,description 寫得再像也不會叫。
確定性的工作交給程式,不靠模型
週報 SOP 第 1 步寫「工時一律照抄腳本輸出,不要自己加總」。為什麼要這麼兇?我們把 36 筆 timesheet 直接貼給模型、 不准用工具,要它加總四個專案的工時(實測,2026-09-24):
| 誰來加總 | 次數 | 四個專案全對 |
|---|---|---|
| qwen3.5-2b 心算(12/24/36 筆各 5 次) | 15 | 0 |
| claude-haiku-4-5 心算(36 筆) | 5 | 3 |
| scripts/hours.py(15 行 Python) | 每次 | 每次 |
重點不是「模型不會加法」——大模型多數時候是對的。重點是 SOP 要的是每次都對、而且查得到為什麼對。 寫成腳本還順便省 token:本 repo 的 publish-videos skill 帶了 3 支腳本、共 7,037 tokens 的程式碼, 它的 SKILL.md 開宗明義:「格式由程式與 video/config.json 決定,不由對話決定」——那 7 千 tokens 從來不必進上下文。
實驗場 4️⃣ 有兩段載入了 skill 的完整執行紀錄可以重播。haiku 照 SOP 叫 skill → 跑 hours.py → 讀 timesheet → 讀範本 → 交件, 工時表一字不差是腳本輸出;但第 4 步「要給主管看就讀文風規則」它沒做——Level 3 讀不讀,是模型判斷的。 2B 小模型也照做跑了腳本、工時全對,卻跳過「讀 note 欄」,完成事項寫成空話: 交給腳本的部分穩如泰山,靠模型自律的部分看模型本事。
Skill、MCP、CLAUDE.md、subagent、slash command:什麼時候用哪個?
| 機制 | 什麼時候進上下文 | 適合放什麼 | 本站 repo 的真實例子 |
|---|---|---|---|
| CLAUDE.md(專案記憶) | 每次對話開頭整份載入 | 每一件事都要守的規則、專案地圖 | 「course id 全站唯一」「marimo 全站釘同一版」這類硬性約束 |
| Skill | description 常駐,全文用到才讀 | 某類任務的 SOP+腳本+範本 | make-lesson(建課)、publish-videos(上傳影片) |
| MCP server | 工具說明進上下文,呼叫時才執行 | 連外部系統、拿即時資料(程式跑在另一個行程) | 本站 MCP 系列課;新版協定見補充 D |
| Subagent | 自己一份獨立上下文 | 大量探索、平行工作、不想污染主對話 | 這個補充系列 6 課,就是 6 個 subagent 平行各寫一課 |
| Slash command | 人打 /名稱 才跑 | 何時執行要由人決定的固定流程 | grill-me:設了 disable-model-invocation: true,模型不會自己叫 |
它們不是五選一,常常疊著用:本 repo 的 CLAUDE.md 有一節的標題就是「建課一律走 make-lesson skill」—— 記憶負責指路、skill 負責做法。在 Claude Code 裡,自訂 slash command 已經併進 skill(同一個資料夾格式), skill 也能設定在 subagent 裡執行、能叫 MCP 工具。用程式呼叫這一整套(Agent SDK)是補充 C 的主題。
先自己判斷,再展開看答案:
「所有回覆都要用繁體中文、金額一律加千分位」
CLAUDE.md。每一次對話、每一件事都要守的規則,本來就該一直在上下文裡;做成 skill 反而要等模型「判斷用得上」才讀。「每月初照 12 步流程做月結報表,其中金額加總常出錯」
Skill+scripts/。只在月結時需要(description 寫清楚何時用),加總交給腳本;平常只佔幾十個 tokens。「查公司 CRM 裡某客戶的最新訂單」
MCP server(或一般的工具呼叫)。資料在外部系統、每次都要即時查,這是「工具」不是「做法」。 若查完還有固定的整理 SOP,可以再包一個 skill 來指揮怎麼用這個工具。「翻完整個 repo 的 300 個檔案,找出所有用到舊 API 的地方」
Subagent。大量讀檔會把主對話的上下文塞爆;讓分身在自己的上下文裡找,只把結論交回來。「正式部署到 production」
Slash command(或 skill 加上 disable-model-invocation: true)。流程可以寫好, 但「什麼時候部署」要由人決定,不能讓模型覺得時機到了就自己跑。一份 SKILL.md,幾十個 agent 讀得懂
agentskills.io 的支援名單在 2026-09-24 列了 46 個產品,包括 Claude(網頁版與 API)、Claude Code、OpenAI Codex/ChatGPT、 GitHub Copilot、VS Code、Cursor、JetBrains Junie、Gemini CLI、Kiro、Roo Code,以及開源的 Goose、OpenCode、OpenHands 等。 各家放 skill 的位置不同:Claude Code 讀 .claude/skills/,Gemini CLI 讀 .gemini/skills/ 或別名 .agents/skills/。本站 repo 的做法是 grill-me 本體放在 .agents/skills/、從 .claude/skills/ 建一個符號連結過去——一份檔案,兩邊都讀得到;旁邊的 agents/openai.yaml 是 Codex 自己的擴充設定。
可攜性有兩個真實的坑,都是這一課實測撞到的:
- 擴充欄位:用官方參考驗證器(skills-ref 0.1.1)掃本 repo 的 9 個 skill,8 個通過, grill-me 被擋下——它用了 Claude Code 專屬的 disable-model-invocation。
- 寬容的解析器會藏錯:description 裡寫了半形冒號(用途: 產生週報),官方驗證器判 YAML 壞掉; 同一份檔案放進 Claude Code,它照樣把整串字當 description 讀進清單。在這家能跑,不代表換一家也能。
分享 skill 之前,跑一次官方驗證器(免費):
不花錢、不用任何服務,自己動手的路徑:
- 寫與驗:skill 只是文字檔,任何編輯器都能寫;實驗場 5️⃣ 的驗證器逐條照官方規則, 上面那行 uvx 是官方工具本身(本課實測過)。
- 自己當 agent:本課的實測腳本 裡有一支約 60 行的最小 skill loader——清單放 system prompt、模型要求才塞 SKILL.md、要求才跑腳本, 接任何 OpenAI 相容端點(預設是本機 Ollama 的 http://localhost:11434/v1)。 我們用 qwen3.5-2b 跑通過(就是 hero 裡 qwen 那組);Ollama 本身沒有實測,走的是同一個介面。
- 用現成的開源 agent:Goose、OpenCode 等都在 agentskills.io 的支援名單上、也能接本機模型——本課沒有逐一實測。
- 注意:網路上「用免費的 Gemini CLI 玩 skill」的舊教學已不適用——Google 公告 Gemini CLI 的免費使用在 2026-06-18 停止服務、改由 Antigravity CLI 接手。
本課名詞速查卡
| Agent Skill | 一個資料夾+一份 SKILL.md(frontmatter+SOP),可附 scripts/references/assets——把做法打包給 agent。 |
| 漸進揭露 | 三層載入:name+description 常駐 → 用得上才讀 SKILL.md → 做到才讀檔/跑腳本。用到才付 token。 |
| description | 模型決定叫不叫 skill 的唯一依據:寫「何時用」、列沒講關鍵字的說法、畫「不用於」的邊界;換模型要重測。 |
| scripts/ | 確定性步驟交給程式:程式碼不進上下文,只有輸出進;每次都對、查得到為什麼對。 |
| CLAUDE.md vs Skill | 永遠要守的進記憶(每次都載入);特定任務的做法進 skill(用到才載入)。 |
| agentskills.io | 開放標準:name ≤64 小寫英數連字號且同資料夾名、description ≤1,024;各家擴充欄位不通用,分享前先跑官方驗證器。 |
換你動手
在實驗場 2️⃣ 把 skill 數拉到 60、用到 1 個、20 輪、SKILL.md 選 make-lesson:全塞 system prompt 的方案每輪要多少 tokens? 塞得進 200K context 的模型嗎?下面那行 listing 字元數又超過 8,000 預算多少?
在實驗場 3️⃣ 選 Claude,重播「s4(英文的 status update)」的模糊版與精準版:兩者的第一步各是什麼? 精準版 description 裡是哪幾個字讓它對上這句英文?
挑一個你工作上每週都做的 SOP,在實驗場 5️⃣ 寫出它的 SKILL.md:通過驗證、description 講清楚做什麼/何時用/不用於什麼, 再替它想 3 句該觸發、3 句「差一點」不該觸發的 prompt。
卡住了?三題在實驗場 6️⃣ 都有折疊解答——先自己做,再打開對照。
情境測驗
離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。
Q1 情境題
團隊整理出 30 份作業 SOP(發版檢查、月結、客訴回覆…),每份 2–4 千 tokens。有人提議:「全部貼進 CLAUDE.md,AI 就什麼都會了。」你會怎麼做?
30 份 × 3 千 tokens ≈ 9 萬 tokens,放進 CLAUDE.md 等於每一輪都重送 9 萬——對話越長越貴,還會擠壓真正的工作空間(實驗場 2️⃣ 可以拉給自己看)。拆成 30 個 skill,平常只有 30 段描述常駐(本 repo 實測一個約 30–170 tokens),真的要做月結才把月結那份讀進來。B 只是換個地方塞:一個大 skill 一觸發就是 9 萬 tokens,而且「公司所有作業流程」這種描述什麼都像、什麼都不準。D 搞錯層次:SOP 是「做法」,MCP 是連外部系統的「工具」;需要查外部資料時,skill 可以指揮模型去用 MCP 工具,兩者是搭配關係。
Q2 錯誤診斷
你的 weekly-report skill 在 Claude Code 裡用得好好的。同事要拿去別的 agent 用,先跑了官方驗證器,卻得到下面的錯誤。最可能的原因與修法是?
錯誤訊息的箭頭指在第 3 行第 16 欄——「用途:」後面那個冒號。YAML 看到沒加引號的值裡又出現「冒號+空格」,會以為你要開始另一組鍵值,於是判定格式錯誤。修法:description: "用途: 產生週報: 每週五用",或用全形「:」、或改成 description: > 的區塊寫法。為什麼 Claude Code 沒事?我們實測(2.1.281)它的解析很寬容,照樣把整串字當 description——寬容的 agent 會把錯藏起來,換到嚴格的解析器才爆,所以 C 正好說反。A 不對:官方驗證器對中文 description 完全沒意見(同一份檔換成全形冒號就通過);D 是無中生有,規格只要求 1–1,024 字。英文使用者最常撞的版本是 description: Use when: ...,原因一模一樣。
Q3 錯誤診斷
專案裝了下面這個 skill。使用者說「write up this week's status update for the team lead from timesheet.csv」,實測 3 次 Claude 都是先讀 CSV 就自己寫,從沒叫 skill,交出來的總工時還一次 141、一次 138。最該先改哪裡?
模型決定要不要叫 skill 時,只看得到 name 和 description——本文寫得再好,沒被叫到就等於不存在(所以 C 不對)。「協助處理報告。」沒說何時用、沒有任何跟 status update 對得上的字,英文請求自然對不上。我們實測把同一個 skill 的 description 換成精準版(寫明週報、本週工作摘要、status report、進度彙整,「即使沒說出『週報』兩個字」,以及不用於什麼),同一句英文 3 次都是第一步就叫 skill。B 可以排除:同一個模糊版,「幫我寫這週的週報」3 次都有叫到——skill 在清單上,只是描述沒涵蓋這種說法。D 解的是「叫了之後能不能跑腳本」,但這裡根本沒叫。
Q4 情境題
你想讓 agent 在「使用者說要發新版」時照 12 步流程做:跑測試、比對三個檔案裡的版本號一致、產 changelog。其中版本號比對很容易看走眼。最合適的包法是?
「只在發版時需要」=skill 的主場(平常只佔描述那幾十個 tokens);「很容易看走眼」=交給腳本(每次都對、程式碼不進上下文)。B 是部分正確:包成 skill 對了,但把確定性的比對交給模型「仔細核對」——實驗場 4️⃣ 的心算實測就是答案:大模型多數時候對,但不是每次。A 讓 12 步在每一輪都佔上下文,跟發版無關的對話也在付錢。C 解的是「上下文要隔離」,發版流程的問題不在這裡;而且 subagent 裡要照什麼 SOP 做,最後還是得寫成 skill 或指令。