深入理解 Claude Code:從 CLAUDE.md 到 Hooks、Skills、Subagents..

如何讓各種 Coding Agent 更好的幹活?

在常規的對話外,Claude Code(也可以是 Codex)其實還提供了一些別樣的控制(或者說:上下文注入)方法,比如:CLAUDE.mdRulesSkillsSubagentsHooksOutput Styles、以及 System Prompt Append

接下來,我會具體聊聊這些奇奇怪怪的東西,用最不繞彎子、最把你穩穩接住🫴的方法,嘗試講清楚這些玩意兒都是個啥,讓你最聽人話👂的知道

 這些方法各自適合放什麼樣的指令

 為什麼在 CLAUDE.md 裡寫「永遠不要做 X」不靠譜

 Skills 和 Subagents 到底怎麼玩

 Hooks 是怎麼做到確定性執行的

 Dynamic Workflows 怎麼讓 Claude 自己寫編排指令碼

此外,最近 Claude 最近還多了一個叫 Dynamic Workflows 的能力,可以讓 Claude 自己寫編排指令碼、協調多個 agent 平行幹活,這裡也會嘗試講清楚

劇前提要

首先,我們必須要理解大模型的工作原理,簡單來說就是【塞進去足夠的上文】,模型就能夠【給出足夠好的結果】:我在央媒的分享:上下文即一切

AI 的能力、問題和用法,本質都由上下文決定。使用者發一條請求,AI 把請求和它能拿到的所有材料拼在一起,組成一段完整的上下文,再往下生成結果...循環往復

而 Agent,則是在你給到初始的目標、背景資訊、限定條件等等之後,它去自己建構上下文

劃重點:Agent 和 ChatBot 的區別在一件事上【誰來建構上下文】

ChatBot 靠人喂,你給它什麼材料它就用什麼材料

Agent 會自己搜網頁、讀文件、調工具,把有用的內容寫進上下文,發現不夠還會自動回頭繼續找

對於 Agent 來說,它只能看到自己上下文內的內容;那麼通過一些工程手段,去限定 Agent 在那些步驟下,能看到那些東西,就變得尤為重要,於是我們就有了如 claude.mdskill 等上下文注入方法

這裡我做了一個表,方便大家更好的進行比較不同的上下文注入方法

如果追一下這幾個東西的設計方法的話,會發現更多有趣的點:

CLAUDE.md 是唯一一個「全程載入、全程佔 token」的方法。你寫的每一行,不管這次任務用不用得上,都在消耗上下文窗口

Skills 的設計很聰明:只有名字和描述在啟動時載入,完整內容等到被呼叫時才進上下文。用完之後如果上下文滿了,最舊的 skill 會被踢掉

Subagents 也是一種上下文注入,它是另起一個對話,在自己獨立的上下文窗口裡跑,跑完只把最終結果返回給主會話,所以從主會話角度來看,的上下文成本是零

Hooks 完全繞過了上下文窗口。它是程式碼,由 harness 在外部執行

下面,我們來具體的講解每一種上下文注入

CLAUDE.md算是項目的「說明書」,Claude Code 啟動時自動讀入

【全程都把你穩穩接住】

這東西非常類似於 ChatGPT 裡面的 Instruction/自訂指令,或者 API Call 裡的 System Prompt,在項目啟動時就會讀取

在 Claude 裡,這個叫做 CLAUDE.md

在 Codex 裡,這個叫 agents.md

這些個檔案時放在根目錄裡的,Claude Code 啟動後就會自動讀取,你可以在裡面去約定一些裡面寫上建構命令、目錄結構、編碼規範、團隊約定什麼的

然後這裡得說一下,CLAUDE.md 分兩種載入模式:

始終載入根目錄的 CLAUDE.md,加上你本地個人偏好的 CLAUDE.md。啟動即載入,壓縮後重新讀取,全程都在

按需載入子目錄下的 CLAUDE.md(比如 app/api/CLAUDE.md),只在 Claude 讀取該目錄下的檔案時才載入。壓縮後丟失,等到再次碰那個目錄才重新載入

子目錄 CLAUDE.md 只在 Claude 碰到那個目錄時才載入

對此,官方建議給每個團隊的目錄放自己的 CLAUDE.md,開發者用 claudeMdExcludes 配置跳過不相關團隊的檔案,團隊之間不互相佔用上下文

也如同會不斷膨脹的 instruction 一樣,CLAUDE.md 也可能會像所有沒有 owner 的配置檔案一樣不斷膨脹...畢竟人總喜歡不斷的積累規則、加規則,然後這個 CLAUDE.md 也會不斷膨脹,讓指令遵循率也下降

CLAUDE.md 的正確用法

告訴 Claude 項目長什麼樣、建構命令是什麼。流程和約束放到 Rules、Skills 這些按需載入的機制裡

對於組織等級的安全策略和合規要求,可以通過 MDM 或配置管理工具統一部署一份集中管理的 CLAUDE.md 到開發者機器上,這份檔案不能被個人配置排除

Rules

這給特定檔案或目錄設定的約束,可以做到「碰到這類檔案才生效」

Rules 是 .claude/rules/ 目錄下的 markdown 檔案。你可以把它理解成更精細的 CLAUDE.mdCLAUDE.md 是全域生效的,Rules 可以只在碰到特定路徑的檔案時才載入

不加路徑限定的 Rule 跟寫在 CLAUDE.md 裡沒區別:啟動即載入,全程佔 token。加了路徑限定就不一樣了

--- paths:   - "src/api/**"   - "**/*.handler.ts" --- 所有 API handler 必須用 Zod 做輸入校驗

只有當 Claude 讀取 src/api/ 下的檔案時,這條 Rule 才會載入。做文件的時候,這條規則壓根不在上下文裡,不浪費 token

至於什麼時候該用 Rule?

當一條約束是跨目錄的(比如所有 migration 檔案只能追加、所有測試檔案必須用某個 mock 庫),但又不需要全域生效時,path-scoped rule 就是最好的選擇

Skills

這個算是大家最熟悉的「技能包」了,只在被呼叫時才載入完整內容,平時只佔幾十個 token

Skills 的位置在 .claude/skills/ 目錄下,每個 skill 是一個資料夾,裡面有一個 SKILL.md 定義名字、描述和完整指令

然後,這裡我必須要說一點,也是常見的誤區:

Skill 不是提示詞 ,也通常不作為資料來源,通常不會有「小紅書搜尋 Skill」的

Skill 的本質,是把一堆檔案,按約定的結構打包成 .zip,然後把後綴改成了 .skll,所以當你呼叫別人的 Skill 時,本質是把別人打包資料夾喂給大模型如果資料夾裡有提示詞,當然可以把 Skill 當提示詞用,但絕對不能說 Skill 是提示詞

說回來,當 Agent 啟動的時候,並非載入 Skill 的全部內容,而只是載入其名字和描述(幾十個 token 的事),完整內容等到 skill 被呼叫時才進上下文

Skill 呼叫方式有兩種:人工斜槓命令(比如 /code-review)或者 Claude 自動匹配任務

Skills 通過 system prompt 觸發,用到才進上下文

Skill 跟 CLAUDE.md 的核心區別是「流程 vs 事實」。CLAUDE.md 放的是 Claude 需要隨時知道的事實(建構命令、目錄結構)。Skill 放的是需要按步驟執行的流程(部署清單、發佈流程、review checklist)。流程不需要全程佔 token,用到的時候載入就行

當對話內容過長,對話內容被壓縮時,不同的內容會被以不同的方式壓縮:

  • 根目錄 CLAUDE.md 不會丟失,精準說壓縮的時候快取會被清掉,但緊接著 Claude Code 會重新讀取這個檔案,所以它永遠在上下文裡
  • 子目錄 CLAUDE.md 會丟失,但等到 Claude 再次碰那個子目錄它還會重新載入
  • Skills 壓縮後也會被重新注入,但有一個共享的 token 預算上限。如果呼叫了太多 skill,最早的會被踢掉

Anthropic 有一份完整的 skill 建構指南:查看指南 https://claude.com/blog/complete-guide-to-building-skills-for-claude

Subagents

這個也相對容易理解,就是讓 Agent 多開幾個窗口幹活,在獨立的上下文窗口裡跑任務,跑完只把結論交回來

Subagents 是 .claude/agents/ 目錄下的 markdown 檔案,用 YAML frontmatter 定義名字、描述、模型選擇和可用工具。你可以把它理解成一個專門處理某類任務的獨立助手

跟 Skills 的關鍵區別在於隔離性。Skill 在主線程裡執行,你能看到每一步的中間過程。Subagent 在自己獨立的上下文窗口裡跑,主會話看不到它的中間過程

Claude Code 的上下文窗口結構,Subagent 有自己獨立的窗口

一個 subagent 做了 50 輪搜尋、讀了 200 個檔案,主會話的上下文窗口裡只多了一段摘要,這也是為什麼要用它:SubAgent 做髒活累活,不污染主會話,並且能加快效率

在實際使用中,有三個典型場景可以去嘗試:深度搜尋(大量中間結果不需要保留)、日誌分析(翻幾百行 log 找問題)、依賴審計(逐個檢查庫版本)

Subagent 這東西時真的很好用,跑測試什麼的非常省心。這幾天我正在弄 AGI Bar 的 Drinking Plan,然後做了個小程序,完全是 SubAgent 叢集在弄,這裡我放個圖大家感受下(其實中間我中斷了次,實際上已經跑了 40+ 個小時了)

然後,Subagent 可以巢狀,最深五層,後面講 Dynamic Workflows 的時候會看到,這個巢狀能力是大規模編排的基礎

怎麼選?

需要看到每一步的中間結果嗎?需要就用 Skill

不需要,讓它自己跑完給你一個結論,用 Subagent

Hooks

這東西的本質,是觸發指令碼:當遇到特定的問題,就自動執行,跟模型的判斷力無關

Hooks 是 Claude Code 裡最不像 AI 的機制。你在 settings.json 裡註冊一個 hook,指定「當某個事件發生時,執行這條命令」。IFTTT 知道吧?就是那種: if this, then do that

八種 hook 事件覆蓋從啟動到完成的全流程

可以先看一下上面這張圖,有八種 Hook 事件:

PreToolUse工具執行前觸發。攔截危險命令、驗證檔案路徑、自動批准安全操作。exit code 2 直接阻止執行

PostToolUse工具執行後觸發。寫檔案後自動跑 Prettier,改程式碼後自動跑 linter

PermissionRequest權限對話方塊彈出前觸發。npm test 跑了第一百遍還在問你要不要允許?自動批准

SessionStart會話啟動時觸發。自動注入 git status、TODO 列表

PreCompact上下文壓縮前觸發。把完整對話備份到檔案,防止重要決策丟失

Stop回覆完成時觸發。檢查任務是否做完、測試是否通過。返回 "continue": true 讓 Claude 繼續幹

SubagentStop跟 Stop 一樣,但針對 subagent

UserPromptSubmit提交 prompt 時觸發。自動追加當前 sprint 資訊

而 Hook 動作本身有五種類型:command(執行命令)、HTTP(調介面)、mcp_tool(調 MCP 工具)、prompt(讓模型判斷)、agent(啟動 agent 判斷)。前三種完全確定性執行,後兩種用模型的判斷力

如果某件事絕對不能發生,用 hook 或 permission 做硬護欄

比如,之前我在弄「龍蝦大逃殺」的時候,不是被踢了麼...艹...龍蝦大逃殺:存活下來的,拿 Mac mini|群裡有只會踢人的龍蝦

當時我是圖個懶省事兒,直接寫在系統提示詞裡面的,除此之外沒加任何防護,但江湖險惡、人心難測...屮屮屮屮屮....被踢了之後痛定思痛,才上了 permission 硬護欄,活動才能順利進行

對了,忘了說了,當時比賽最終贏家是【四毛】,我加了一個月好友才加上

然後那段時間 Mac Mini 無現貨(這可惡的龍蝦潮),訂等了好久才到

之後還會有類似的活動,歡迎大家來參加:中獎機率倍兒高,獎品也嘛倍好~

話說回來,做安全防護的時候,任何的提示詞防護都是不靠譜的,都很有可能在長會話、壓力大、或被 prompt injection 干擾時失效。而 PreToolUse hook 攔截就是確定性的,作為大守密者,永遠幫你把握住「永遠不要執行 rm -rf」這樣的資訊

更多 hook 配置細節:查看指南 https://claude.com/blog/how-to-configure-hooks

Output Styles

你可以通過這個,修改 Claude Code 的「人設」為貓娘,注入到 system prompt 裡,權重最高但影響面也最大

Output Styles 這個東西的位置,在 .claude/output-styles/ 下面,能夠注入到 system prompt 裡、永遠不會被壓縮,且指令遵循權重最高

但在另外的一方面,你得知道:自訂 output style 會替換掉默認的 system prompt

這裡說一下 system prompt,這是 Claude 裡的內建基礎提示詞,包含了很多定義,比如:怎麼限定改動範圍、安全問題怎麼處理、改完程式碼先跑測試

除非你在 frontmatter 裡加 keep-coding-instructions: true

對此如果你想進一步研究的話,可以先看一下三個內建的 style:Proactive(自主決策多)、Explanatory(教學模式)、Learning(協作編碼)

System Prompt Append

一句話解釋:通過 CLI 參數臨時往 system prompt 後面追加內容,不修改默認指令,只對當次呼叫生效

如果覺得 Output Styles 改動太大,append-system-prompt 是一個更甜品的選擇。它只往默認 system prompt 後面追加一段內容,不替換任何東西。Claude Code 原本的軟體工程指令全部保留

在 CLI 裡,你可以這麼輸入:

claude --append-system-prompt "所有回覆使用中文,程式碼註釋也用中文"

這條指令只對當次呼叫生效,不寫檔案,不跨 session。適合加編碼規範、輸出格式、語氣偏好這類輕量指令

壓縮行為跟 Output Styles 一樣:永遠不會被壓縮

但要注意一個遞減效應:追加的指令越多,Claude 對每條指令的遵循率越低。特別是指令之間有衝突的時候,遵循率下降得更快

常見誤區

對於這些注入方法,其實也有些常見的配置誤區的,這些來自官方,我給翻譯&補充了下:

錯誤:「每次 X,必須做 Y」寫在 CLAUDE.md 裡

比如你希望每次編輯檔案後自動跑 formatter。這類行為應該用 PostToolUse hook。模型選擇做一件事跟自動執行一件事完全是兩碼事

錯誤:「絕對不要做 Z」寫在 CLAUDE.md 裡

絕對性約束用 PreToolUse hook 做硬護欄,exit code 2 直接阻止。組織等級的護欄用 Managed Settings,管理員部署,使用者不能覆蓋

錯誤:30 行的流程寫在 CLAUDE.md 裡

部署流程、review checklist 放 skill。CLAUDE.md 放事實,skill 放流程

錯誤:Rule 沒加路徑限定

只適用於 src/api/** 的約束,沒加 paths: 就跟寫在 CLAUDE.md 裡一樣,全程載入白費 token

錯誤:個人偏好寫在項目級檔案裡

個人偏好(比如 commit message 格式)放使用者級的本地檔案,項目級檔案只放團隊共識

Dynamic Workflows

前段時間,Anthropic 發佈了 dynamic workflows/動態工作流,簡單來說就是讓 Claude 自己寫編排指令碼,協調多個 subagent 平行工作,解決複雜任務

其誕生,是為瞭解決默認 harness 的三個老問題:

偷懶Agentic laziness,安全審查要查 50 項,Claude 查到第 35 項就宣佈完成了

自我偏好Self-preferential bias,讓 Claude 檢查自己寫的程式碼,它傾向於覺得沒問題

目標漂移Goal drift,長會話中每次壓縮都是有損的,邊緣需求和約束容易在壓縮中丟失

在這個過程中,三個核心函數起到了作用:

  • Agent(prompt,opts?)
  • parallel([fns])
  • pipline(items,...)

Dynamic workflow 用獨立上下文窗口隔離每個子任務,從結構上消解這三個問題。觸發方式很直接:跟 Claude 說「用一個 workflow」,或者用觸發詞 ultracode

靜態工作流 vs 動態工作流

至於 Dynamic workflow 的編排方式,A社總結有以下六種:

常見的工作流編排模式

Classify-and-act
先用一個 classifier 判斷任務類型,再分發給對應的專用 agent 處理

Fan-out-and-synthesize

把任務拆成 N 個子任務,每個跑一個 subagent,最後一個 agent 彙總結果

Adversarial verification

每個執行 agent 配一個驗證 agent,按標準對抗性檢查輸出

Tournament

N 個 agent 用不同方法做同一件事,逐對比較選出最優

Generate-and-filter

先大量生成,再過濾去重,只留驗證過的高品質結果

Loop until done

不設固定輪數,循環 spawn agent 直到滿足終止條件

實際案例

以之前 Bun 從 Zig 重寫到 Rust 用了 dynamic workflows 這個事兒為例,過程中每個修復跑一個 subagent 在獨立 worktree 裡改,另一個 agent 對抗性 review,通過後合併

深度驗證模式:執行 agent + 驗證 agent 對抗

Deep research 是 Claude Code 內建的一個 workflow skill(/deep-research)。扇出搜尋、抓取源文件、對抗驗證每條聲明、彙總成帶引用的報告

還有一個反向用法:讓 workflow 翻你最近 50 個 session,找出你反覆修正 Claude 的模式,聚類成規則候選,對抗驗證每條(能不能真的防止之前的錯誤?),通過的寫進 CLAUDE.md

反向用法:從歷史 session 挖掘規則候選



最後

這些上下文注入的方法,本質是延續一個思路:需要的時候,出現;不需要的時候,消失

對應到工程上,就是:不同的指令,要有不同的生命周期

有的需要全程在場(CLAUDE.md),有的只在特定場景出現(path-scoped Rules),有的用到才載入(Skills),有的在獨立窗口裡跑(Subagents),有的完全不佔上下文(Hooks)

把所有指令塞進一個 CLAUDE.md 是最簡單的做法,但也是最浪費、且會污染上下文的做法,做正式項目的時候,就不推薦了

事實放 CLAUDE.md,流程放 Skill,護欄放 Hook,隔離任務給 Subagent

然後,如果整套 Agent 系統是整個團隊在用,就可以嘗試著把這些東西打包成一個 plugin,方便共享/分發 (賽博禪心)