Claude Code:從 claw-code 的分析看一個 Coding Agent 的設計關心
claw-code 是 Claude Code 的 clean-room 重寫版。它本身是一個研究工具,不過有意思的地方不是重寫這件事,而是它帶出來的參考資料:29 個子系統 JSON 快照,描述了一個 1902 個 TypeScript 檔案、104 個 hook 模組、130 個 service 模組的系統。
讀這些快照,有件事讓我停下來:安全相關的基礎建設佔了多少份量。BashTool 子目錄有 9 個模組(主入口、權限層、安全檢查、命令語義分析、破壞性命令警告、sandbox 判斷……),hooks/ 有 104 個模組,permissions/ 貫穿整個架構。這些數字說的事情比任何功能清單都直接:Claude Code 的核心問題,是怎麼讓一個有能力執行任意 shell command 的 Agent 在你的機器上安全地工作。
讀完這篇,你會理解:
- Claude Code 的 Hook 系統為什麼設計得這樣簡單,三個 exit code 決定一切
- PermissionMode 的有序 enum,以及為什麼 shell execution 的子系統要反覆分拆
- System Prompt Dynamic Boundary 是什麼,以及它解決的 prefix cache 問題
- Compaction 為什麼是本地確定性算法(不用 LLM),和 CrewAI 的對比
- Hooks / Skills / MCP 三個擴展層的設計分工
Hook 系統:用 exit code 定義一條攔截線
Claude Code 的 Hook 系統設計讓我意外的,是它有多簡單。
協議是這樣的:每次 Agent 要呼叫工具,Claude Code 會先執行所有設定的 hook command。這些 command 收到的是一個 JSON payload(透過 stdin),裡面是「誰要做什麼」:
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {"command": "rm -rf ./build"},
"tool_result_is_error": false
}
然後 hook command 用 exit code 表達意見:
exit 0 = 允許,繼續執行
exit 2 = 拒絕,阻擋工具執行(stdout 作為拒絕訊息)
其他非零 = 警告,繼續執行,但 stdout 附加進工具結果
任何可執行檔都可以當 hook,shell script、Python script、Go binary,沒有限制。設定長這樣:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": ".claude/hooks/validate.sh"}]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{"type": "command", "command": "eslint --fix $CLAUDE_FILE_PATHS"}]
}
]
}
}
這個設計有一個值得注意的地方:hook feedback 不論是 allow 還是 deny,都會進入 model context。exit 0 的情況下,hook 的 stdout 可以作為附加資訊送進對話;exit 2 拒絕時,拒絕原因也進入 context。這意味著 hook 不只是守門員,它還可以悄悄注入資訊,讓 Agent 在下一步知道上一個工具被攔截的原因。
對比 CrewAI 的 Guardrail:兩者都在攔截輸出,但層次不同。Guardrail 是「task 完成後,檢查結果是否符合規格」,是業務層的驗收。Claude Code 的 hook 是「工具執行前後,決定這個 shell command 能不能跑」,是執行層的安全閘。一個在意輸出的品質,一個在意執行的安全性。
Permission 系統:有序的 Mode 層級
如果說 hook 是外部可設定的攔截層,Permission 系統就是內建的信任模型。
Claude Code 的 PermissionMode 是一個有序的 enum:
ReadOnly < WorkspaceWrite < DangerFullAccess < Prompt < Allow
每個工具有它需要的最低 permission mode。bash 工具的 required_permission 是 DangerFullAccess,是所有工具裡最高的,因為 shell execution 可以做任何事。read_file 只需要 ReadOnly,write_file 需要 WorkspaceWrite。
當 Agent 要呼叫一個工具,runtime 會比較 active mode 和 required mode:active mode 大於等於 required mode 就放行;否則拒絕,或是進入 Prompt mode,彈出一個 Y/N 讓使用者決定。
讓這個設計真正有彈性的,是 PermissionPrompter trait 的設計:
InteractivePrompter → 終端 Y/N 提示,等人類確認
RecordingPrompter → 預設答案(測試用)
None → 自動拒絕所有需要升級的操作(headless 模式)
同一個 ConversationRuntime,透過注入不同的 prompter,可以在互動模式(人工確認每個危險操作)、自動化腳本(headless,遇到需要升級的操作直接停下)、測試環境(預設答案跑完整流程)之間切換,不需要修改核心 loop 邏輯。
BashTool 子系統的分拆也在說同一件事。9 個模組,bashPermissions.ts、bashSecurity.ts、commandSemantics.ts、destructiveCommandWarning.ts、shouldUseSandbox.ts、readOnlyValidation.ts、sedValidation.ts……這不是過度設計,而是在說 shell 執行的「安全」這個問題有多少個維度。什麼命令是破壞性的?sed 的哪些用法需要特別處理?什麼情況下要進 sandbox?每個問題都是一個模組在回答。
System Prompt Dynamic Boundary
Claude Code 有一個設計細節很少被討論:system prompt 的組建方式。
大多數框架在每一輪對話前,會重新組建完整的 system prompt,把環境資訊、設定內容、CLAUDE.md 全部塞進去,送出去。問題是 Anthropic API 有 prefix cache:如果 prompt 前綴和上一輪完全一樣,API 就不需要重新處理那部分,可以省下大量 input token 費用。只要 system prompt 每輪都不一樣,cache 就完全沒用。
Claude Code 的解法是一個叫做 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 的 sentinel string,把 system prompt 切成兩段:
[靜態部分,可快取]
intro | system rules | task-doing guidelines | CLAUDE.md 固定內容
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__
[動態部分,每次刷新]
environment info | git status | project context | 當前 session 變數
靜態部分每輪都一樣,享受 prefix cache;動態部分每次刷新,每次重新計算。快取優化不是事後加進去的補丁,而是在架構層就決定了哪些內容是穩定的、哪些是會變的。
這個解法和 hermes-agent 的做法形成有趣的對比。hermes 的選擇是:system prompt 在 session 開始時 build 一次,之後永遠不重建。兩個方案都在解 prefix cache 的問題,但方向不同:hermes 用「完全不改」換取 cache 命中,Claude Code 用「切成兩段」讓動態內容和快取策略共存。
Compaction:確定性算法,不用 LLM
長對話的 context 管理,是這個系列每個框架都必須面對的問題。Claude Code 的答案,和前面所有框架都不一樣。
觸發條件:累積 input token 超過 200,000,自動壓縮。壓縮的格式是結構化的 XML:
<analysis>
推理過程(不保留)
</analysis>
<summary>
- scope count: N
- tool mentions: bash, read_file, edit_file
- 最近 3 個 user requests: ...
- pending work: TODO / next steps / remaining tasks
- key files: ./src/main.py, ./config.yaml
- timeline: [完整執行事件序列]
</summary>
<analysis> 裡的推理過程在壓縮後丟棄,<summary> 裡的結構化狀態保留。這個算法是本地確定性的,不額外呼叫 LLM,runtime 直接計算,沒有額外成本也沒有隨機性。
對比 CrewAI 的 memory 系統(EncodingFlow 會呼叫 LLM 推斷重要性和 scope)和 hermes-agent 的 compaction(用 LLM 對壓縮段落做摘要),兩者都依賴 LLM 的判斷來決定「什麼值得保留」。Claude Code 的判斷是結構化的:pending work、key files、最近的 request、tool mentions,這些不需要 LLM 來推斷。
這個選擇背後有一個假設:coding agent 在長對話裡,需要確定性保留的不是「語意上最重要的事情」,而是「正在進行的工作狀態」。pending work 是什麼?動過哪些檔案?這些是確定性的事實,不需要語意判斷。
三個擴展層:Hooks / Skills / MCP
Claude Code 有三個擴展機制,乍看都是「讓 Agent 能做更多事」,但設計上服務的是三個完全不同的需求。
Hooks 是執行層的介入。操作對象是工具呼叫本身,用來在 Agent 呼叫 Bash 之前驗證命令、在它寫完檔案之後跑 linter、在任何工具結果裡注入額外資訊。Hook 用 shell 協議(exit code)定義,不需要理解 Claude Code 的內部結構,任何能寫 shell script 的人都可以用。
Skills 是 context 層的擴展。每個 skill 是一個 Markdown 文件,定義一個可複用的「slash command 巨集」,不介入工具執行,而是在對話層注入行為。/commit、/review-pr、/loop 這類 skill,本質上是在告訴 Agent「遇到這個 trigger,按照這份說明做事」。claw-code 的參考資料顯示,Claude Code 有 20 個 bundled skill(batch、loop、skillify、scheduleRemoteAgents……),而且 skill 可以從 MCP server 動態提供。
MCP 是協議層的整合。JSON-RPC 2.0,讓外部 server 提供工具和 skill。5 種 transport(stdio subprocess、HTTP/SSE、WebSocket、OAuth gate……),支援整個 server 的生命週期管理。MCP 解決的問題不是「怎麼介入執行」或「怎麼複用行為」,而是「怎麼把外部能力接進來」。
三層的分工很清楚:有什麼事情需要在工具執行前後介入,用 Hook;有什麼行為需要複用,定義成 Skill;有什麼外部能力需要整合,走 MCP。三個需求不互相污染,各自走各自的層。
這些設計在問什麼問題
把這幾個機制放在一起,Claude Code 的問題比「怎麼做一個 coding assistant」更具體:怎麼讓一個有能力在你的機器上執行任意命令的 Agent,安全地、可控地工作。
這個問題的難度在於它有兩個互相拉扯的面向。讓 Agent 真的有用,它需要能執行 shell command、能改你的檔案、能接入外部工具。但讓這件事安全,你需要一個地方能攔截、能分級授權、能審計。Claude Code 的設計,是把這兩件事分別交給不同的層:MCP 和 Skills 負責「能做什麼」,Hooks 和 Permission 系統負責「能不能做、由誰決定」。
在這個系列裡,每個框架問的問題都不同。hermes 問「怎麼讓 Agent 越用越好」,OpenClaw 問「怎麼讓 Agent 永遠在線」,CrewAI 問「怎麼讓一組 AI 像組織一樣工作」,Claude Code 問的是「怎麼讓 Agent 安全地在你的環境裡執行」。這個問題決定了它為什麼在 bash 工具上花了 9 個模組,為什麼 hook 系統有 104 個模組,為什麼 permission mode 是有序的 enum 而不是一個布林值。
結語:分析 claw-code 帶出來的參考資料,最讓我印象深刻的不是某個機制有多聰明,而是安全基礎建設佔的份量:104 個 hook 模組、bash 子系統的 9 層分拆、貫穿整個架構的 permission 系統。這些不是功能,是對「在你的機器上執行」這件事有多謹慎的一種量化。