2026 年 3 月 31 日下午,一條推特引爆了整個技術圈——@Fried_rice 曝光了 Claude Code 的完整原始碼外洩事件。消息一出,開發者社群瞬間沸騰。

說實話,作為一個長期關注 AI Agent 架構的工程師,看到這條消息的第一反應不是吃瓜,而是——終於可以驗證我對 Claude Code 的猜想了。之前用它寫程式時,一直好奇它的「手感」為什麼和其他 AI 編程工具完全不一樣:上下文似乎永遠不會丟、工具呼叫快得不像是串行的、權限管控粒度細到單條命令。既然原始碼有了,就來了解一下吧~
如果你只把 Claude Code 當成「命令列版的 Copilot」,那就嚴重低估它了。
從原始碼角度看,Claude Code 是一個完整的 Agent 執行時系統——它有自己的 Agent 主迴圈、工具註冊與調度框架、多層上下文管理策略、權限控制體系、外掛與技能擴充機制、子 Agent 編排能力,甚至還有一套基於 Ink(Terminal 用的 React)的完整終端 UI 框架。
它和 ChatGPT、GitHub Copilot 的本質區別在於:
| 維度 | ChatGPT / Copilot | Claude Code |
|---|---|---|
| 互動模式 | 對話 / 補全 | 自主 Agent 循環 |
| 工具能力 | 無 / 有限 | 47+ 內建工具,支援 MCP 擴展 |
| 上下文管理 | 簡單截斷 | 5 層上下文壓縮管道 |
| 代碼修改 | 輸出程式碼片段 | 直接編輯檔案系統 |
| 權限控制 | 無 | 3 層權限架構 + AST 級命令分析 |
| 子任務 | 無 | 多 Agent 並發編排 |
Claude Code 值得研究的原因很簡單:它是目前工程化程度最高的 AI Coding Agent 實作之一,其架構設計中蘊含了大量可復用的工程模式。
在深入原始碼之前,先看 Claude Code 的全域架構。可以用下列分層視圖理解:
┌─────────────────────────────────────────────────────────────────┐
│ 使用者互動層 (Ink/React Terminal UI) │
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │PromptInput│ │MessageList │ │ Spinner │ │PermissionDialog │ │
│ └──────────┘ └────────────┘ └──────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ REPL 編排層 (screens/REPL.tsx) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ useReplBridge · useCanUseTool · useTypeahead │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Agent 核心層 │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ query() │ │ QueryEngine │ │ SubAgent 編排 │ │
│ │ Agent 主迴圈 │ │ 會話編排器 │ │ (AgentTool) │ │
│ └──────┬───────┘ └──────────────┘ └───────────────────┘ │
│ │ │
│ ┌──────▼───────────────────────────────────────────────┐ │
│ │ 工具調度層 │ │
│ │ toolOrchestration → toolExecution │ │
│ │ StreamingToolExecutor (流式並行執行) │ │
│ └──────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 工具實作層 (47+ Tools) │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Bash │ │ Edit │ │ Read │ │ Grep │ │ Agent│ │ Skill│ ... │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 上下文管理層 │
│ ToolResultBudget → SnipCompact → MicroCompact │
│ → ContextCollapse → AutoCompact → ReactiveCompact │
├─────────────────────────────────────────────────────────────────┤
│ 基礎設施層 │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────────┐ │
│ │State │ │Perms │ │Memory │ │Plugins │ │MCP/Bridge │ │
│ │Store │ │System │ │System │ │/Skills │ │Integration │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────┘
下面逐層拆解。
這是 Claude Code 最核心的程式,位於 src/query.ts。
3.1 整體結構
整個 Agent 迴圈被實作為一個非同步產生器(async generator),核心就是一個 while(true) 迴圈:
// src/query.ts
export async function* query(params: QueryParams): AsyncGenerator<
StreamEvent | RequestStartEvent | Message | TombstoneMessage | ToolUseSummaryMessage,
Terminal
> {
const consumedCommandUuids: string[] = []
const terminal = yield* queryLoop(params, consumedCommandUuids)
for (const uuid of consumedCommandUuids) {
notifyCommandLifecycle(uuid, 'completed')
}
return terminal
}
query() 是一個薄殼,實際邏輯在 queryLoop() 裡。注意回傳型別 Terminal——這是一個離散列舉,表示迴圈退出的原因(completed、aborted、max_turns、prompt_too_long 等)。
3.2 迴圈狀態
每次迴圈疊代之間傳遞的可變狀態被封裝在一個 State 物件中:
// src/query.ts
type State = {
messages: Message[]
toolUseContext: ToolUseContext
autoCompactTracking: AutoCompactTrackingState | undefined
maxOutputTokensRecoveryCount: number
hasAttemptedReactiveCompact: boolean
maxOutputTokensOverride: number | undefined
pendingToolUseSummary: Promise<ToolUseSummaryMessage> | null | undefined
stopHookActive: boolean | undefined
turnCount: number
transition: Continue | undefined // 上一次迭代為什麼繼續了
}
我在讀這段程式時注意到一個設計細節:transition 欄位記錄了上一次迴圈「為什麼繼續」。註解說這是為了讓測試能斷言復原路徑(recovery path)是否觸發,而不需要檢查訊息內容。這是一種很實用的可觀測性設計。
3.3 單次迭代的完整流程
每一次 while(true) 迭代就是一個完整的 think-act 週期。我把讀原始碼時整理出來的流程畫成了這張圖:
┌────────────────────────────────────────────────────┐
│ 單次迭代開始 │
│ │
│ 1. 上下文預處理管道 │
│ applyToolResultBudget (大結果持久化到磁碟) │
│ ↓ │
│ snipCompact (裁剪歷史訊息) │
│ ↓ │
│ microcompact (訊息級微優化) │
│ ↓ │
│ contextCollapse (上下文折疊投影) │
│ ↓ │
│ autoCompact (整個對話摘要) │
│ │
│ 2. 呼叫模型 API(流式) │
│ deps.callModel() → streaming │
│ ↓ │
│ 偵測 tool_use blocks → StreamingToolExecutor │
│ (模型還在生成時就開始執行工具!) │
│ │
│ 3. 錯誤復原 │
│ prompt-too-long → 上下文折疊 / 反應式壓縮 │
│ max-output-tokens → 逐步遞增限制重試(最多 3 次) │
│ 模型故障 → fallback 到備用模型 │
│ │
│ 4. 工具執行 │
│ partitionToolCalls → 並發安全 / 串行批次 │
│ runToolsConcurrently / runToolsSerially │
│ │
│ 5. 續行判斷 │
│ 有 tool_use → needsFollowUp=true → continue │
│ stopHook 注入 → retry │
│ token budget 未耗盡 → continue │
│ 以上都不是 → return Terminal │
│ │
└────────────────────────────────────────────────────┘
3.4 為什麼用非同步產生器?
這個選擇非常值得說明。用 async function* 而不是回呼或 Promise 鏈有幾個關鍵好處:
對比之下,很多 Agent 框架用的是 callback 或 EventEmitter,在錯誤復原和取消傳播上會複雜得多。
3.5 流式工具執行——Claude Code 快的祕密
我在讀原始碼時發現了一個「偷跑」機制:StreamingToolExecutor。
// src/services/tools/StreamingToolExecutor.ts
export class StreamingToolExecutor {
private tools: TrackedTool[] = []
private hasErrored = false
private siblingAbortController: AbortController
addTool(block: ToolUseBlock, assistantMessage: AssistantMessage): void {
// 工具從流中解析出來時就立刻加入執行隊列
// 不需要等待完整的模型回應
}
}
當模型的流式回應中出現 tool_use block 時,StreamingToolExecutor 立刻開始執行這個工具,不等模型回應完成。這意味著當模型還在生成第 2、3 個工具呼叫時,第 1 個工具可能已經執行完了。
更精妙的是它的並發控制:
這個設計讓 Claude Code 在多檔案讀取等場景下有明顯的速度優勢。
4.1 Tool 抽象層
所有工具統一由 buildTool() 工廠函數構建,核心型別定義在 src/Tool.ts:
// src/Tool.ts (簡化)
export type Tool<Input, Output, Progress> = {
name: string
inputSchema: ZodSchema // Zod schema 驗證輸入
call(args, context, canUseTool): Promise<ToolResult<Output>>
prompt(options): Promise<string> // 貢獻到 system prompt
checkPermissions(input, context): Promise<PermissionResult>
isConcurrencySafe(input): boolean // 能否並行執行?
isReadOnly(input): boolean // 是否只讀檔案系統?
isDestructive?(input): boolean // 是否不可逆?
maxResultSizeChars: number // 超過多少持久化到磁碟
// ... 還有渲染、進度、UI 等 ~40 個方法
}
關鍵設計:預設值是 fail-closed 的。
const TOOL_DEFAULTS = {
isConcurrencySafe: (_input?: unknown) => false, // 預設不安全
isReadOnly: (_input?: unknown) => false, // 預設會寫
isDestructive: (_input?: unknown) => false,
}
也就是說,如果一個新工具忘了宣告自己是並發安全的,它會自動被串行執行。這種「安全預設」的設計在大型系統中非常重要。
4.2 工具註冊:條件編譯與死碼剔除
src/tools.ts 中的 getAllBaseTools() 是所有內建工具的註冊中心。我在讀這段程式時被它的條件編譯機制震撼到了:
// src/tools.ts
import { feature } from 'bun:bundle'
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('./tools/SleepTool/SleepTool.js').SleepTool
: null
const CtxInspectTool = feature('CONTEXT_COLLAPSE')
? require('./tools/CtxInspectTool/CtxInspectTool.js').CtxInspectTool
: null
// 內部專用工具
const REPLTool = process.env.USER_TYPE === 'ant'
? require('./tools/REPLTool/REPLTool.js').REPLTool
: null
feature() 來自 bun:bundle,是一個建置時常數。Bun 打包器會在編譯期求值這些表達式,對外部發佈時,feature('KAIROS') 永遠是 false,整個 require() 分支會被徹底剔除——連字串字面量都不會留在產物中。
這意味著 Claude Code 的外部發佈版和內部版本共享同一套原始碼,但透過建置時 feature flag 產出完全不同的產物。這比執行時 feature flag 要乾淨得多。
4.3 工具調度:智慧分批
src/services/tools/toolOrchestration.ts 實作了工具調度的核心邏輯:
// src/services/tools/toolOrchestration.ts
function partitionToolCalls(
toolUseMessages: ToolUseBlock[],
toolUseContext: ToolUseContext,
): Batch[] {
return toolUseMessages.reduce((acc: Batch[], toolUse) => {
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
const isConcurrencySafe = tool?.isConcurrencySafe(parsedInput) ?? false
// 連續的並發安全工具合併為一個批次(並行)
// 非安全工具各自一個批次(串行)
if (isConcurrencySafe && lastBatch?.isConcurrencySafe) {
lastBatch.blocks.push(toolUse)
} else {
acc.push({ isConcurrencySafe, blocks: [toolUse] })
}
return acc
}, [])
}
這裡的關鍵洞察:並發安全性是 per-invocation 的,不是 per-tool-type 的。同一個 BashTool,ls 可能是並發安全的,rm -rf 就不是。這種粒度的控制是透過 isConcurrencySafe(input) 實作的——傳入的是具體的呼叫參數。
4.4 幾個值得深究的工具實作
FileEditTool:搜尋替換而不是行號編輯
// src/tools/FileEditTool/FileEditTool.ts (簡化)
// 輸入: file_path, old_string, new_string, replace_all
Claude Code 選擇了搜尋替換而不是行號編輯來修改檔案。為什麼?
因為行號編輯有一個致命缺陷:當模型產生的上下文與實際檔案不同步時(例如檔案被外部修改了),行號會不對。搜尋替換則更穩健——只要目標文字存在,不論它在哪一行。
此工具內建多重安全機制:
BashTool:最複雜的工具
BashTool 是整個系統中最複雜的工具實作,約 1800 行程式。它的權限系統(bashPermissions.ts)特別值得注意:
還有一個有趣細節:_simulatedSedEdit 內部欄位。當 Bash 指令包含 sed 編輯時,權限對話框會預先計算 sed 的執行結果,讓使用者在審核時就能看到「這條指令會把檔案改成什麼樣」。
AgentTool:子 Agent 編排
AgentTool 是多 Agent 架構的核心,支援多種執行模式:
// src/tools/AgentTool/AgentTool.tsx (輸入 schema 簡化)
z.object({
description: z.string(), // 3-5 詞任務描述
prompt: z.string(), // 完整任務提示
subagent_type: z.string(), // 專用 Agent 類型
model: z.enum(['sonnet', 'opus', 'haiku']),
run_in_background: z.boolean(),
isolation: z.enum(['worktree']), // git worktree 隔離
})
支援的模式包括:
這可能是 Claude Code 原始碼中最精妙的部分。上下文窗口管理不是簡單的「截斷舊訊息」,而是一個 5 層逐級壓縮的管道,每一層有不同的觸發條件、代價與粒度。
5.1 管道全景
Layer 1: Tool Result Budget (每條訊息限額)
↓ 大的工具結果持久化到磁碟,替換為預覽
Layer 2: Snip Compact (歷史裁剪)
↓ 移除對話中間的舊訊息
Layer 3: Microcompact (訊息級微優化)
↓ 編輯單條訊息內容,不破壞 prompt cache
Layer 4: Context Collapse (上下文折疊)
↓ 讀時投影,摘要存在獨立的 collapse store
Layer 5: Auto Compact (全對話摘要)
↓ Fork 一個獨立 Agent 生成對話摘要
↓ (應急) Reactive Compact
當 API 回傳 prompt-too-long 時緊急觸發
5.2 每層的實作細節
我在原始碼中找到 Auto Compact 的觸發閾值計算:
// src/services/compact/autoCompact.ts
export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS // 緩衝 13,000 tokens
}
當估計 token 數超過 contextWindow - 13000 - maxOutputTokens 時觸發。Auto Compact 使用一個獨立的 API 呼叫(fork agent)來生成摘要,避免阻塞主迴圈。
更巧妙的是 Reactive Compact——當 API 回傳 prompt-too-long 錯誤時,這個錯誤會被扣留(withhold)不傳給呼叫方,然後緊急觸發壓縮。如果壓縮成功,迴圈無感知地繼續;如果失敗,錯誤才會浮出。
// src/query.ts 中的錯誤復原邏輯(簡化)
// prompt-too-long 復原路徑:
// 1. 先嘗試 context collapse drain
// 2. 再嘗試 reactive compact
// 3. 都失敗了才讓錯誤浮出
Auto Compact 還有一個熔斷器:連續失敗 3 次後,停止重試。這防止在上下文真的無法壓縮時(比如 system prompt 本身就很大)陷入無限重試。
5.3 為什麼需要 5 層?
每一層解決不同的問題:
分層化設計意味著「輕量操作先執行」。如果 Snip 就夠了,就不會觸發昂貴的 Auto Compact。如果 Context Collapse 把 token 數壓到了閾值以下,Auto Compact 直接跳過。
Claude Code 能直接修改檔案和執行指令,權限系統就是安全命脈。原始碼中實作了三層縱深防禦。
6.1 Layer 1: 規則比對
// src/utils/permissions/permissions.ts (簡化)
async function hasPermissionsToUseToolInner(tool, input, context) {
// 1a. 整個工具被 deny?
const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
if (denyRule) return { behavior: 'deny' }
// 1b. 整個工具需要 ask?
const askRule = getAskRuleForTool(appState.toolPermissionContext, tool)
if (askRule) return { behavior: 'ask' }
// 1c. 工具自身的權限檢查
return await tool.checkPermissions(parsedInput, context)
}
規則來自多個來源,按優先順序:policySettings → userSettings → projectSettings → cliArg → command → session。
規則語法支援模式匹配:
6.2 Layer 2: 工具專屬檢查
每個工具實作自己的 checkPermissions()。例如檔案工具會檢查:
BashTool 的檢查更複雜:
6.3 Layer 3: 互動式審核
當前兩層回傳 behavior: 'ask' 時,進入互動式流程:
hasPermissionsToUseToolInner() → 'ask'
↓
useCanUseTool hook
↓
handleCoordinatorPermission() (協調者 Agent)
↓
Bash Classifier (LLM 分類器預判)
↓
handleInteractivePermission() (使用者審核 UI)
Bash Classifier 是一個值得注意的設計:在 auto 模式下,一個輕量 LLM 會對命令進行安全評估。如果分類器認為安全,可以跳過使用者確認。這是在「安全性」和「流暢性」之間的折衷——既不像 bypassPermissions 那樣完全放鬆安全檢查,也不像 default 那樣每次都彈窗。
Claude Code 沒有用 Redux、Zustand 或任何狀態管理函式庫,而是用 34 行程式實作了自己的 store:
// src/state/store.ts — 完整原始碼
type Listener = () => void
type OnChange = (args: { newState: T; oldState: T }) => void
export type Store = {
getState: () => T
setState: (updater: (prev: T) => T) => void
subscribe: (listener: Listener) => () => void
}
export function createStore<T>(
initialState: T,
onChange?: OnChange,
): Store<T> {
let state = initialState
const listeners = new Set<Listener>()
return {
getState: () => state,
setState: (updater: (prev: T) => T) => {
const prev = state
const next = updater(prev)
if (Object.is(next, prev)) return // 參考相等跳過
state = next
onChange?.({ newState: next, oldState: prev })
for (const listener of listeners) listener()
},
subscribe: (listener: Listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
用 React 的 useSyncExternalStore 搭配選擇器訂閱,只有被選中的 state 切片變動時才觸發重新渲染。這套方案在 CLI 場景下足夠了——不需要 middleware、devtools、時間旅行除錯等 Web 應用常用功能。
AppState 本身使用了 DeepImmutable 類型約束,在編譯期強制不可變。
8.1 System Prompt 的組裝
src/constants/prompts.ts 中的 getSystemPrompt() 回傳一個 string[]——字串陣列而非單一字串。這是為了支援兩層快取:
// src/constants/prompts.ts (簡化)
export async function getSystemPrompt(tools, model): Promise<string[]> {
return [
// --- 靜態內容(全域可快取)---
getSimpleIntroSection(), // 身份介紹
getSimpleSystemSection(), // 核心系統規則
getSimpleDoingTasksSection(), // 編碼行為指導
getActionsSection(), // 可逆性/影響範圍指導
getUsingYourToolsSection(tools), // 工具使用偏好
getSimpleToneAndStyleSection(), // 溝通風格
// === 動態內容邊界 ===
...(shouldUseGlobalCacheScope()
? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY]
: []),
// --- 動態內容(每會話)---
...resolvedDynamicSections, // Memory、環境資訊、MCP 指令等
].filter(s => s !== null)
}
SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之前的內容可以跨使用者/跨會話複用快取(Anthropic API 的 prompt caching 功能),之後的內容每個會話獨立。這直接影響 API 成本——快取命中的 token 價格遠低於全量輸入。
8.2 動態 Prompt Section 的記憶化
每個 prompt section 都透過 systemPromptSection() 包裝,實現會話級記憶化——相同參數只計算一次:
// src/constants/systemPromptSections.ts (概念)
export function systemPromptSection(computeFn) {
// 一個 session 內只計算一次
// 後續呼叫直接回傳快取結果
}
還有 DANGEROUS_uncachedSystemPromptSection()——每個 turn 重新計算。用於 MCP 伺服器指令這種可能在會話中途變動的內容(MCP 伺服器可以動態連接/斷開)。
8.3 Latch 機制
Bootstrap state 中有多個「鎖存器」變數:
afkModeHeaderLatched
fastModeHeaderLatched
cacheEditingHeaderLatched
thinkingClearLatched
這些變數一旦設為 true 就不會回退為 false。目的:如果 fast mode 在會話中途開啟然後關閉,發送給 API 的 header 保持「曾經開啟過」狀態,避免因 header 變化導致 prompt cache 失效。
這種對快取命中率的極致優化在整個程式庫中隨處可見。
9.1 Skill 系統
Skills 是由 Markdown 檔案驅動的能力擴充:
// src/skills/bundledSkills.ts (簡化)
export type BundledSkillDefinition = {
name: string
description: string
whenToUse?: string // 模型何時應該呼叫這個 Skill
allowedTools?: string[] // 限制可用工具
model?: string // 覆蓋模型
context?: 'inline' | 'fork' // 內嵌執行 or fork 子 Agent
hooks?: HooksSettings // 每 Skill 的 hook 設定
getPromptForCommand: (args, context) => Promise<ContentBlockParam[]>
}
Skill 的載入來源有明確優先順序:
一個特別有趣的特性是條件啟動:activateConditionalSkillsForPaths() 會在 FileEditTool、FileReadTool、FileWriteTool 每次操作檔案時被呼叫。當使用者操作某些目錄或檔案模式時,對應的 Skill 會自動啟動。
9.2 外掛系統
外掛分三個層級:
一個外掛可以提供:Commands、Agents、Skills、Hooks、MCP Servers、LSP Servers、Output Styles、Settings——幾乎可以擴充系統的所有面向。
在錯誤處理方面,原始碼定義了 25+ 種 PluginError 型別的聯合型別,每種都攜帶特定的上下文資訊。
9.3 Memory 系統
src/memdir/ 實作了檔案級的持久化記憶:
安全方面,validateMemoryPath() 拒絕相對路徑、根路徑、UNC 路徑、空字元、長度不足 3 的路徑;並且明確排除 projectSettings 作為 autoMemoryDirectory 的來源——防止惡意倉庫將記憶寫入重定向到 ~/.ssh。
10.1 Task 系統
src/tasks/ 不是簡單的代辦清單,而是一個並發任務執行管理器,支援 7 種任務類型:
type TaskType =
| 'local_bash' // 背景 shell
| 'local_agent' // 本地子 Agent
| 'remote_agent' // 遠端 Agent
| 'in_process_teammate' // 進程內協作者
| 'local_workflow' // 工作流
| 'monitor_mcp' // MCP 監控
| 'dream' // 自動記憶整理
Task ID 使用 randomBytes(8) 生成(36^8 ≈ 2.8 兆種組合),註解中明確提到這是為了防止符號連結攻擊。
10.2 DreamTask:AI 的「睡眠整理」
DreamTask 是一個自動記憶整理子 Agent——當觸發條件滿足時,它會回顧最近的對話,將有價值的資訊萃取到持久化記憶檔中。如果被中途 kill,它會回滾整理鎖的時間戳,讓下次會話可以重試。這個設計靈感明顯來自人類的睡眠記憶整理機制。
10.3 背景主會話
當使用者連按兩次 Ctrl+B 時,LocalMainSessionTask 把當前對話推到背景。背景執行的查詢有獨立的 transcript 檔案,會送出進度更新(工具數、token 數),完成時發出 XML 通知。這讓使用者可以在等待長任務時繼續互動。
10.4 Stall Watchdog
LocalShellTask 內建了一個卡死偵測器:
這種主動監控機制在其他 Agent 工具中很少見。
從原始碼分析角度,我總結 Claude Code 強於其他 AI Coding Agent 的根本原因:
11.1 端到端的工程化閉環
不是簡單地把 LLM 包一層 API——從輸入解析、提示詞組裝、上下文管理、工具調度、權限控制、錯誤復原到輸出渲染,每個環節都經過精心設計。
11.2 上下文管理的工程深度
5 層壓縮管道 + 快取優化 + reactive 復原 + 熔斷器。這套系統解決的核心問題是:讓模型在有限的上下文視窗中始終看到最相關的資訊。
大多數 Agent 框架的上下文管理是「滿了就截斷」,Claude Code 的策略是「分層遞進壓縮,輕量操作優先,重量操作兜底」。
11.3 安全模型的深度
三層權限系統 + AST 級命令分析 + LLM 分類器 + UNC 路徑防護 + stale write 偵測 + must-read-first guard。這些不是「加個確認彈窗」能比的——每一層都在解決不同的攻擊面。
11.4 Streaming 執行的效能優勢
模型生成和工具執行重疊進行,加上智慧並發分批,讓 Claude Code 在多檔案操作場景下的延遲遠低於「等生成完再執行」的串行方案。
11.5 設計上的 trade-off 意識
從 Claude Code 原始碼中提煉出的可移植經驗:
12.1 Agent Loop 設計模式
async function* agentLoop(params):
while (true):
preprocess context // 上下文預處理管道
response = callModel() // 呼叫模型
if error: recover or break // 錯誤復原
if tool_use:
results = executeTool() // 工具執行
yield results // 向外暴露事件
continue // 繼續迴圈
else:
return terminal // 迴圈結束
關鍵點:
12.2 Tool 抽象模式
Tool = {
inputSchema, // 輸入驗證(Zod)
call(), // 執行邏輯
checkPermissions(), // 權限檢查
isConcurrencySafe(), // 並發安全性宣告
isReadOnly(), // 只讀宣告
prompt(), // 對 system prompt 的貢獻
}
關鍵點:
12.3 分層上下文管理模式
輕量級(零/低 API 成本)→ 中量級(局部優化)→ 重量級(全量壓縮)→ 應急(reactive)
關鍵點:
12.4 權限 Layered Defense
Layer 1: 靜態規則比對(快,零成本)
Layer 2: 工具專屬檢查(中等,可能有 I/O)
Layer 3: LLM 分類器 + 使用者互動(慢,但最靈活)
關鍵點:
12.5 State Store 模式
34 行的極簡 store + DeepImmutable 類型約束 + useSyncExternalStore 選擇器。證明了在很多場景下,你不需要 Redux。
12.6 Skill 擴展模式
用 Markdown + YAML frontmatter 定義能力擴充。降低擴充門檻——寫一個 .md 檔就能給 Agent 新增能力。條件啟動機制讓 Skill 能根據當前操作的檔案類型自動載入。
技術選型建議
實作路徑建議
讀完 Claude Code 的原始碼,我最大的感受不是「它有多少花俏的技術」,而是每一個設計決策背後都有明確的工程權衡。
非同步產生器不是為了炫技,而是因為它真的解決了 Agent 迴圈中事件流控制的核心問題。5 層上下文管道不是過度設計,而是因為 LLM 的上下文視窗就是 Agent 系統最關鍵的資源瓶頸。34 行的 Store 不是偷懶,而是因為 CLI 工具確實不需要 Redux 那套東西。
Claude Code 證明了一件事:AI Agent 的競爭力不僅在模型能力上,更在工程化的深度上。同樣的基座模型,配上精心設計的工具系統、上下文管理、權限控制和錯誤復原,使用體驗可以天差地別。
這也意味著,AI Agent 領域的競爭正在從「誰的模型更強」轉向「誰的 Agent 工程做得更好」。Claude Code 的原始碼為這個方向提供了一個高水準的參考實作——無論你是在構建自己的 Coding Agent,還是在設計其他領域的 Agent 系統,都能從中找到可借鑑的工程模式。
原始碼面前,了無秘密。但理解設計意圖,需要一點工程直覺。希望這篇文章能幫你建立這種直覺。