大家好,我是雙越。[wangEditor](https://link.juejin.cn?target=https%3A%2F%2Fwww.wangeditor.com%2F) 作者,前百度、滴滴資深前端工程師,慕課網金牌講師,PMP,[前端面試派](https://link.juejin.cn?target=https%3A%2F%2Fwww.mianshipai.com%2F) 作者。
我正致力於兩個專案的開發和升級,有興趣的可以私訊我,加入專案小組。
本文介紹 Claude Code 智慧體的設計和實作,核心的模組架構和流程。
2026 年 3 月 31 日,安全研究員 Chaofan Shou 在分析 Claude Code 的 npm 套件(v2.1.88)時,發現套件內附帶了一個體積高達 59.8MB 的 .map 檔案。這是 TypeScript 編譯器產生的 Source Map——一種將編譯後的 JavaScript 程式碼對應回原始 TypeScript 原始碼的除錯檔案,本不應該出現在發佈套件裡。
原因很簡單:Claude Code 使用 Bun 執行環境進行打包,而 Bun 預設會產生 source map,打包腳本遺漏了排除該檔案的步驟。就這樣,整整約 51.2 萬行、近 1900 個 TypeScript 檔案的完整原始碼,以一種意外的方式進入了公眾視野。
研究者們深入分析之後,得出了一個出乎所有人意料的結論:
其中只有 1.6% 是真正的 AI 決策邏輯,其餘 98.4% 都是確定性的基礎設施——權限控制、上下文管理、工具路由和錯誤恢復邏輯。
這個數字徹底顛覆了很多人對 AI 程式設計工具的想像。大多數人以為 Claude Code 的核心是某種精妙的 AI 推理機制,但實際上,真正呼叫模型的程式碼只是整個系統裡薄薄的一層。支撐起整個產品的,是大量工程性極強、邏輯嚴密的「鷹架」程式碼。
這個發現有深刻的工程啟示:建構一個可靠的 AI Agent,難點不在於呼叫模型,而在於如何管理模型周圍的一切。
while(true) 迴圈,負責協調所有其他模組。main.tsx 是整個系統的入口檔案,但它本身幾乎不包含業務邏輯——它只負責「搭舞台」,然後把控制權交出去。啟動時,它按順序執行四個初始化步驟:
loadConfig() 按優先順序合併多個設定來源。優先順序由高到低依序是:環境變數 → 專案級 CLAUDE.md → 使用者級 ~/.claude/config → 內建預設值。這裡有一個重要細節:CLAUDE.md 在這一步被一次性讀入記憶體,後續不會再解析,這就是為什麼修改 CLAUDE.md 之後需要重新啟動 Claude Code 才能生效。
checkAuth() 尋找 API Key,順序是:ANTHROPIC_API_KEY 環境變數 → ~/.claude/auth 檔案 → 提示使用者登入。找不到就直接報錯退出,這是最高優先順序的前置條件。
registerTools() 將 tools/ 目錄下所有工具載入到工具註冊表(Tool Registry)。注意:此時只是「註冊」中繼資料,不是真正初始化——標記了 defer_loading: true 的工具,要等到被實際呼叫時才會初始化。
detectMode() 讀取命令列參數和環境變數,判斷應該進入哪種執行模式,然後把控制權移交給對應的模組。從這一刻起,main.tsx 退出舞台。
Claude Code 支援五種截然不同的執行模式,涵蓋了從日常互動到 CI 自動化的全部情境。
Interactive 模式(預設):直接輸入 claude 啟動,進入帶有完整 UI 的互動對話迴圈。適合日常開發時的人機協作。UI 層由 React/Ink 驅動,支援鍵盤輸入、串流輸出和歷史對話恢復(--resume <session-id>)。
Pipe 模式:當系統檢測到 stdin.isTTY === false(即輸入來自管道而非終端機鍵盤),自動進入此模式。一次性讀取 stdin 全部內容,執行完畢後退出,不進入互動迴圈。典型用法:
lua 體驗AI程式助手 程式碼解讀複製程式碼git diff | claude -p "幫我根據這份 diff 寫一條規範的 commit message"
cat error.log | claude -p "分析這個錯誤的根本原因"
Headless 模式:使用 -p 或 --print 參數時啟用。不啟動 UI,直接執行給定的 prompt,輸出純文字結果。與 Pipe 模式的差別在於觸發條件——Pipe 是「輸入來自管道」,Headless 是「明確宣告無 UI 執行」。典型用法:
arduino 體驗AI程式助手 程式碼解讀複製程式碼claude -p "給這段程式碼寫單元測試" < utils.ts > utils.test.ts
SDK 模式:環境變數 CLAUDE_CODE_SDK_MODE=1 時啟用,通常由官方 SDK 自動設定。透過 stdin/stdout 交換 JSON 訊息,供其他程式(Python、Go 等)以程式方式控制 Claude Code,類似 Language Server Protocol 的設計思路。
SubAgent 模式:當環境變數 CLAUDE_SUBAGENT_MODE=1 時啟用。這是被主 Agent 的 AgentTool 內部呼叫時自動觸發的模式。子 Agent 擁有完全獨立的上下文視窗,完成任務後將結果作為工具回傳值傳回父 Agent。
模式檢測的優先順序是:SubAgent → SDK → Headless → Pipe → Interactive(預設兜底)。
Interactive 模式下,UI 層由 Ink 驅動。Ink 的核心思想是把 React 的元件樹渲染到終端機——你可以用寫 Web 元件的方式寫終端 UI。
這套渲染器採用遊戲引擎式的髒檢查最佳化:只重繪發生變化的行,而非每次刷新整個螢幕。這確保了在模型串流輸出時,螢幕不會產生閃爍或撕裂。
架構上,UI 層和業務層透過共享的 AppState 物件通訊,彼此不感知內部實作:
messages[]、isLoading、currentToolCall、tokenUsage 等這種分離讓兩層可以獨立測試和替換,也是整個系統保持可維護性的基礎之一。
Agent 主迴圈是整個系統的心臟。理解它,就理解了 Claude Code 的一切。
① 上下文載入:每輪迴圈開始時,系統建構本輪傳送給模型的完整上下文。這包括:從 MEMORY.md 讀取指標索引(體積小,始終駐留)、按需拉取被指標引用的主題檔案、注入 CLAUDE.md 靜態設定、以及計算目前剩餘的 token 預算。
② 工具路由 & 延遲載入:決定本輪 API 呼叫中注入哪些工具的 schema。內建工具 40+,加上 MCP 外部工具可能有幾百個,全部注入會耗盡大量 token。defer_loading 機制確保只有「本輪可能用到的」工具才會被注入(詳見工具系統章節)。
③ 權限檢查(預檢):在送出 API 請求之前,對目前上下文中待執行的操作做粗粒度的權限過濾,並查詢拒絕記錄(DenialLog)——如果使用者曾經拒絕過某個操作,這裡會提前過濾掉,不再打擾。
④ 模型呼叫:整個迴圈中唯一真正呼叫 AI 的步驟,透過 QueryEngine.call() 完成。QueryEngine 內部處理所有網路細節:串流輸出、錯誤重試、token 計費等。主迴圈只關心輸入和輸出,完全不感知 QueryEngine 的內部實作。
⑤ 回應解析 + stop_reason 路由:解析模型回傳的內容,識別 stop_reason 並決定下一步走向。這是整個迴圈的控制流核心(詳見下一節)。
⑥ 工具執行:當 stop_reason === 'tool_use' 時進入此階段。先做精細的權限檢查(包括向使用者彈出確認提示),通過後呼叫對應工具的 execute() 函數,將回傳的 tool_result 追加到 messages[]。
⑦ 狀態更新 & 壓縮檢查:更新 token 計數,將目前 session 狀態持久化到磁碟(支援 --resume 恢復),並檢查是否需要觸發上下文壓縮策略。
scss 體驗AI程式助手 程式碼解讀複製程式碼async function agentLoop(userMessage: string) {
// 將使用者訊息加入歷史
messages.push({ role: 'user', content: userMessage })
while (true) {
// ① 上下文載入
const context = buildContext({
messages, // 完整對話歷史
memoryIndex, // MEMORY.md 指標索引(始終在記憶體中)
claudeConfig, // CLAUDE.md 靜態設定(啟動時載入一次)
tokenBudget, // 目前剩餘 token 預算
})
// ② 工具路由:按需決定注入哪些工具 schema
const tools = selectTools(context)
// ③ 權限預檢(查拒絕記錄,粗篩)
// 主要在步驟 ⑥ 精細檢查,這裡是快速過濾
// ④ 呼叫模型(唯一的 AI 步驟)
const response = await queryEngine.call({
messages: context.messages,
tools: tools,
system: context.systemPrompt,
})
// ⑤ 解析 stop_reason,決定走向
const { stop_reason, content } = response
if (stop_reason === 'end_turn') {
// 模型說「我完成了」→ 輸出給使用者,結束本輪
displayToUser(content)
break
}
if (stop_reason === 'max_tokens') {
// 上下文塞滿 → 觸發壓縮,重置預算,重試
await compressContext()
continue
}
// stop_reason === 'tool_use' → 執行工具
// ⑥ 工具執行
const toolCalls = extractToolCalls(content)
for (const call of toolCalls) {
// 精細權限檢查(可能彈出使用者確認)
if (!await checkPermission(call)) {
messages.push(toolResult(call.id, 'Permission denied'))
continue
}
// 執行並寫回結果
const result = await executeTool(call)
messages.push({ role: 'user', content: toolResult(call.id, result) })
}
// ⑦ 狀態更新
updateTokenCount()
persistSession() // 寫磁碟,支援 --resume
checkCompression() // 是否需要觸發壓縮策略
// 迴圈繼續 → 模型將看到 tool_result 後決定下一步
}
}
工具呼叫不會退出迴圈,而是把結果追加回 messages,讓模型在下一輪看到工具執行結果再決定下一步。這就是 Claude Code 能「自主完成多步任務」的根本原因。
stop_reason 只有三個值,但它們決定了迴圈的全部控制流:
end_turn:模型認為任務完成,輸出內容給使用者,break 跳出迴圈,等待下一條訊息。tool_use:模型要呼叫工具,附帶工具名和參數。執行工具、將結果寫回 messages[],continue 回到迴圈頂部。max_tokens:生成過程中上下文視窗被填滿,無法繼續。觸發壓縮策略,重置 token 預算後重試目前輪次。來看一個真實場景:你讓 Claude Code「找出專案裡所有未使用的變數並刪除」。
第 1 輪(stop_reason = tool_use):模型思考後決定先了解專案結構。呼叫 BashTool,執行 find . -name "*.ts" | head -50,回傳 38 個 TypeScript 檔案的列表。tool_result 追加到 messages[],迴圈繼續。
第 2 輪(stop_reason = tool_use):模型看到檔案列表,決定執行靜態檢查。呼叫 BashTool,執行 npx tsc --noEmit 2>&1,回傳 12 條「變數已宣告但未讀取」的 warning。由於輸出較大,QueryEngine 自動用 MicroCompact 壓縮工具輸出後存入上下文。迴圈繼續。
第 3 輪(stop_reason = tool_use):模型分析 12 條 warning,決定一次性修改多個檔案。它回傳了 5 個 tool_use 區塊(Anthropic API 支援一次回傳多個),對應 5 個檔案的 FileEditTool 呼叫。權限檢查彈出確認(ask 模式),使用者確認後,5 個檔案被依序修改。
第 4 輪(stop_reason = end_turn):模型再次執行 npx tsc --noEmit 驗證,0 個 warning。生成最終回覆:「已在 5 個檔案中刪除 12 個未使用變數,編譯檢查通過。」break 退出迴圈。
整個過程,使用者只輸入了一句話。模型自主決定了「讀結構 → 靜態分析 → 修改 → 驗證」四步,每一步都是它在看到上一步的 tool_result 後做出的獨立決策。
QueryEngine 是 Claude Code 與 Anthropic API 通訊的唯一入口和智慧 HTTP 用戶端——你給它對話歷史和工具列表,它替你處理好所有網路層的複雜性,回傳模型的回應。
sql 體驗AI程式助手 程式碼解讀複製程式碼輸入:
messages[] 完整對話歷史
tools[] 工具 schema 列表(只含 name/description/input_schema,不含 execute 函數)
system 系統提示詞
輸出:
stop_reason 'end_turn' | 'tool_use' | 'max_tokens'
content[] 文字塊 + 工具呼叫塊的混合陣列
usage { input_tokens, output_tokens, cache_read_tokens, ... }
串流輸出(Streaming):模型的 token 是一個個生成的,QueryEngine 透過 Server-Sent Events 接收串流回應,邊接收邊推送給 UI 層。使用者看到的「字元一個個出現」的效果就來自這裡。串流模式還有一個好處:如果使用者中途按 Ctrl+C,可以立即中斷,不必等到整個回應生成完畢。
快取(Prompt Caching):Anthropic API 支援對系統提示詞和長對話歷史做服務端快取(Cache Breakpoints)。QueryEngine 自動在合適的位置插入快取標記,讓重複內容(如固定的工具 schema、專案上下文)命中快取,顯著降低 API 成本和回應延遲。usage 欄位中的 cache_read_tokens 就是快取命中的 token 數。
錯誤後重試:QueryEngine 內建了完整的重試策略:
Retry-After,精確等待對應時間後重試,不做無效輪詢。Token 計費與成本追蹤:每次 API 呼叫後,QueryEngine 從 usage 欄位提取 token 消耗,累加到 session 級的成本統計。這是 Claude Code 能在右上角即時顯示「本次 session 花費 $X.XX」的資料來源。同時,token 消耗會用於更新上下文預算,觸發壓縮策略的判斷。
雙模型策略:QueryEngine 內部並非只呼叫一個模型。對於需要深度推理的主迴圈呼叫,使用 Opus;對於上下文壓縮摘要、工具輸出摘要等輔助任務,自動切換到 Haiku。Opus 更強但更貴,Haiku 更快且便宜——這個切換對主迴圈完全透明,每天節省大量 API 成本。
所有工具都繼承自 Tool.ts 中定義的抽象基類,該基類只有四個核心欄位:
typescript 體驗AI程式助手 程式碼解讀複製程式碼abstract class Tool {
// ① 工具名:模型呼叫時使用的唯一識別
abstract name: string
// 例:"bash", "read_file", "agent"
// ② 輸入 Schema:定義模型呼叫時的參數格式(JSON Schema)
abstract input_schema: JSONSchema
// 模型必須按此格式傳參,否則直接報錯,不執行
// ③ 權限等級:決定需要什麼授權才能執行
abstract permission_level: 'read' | 'write' | 'execute' | 'network'
// 主迴圈在步驟 ③ 和步驟 ⑥ 都會檢查這個欄位
// ④ 執行函數:真正做事的地方
abstract execute(input: ValidatedInput): Promise<ToolResult>
// 回傳的 ToolResult 會被追加到 messages[] 作為 tool_result
}
40+ 個工具,每一個都是在實作這四個欄位,沒有其他魔法。工具系統之所以可以無限擴充,正是因為介面足夠簡單——任何人實作這個介面,就能給 Agent 增加新能力。
BashTool:權限等級 execute,風險最高。接受 command、timeout、workdir 三個參數,在指定目錄執行任意 shell 命令。有黑名單保護(禁止 rm -rf / 等危險命令),預設 30 秒逾時強制中止。這是工具系統裡能力最強的工具,權限系統的大部分複雜度都是為了管控它而存在的。
FileReadTool:權限等級 read,風險最低。接受 path、offset、limit 三個參數,讀取指定檔案的內容。單次最多回傳 2000 行,超出自動截斷並提示,防止大檔案直接塞滿上下文視窗。它是 ask 模式下唯一無需使用者確認即可自動執行的工具類別。
AgentTool:權限等級 execute,性質特殊。接受 task、context、tools 三個參數,在內部以 sub-agent 模式啟動一個全新的 Claude Code 子程序,將任務交給它獨立完成,最終把子 Agent 的輸出作為 tool_result 回傳給父 Agent。這是多 Agent 協作架構的核心入口。
關於平行呼叫:Anthropic API 允許模型在一次回應中回傳多個 tool_use 區塊。主迴圈用 Promise.all 並行執行所有工具,然後將所有 tool_result 一起追加到 messages[]。這是 Claude Code 能平行讀取多個檔案、或同時啟動多個子 Agent 的底層機制。
Claude Code 內建 40+ 工具,加上使用者配置的 MCP Server 工具,總數可能超過 200 個。每個工具的 input_schema 平均約 300 token。如果每次 API 呼叫都注入全部工具,僅工具 schema 就會消耗 6 萬+ token,嚴重壓縮留給對話內容的空間。
defer_loading 機制解決了這個問題:
typescript 體驗AI程式助手 程式碼解讀複製程式碼interface ToolDefinition {
name: string
input_schema: JSONSchema
permission_level: PermissionLevel
defer_loading: boolean // 是否延遲載入
load_when?: (ctx: Context) => boolean // 觸發條件
execute: (input: unknown) => Promise<ToolResult>
}
function selectTools(context: ConversationContext): ToolDefinition[] {
return [...toolRegistry.values()].filter(tool => {
if (!tool.defer_loading) return true // 核心工具:始終注入
if (!tool.load_when) return false // 無條件:始終不注入
return tool.load_when(context) // 按條件判斷
})
}
核心工具(bash、read_file、glob、grep)標記 defer_loading: false,始終注入。上下文相關工具(如 web_fetch)和 MCP 外部工具標記 defer_loading: true,只有當 load_when(ctx) 回傳 true 時才注入。實務上,每輪呼叫只注入 8-12 個工具,節省了約 96% 的工具 schema token 消耗。
Claude Code 提供三種全域權限策略,透過 --permission-mode 參數或 CLAUDE.md 設定:
write/execute/network 等級的操作需要使用者確認,read 等級自動放行。日常開發推薦使用,在效率和安全之間取得平衡。read)都需要確認。極度謹慎的場景使用,但會嚴重降低效率。每個工具在定義時靜態宣告自己的 permission_level,共四個等級:
FileReadTool、GlobTool、GrepTool。不修改任何狀態,ask 模式下自動放行。FileEditTool、FileCreateTool。ask 模式下首次需要確認。BashTool、AgentTool。影響範圍最廣,需要明確授權。WebFetchTool 和 MCP 工具。防止資料意外外洩。兩個維度交叉形成權限判斷矩陣:
yaml 體驗AI程式助手 程式碼解讀複製程式碼// 權限判斷矩陣(兩個維度交叉)
const permissionMatrix = {
// auto ask manual
read: { auto: true, ask: true, manual: false },
write: { auto: true, ask: false, manual: false },
execute:{ auto: true, ask: false, manual: false },
network:{ auto: true, ask: false, manual: false },
}
// false = 需要使用者確認才能執行
當使用者拒絕某個操作後,系統需要記錄這個意圖——否則 Agent 可能在同一任務中反覆請求同樣的權限,持續打擾使用者。這就是拒絕追蹤(Denial Tracking)系統的作用。
下面是這個系統的 46 行核心實作:
typescript 體驗AI程式助手 程式碼解讀複製程式碼class DenialLog {
// session 級拒絕記錄(重啟後清空,不做持久化)
private denied = new Set<string>()
private allowed = new Set<string>() // 「本次 session 全部允許」的工具
// 檢查是否已被拒絕(最高優先順序)
isDenied(toolName: string): boolean {
return this.denied.has(toolName) && !this.allowed.has(toolName)
}
// 記錄拒絕
record(toolName: string) {
this.denied.add(toolName)
}
// 使用者選擇「本次 session 全部允許」→ 覆蓋之前的拒絕
allowForSession(toolName: string) {
this.allowed.add(toolName)
}
}
async function checkPermission(tool: ToolDefinition): Promise<PermissionResult> {
// 第一關:查拒絕記錄(最高優先順序,直接拒絕不再詢問)
if (denialLog.isDenied(tool.name)) {
return { allowed: false, reason: 'previously_denied' }
}
// 第二關:查權限矩陣
const needsConfirm = !permissionMatrix[tool.permission_level][currentMode]
if (!needsConfirm) {
return { allowed: true } // 直接放行
}
// 第三關:彈出使用者確認
const answer = await askUser({
message: `Allow ${tool.name}?`,
options: ['Allow once', 'Allow this session', 'Deny', 'Deny this session']
})
if (answer === 'Deny' || answer === 'Deny this session') {
denialLog.record(tool.name) // 寫入拒絕記錄
return { allowed: false, reason: 'user_denied' }
}
if (answer === 'Allow this session') {
denialLog.allowForSession(tool.name)
}
return { allowed: true }
}
程式碼之所以只有 46 行,是因為簡單性是刻意追求的。權限系統越複雜,出現漏洞的可能性越高。這套實作編碼了一個核心原則:當使用者失去信任時,優雅降級,不反覆打擾。 被拒絕的操作回傳 "Permission denied" 作為 tool_result,模型看到後會尋找其他方案或告知使用者,而非陷入無限重試。
Agent 執行時,messages[] 隨著每一輪工具呼叫不斷膨脹。不加管理,10-20 輪後必然觸達上下文視窗上限,Agent 崩潰或被迫截斷歷史。Claude Code 設計了五級梯度壓縮策略,從輕到重按需觸發:
Snip(零成本):直接從 messages[] 頭部刪除最舊的若干輪對話(保留系統訊息和最近 N 輪)。有損且粗糙,但零延遲、零成本,是最後的緊急兜底手段。
MicroCompact(零成本):在工具輸出寫入 messages[] 之前,檢查其長度。超過閾值(約 2000 行)則直接截斷,末尾追加 [Output truncated: X lines omitted]。純本地字串操作,無語義理解,快但精度差——適合日誌、編譯輸出等資訊密度低的場景。
ApiMicroCompact(低成本):與 MicroCompact 的差別在於「有語義」。把超大的工具輸出發給 Haiku,生成結構化摘要後存入磁碟快取(key 是輸出內容的 hash)。後續引用摘要而非原始輸出。相同命令重複執行時,可直接命中快取,無需再次 API 呼叫。
AutoCompact(中成本):當上下文剩餘 token 低於 13,000 時觸發(留出壓縮本身所需的空間)。呼叫 Haiku,生成最多 20,000 token 的結構化摘要替換舊歷史,壓縮後預算大幅恢復。內建熔斷機制:連續失敗 3 次(如摘要本身太長),停止重試,降級到 Snip。
Full Compact(高成本):使用者手動執行 /compact 或系統開啟 ContextCollapse feature flag 時觸發。徹底壓縮整個對話歷史,同時重新注入:最近存取的檔案(每檔案上限 5,000 token)、目前活躍的任務計畫、相關工具 schema。完成後工作預算重置為 50,000 token,相當於給長任務一個全新的「乾淨起點」。
scss 體驗AI程式助手 程式碼解讀複製程式碼async function checkCompression(state: AgentState): Promise<void> {
const remaining = state.totalBudget - state.usedTokens
// 工具輸出截斷:在步驟 ⑥ executeTool 的輸出階段執行
// (MicroCompact / ApiMicroCompact 在這裡,不在 checkCompression 裡)
// AutoCompact:接近上限時觸發
if (remaining < 13_000) {
const success = await autoCompact(state)
if (!success) {
// 壓縮失敗,熔斷計數
state.compactFailCount++
if (state.compactFailCount >= 3) {
// 連續失敗 3 次 → 降級到 Snip
snip(state)
state.compactFailCount = 0
}
} else {
state.compactFailCount = 0
// 壓縮成功,預算恢復
state.usedTokens = state.usedTokens * 0.3
}
return
}
// Snip:極端情況下的兜底(ratio > 0.98)
if (state.usedTokens / state.totalBudget > 0.98) {
snip(state)
}
}
優先順序由高到低:AutoCompact > Snip。MicroCompact 和 ApiMicroCompact 在工具執行階段獨立運作,不透過 checkCompression 觸發。Full Compact 是使用者主動觸發的獨立操作。
AutoCompact 不是讓模型「隨便總結一下」,而是生成固定結構的摘要,確保關鍵資訊一定被保留:
shell 體驗AI程式助手 程式碼解讀複製程式碼const AUTOCOMPACT_PROMPT = `
你是一個對話歷史壓縮助手。將以下對話壓縮為結構化摘要。
必須包含以下章節,不得省略:
## 已完成的任務
(列出本次會話中已經完成的所有操作,要具體)
## 關鍵發現
(程式碼結構、重要檔案位置、已知問題、重要約束等)
## 目前狀態
(此刻正在做什麼,進行到哪一步)
## 待完成事項
(還需要做什麼,按優先順序排列)
## 重要決策
(已經做出的技術決策和原因,避免重複討論)
壓縮後長度不得超過 20,000 token。
`
固定章節的設計有一個深層用意:每次壓縮後,模型都能從同樣結構的上下文裡找到它需要的資訊,行為保持一致。 如果摘要格式每次不同,模型在壓縮後的表現可能會出現難以預測的漂移。這也是 AutoCompact 的「優雅」之處——它不只是縮短了上下文,而是重新整理了上下文,讓 Agent 能以穩定的狀態繼續工作。
上下文壓縮解決了「歷史如何瘦身」,但還有另一個問題:專案相關的長期知識(認證邏輯、資料庫 schema、API 規範……)該如何在多次會話之間持久保存,又不占滿上下文?
答案是三層 Memory 架構:
第一層:MEMORY.md 指標索引(始終在上下文中)
這是唯一保證始終駐留在上下文視窗的檔案,但它本身非常輕量——每條索引約 150 字元,整個檔案保持在 ~2,000 token 以內。它只存「指標」,不存內容:
bash 體驗AI程式助手 程式碼解讀複製程式碼# Memory Index
- auth-system → memory/auth.md (JWT 實作, refresh token 邏輯)
- db-schema → memory/db-schema.md (users 表, orders 表結構)
- api-design → memory/api-design.md (REST 規範, 錯誤碼定義)
- deployment → memory/deploy.md (CI/CD 流程, 環境變數清單)
第二層:主題檔案(按需載入)
被 MEMORY.md 引用的具體知識檔案,儲存在 memory/ 目錄下。每個檔案聚焦一個主題,可以任意詳細。需要時,模型透過 FileReadTool 讀取對應檔案,用完後無需保留在上下文——下次需要時再讀即可。
第三層:CLAUDE.md 靜態設定
專案級的固定偏好和約定,啟動時一次性讀入,始終駐留。適合存放:編碼規範、工具鏈偏好、專案特殊約束等不會頻繁變化的設定。
永遠不把全量知識放入上下文,只放指標。
這個設計和資料庫索引的思路完全一致:資料庫不會把所有資料載入記憶體,而是維護一個精簡的 B+ Tree 索引,需要時按索引定位磁碟上的具體資料。Memory 系統做的是同樣的事——用 2,000 token 的索引管理任意大小的知識庫,按需取用,不預先占用上下文空間。
關鍵設計決策:父子 Agent 完全隔離。 子 Agent 拿不到父 Agent 的 messages[],也感知不到其他子 Agent 的存在。父子之間的介面只有兩個:輸入是 task + context 文字描述,輸出是子 Agent 的最終回覆文字。
這個強隔離帶來三個好處:① 上下文乾淨——子 Agent 的多輪工具呼叫不污染父 Agent 的上下文;② 可平行——多個子 Agent 互不依賴,可以真正同時執行;③ 可替換——父 Agent 不關心子 Agent 內部實作,只要最終結果符合預期。
go 體驗AI程式助手 程式碼解讀複製程式碼const AgentTool: ToolDefinition = {
name: 'agent',
permission_level: 'execute', // 繼承呼叫方權限
defer_loading: false, // 核心工具,始終可用
input_schema: {
type: 'object',
properties: {
task: { type: 'string', description: '子任務的完整描述,越具體越好' },
context: { type: 'string', description: '傳遞給子 Agent 的背景資訊' },
tools: { type: 'array', description: '允許子 Agent 使用的工具列表' },
},
required: ['task']
},
execute: async (input) => {
// ① 以 sub-agent 模式啟動子程序
const child = spawn('claude', [], {
env: {
...process.env,
CLAUDE_SUBAGENT_MODE: '1', // main.tsx 會據此進入 subagent 模式
CLAUDE_PARENT_TASK: input.task,
}
})
// ② 透過 stdin 傳入任務描述
// 注意:父 Agent 的 messages[] 不傳給子 Agent
// 子 Agent 只知道自己的任務,完全不知道父 Agent 的上下文
child.stdin.write(JSON.stringify({
task: input.task,
context: input.context,
tools: input.tools ?? defaultSubAgentTools,
}))
// ③ 等待子程序完成(可與其他子 Agent 並行等待)
const result = await waitForCompletion(child)
// ④ 子 Agent 的最終輸出作為 tool_result 回傳
// 父 Agent 只看到這一句話,不知道子 Agent 內部跑了多少輪
return {
type: 'tool_result',
content: result.finalOutput,
}
}
}
父 Agent 呼叫多個 AgentTool 時,主迴圈用 Promise.all 並行執行,實現真正的平行處理:
javascript 體驗AI程式助手 程式碼解讀複製程式碼// 主迴圈步驟 ⑥:並行執行多個工具(包括多個 AgentTool)
const results = await Promise.all(
toolCalls.map(call => executeToolCall(call))
)
// 所有結果一起追加到 messages[]
for (const [call, result] of zip(toolCalls, results)) {
messages.push(toolResult(call.id, result))
}
MCP(Model Context Protocol)是 Anthropic 提出的開放協定,解決一個核心問題:如何讓外部服務以標準方式向 Claude Code 暴露工具,而無需 Anthropic 為每個服務單獨撰寫內建工具。
任何服務——Asana、GitHub、自建資料庫、企業內部 API——只要實作 MCP 協定,就能被 Claude Code 當作工具使用,不需要修改 Claude Code 的任何程式碼。MCP 之於 Claude Code,類似 USB 協定之於電腦:定義了標準介面,讓外接裝置可以即插即用。
啟動時握手與工具發現:Claude Code 啟動時,對每個設定的 MCP Server 發起 initialize 請求,握手成功後立即請求 tools/list,取得該 Server 提供的工具列表和每個工具的 schema。
註冊到 Tool Registry:Claude Code 將 MCP 回傳的工具 schema 包裝成內部 ToolDefinition 格式,注入工具註冊表,並標記 defer_loading: true(MCP 工具幾乎全部延遲載入)。從這一刻起,MCP 工具和內建工具在主迴圈眼裡完全一致。
執行期呼叫:模型需要呼叫 MCP 工具時,execute() 函數內部由 mcpProxy 將呼叫轉發給對應的 MCP Server,回傳結果包裝成標準 tool_result,寫入 messages[]。整個過程對主迴圈透明。
設定方式:
yaml 體驗AI程式助手 程式碼解讀複製程式碼# CLAUDE.md
mcp_servers:
- name: asana
type: http
url: https://mcp.asana.com/sse
- name: my-db-tool
type: stdio
command: node ./mcp-server/index.js
MCP Server 支援三種傳輸方式:stdio(本地子程序)、SSE(HTTP Server-Sent Events)和 HTTP(標準 REST)。無論哪種傳輸方式,Claude Code 端的整合邏輯完全相同。
學完 Claude Code 的整個架構,最深的感受是:這不是一個「AI 專案」,而是一個「以 AI 為核心的工程專案」。
整個系統中,真正屬於 AI 的部分只占 1.6%——就是 QueryEngine 裡呼叫 Anthropic API 的那一段程式碼。其餘 98.4% 都是嚴肅的工程:精心設計的狀態機、多層次的權限系統、梯度化的資源管理策略、可組合的外掛架構。
這揭示了一個對所有 AI 應用開發者都有價值的洞見:
讓 AI Agent 能做什麼,取決於工具系統。讓 AI Agent 做得好不好,取決於上下文管理。讓 AI Agent 在真實任務中穩定執行,取決於權限控制和錯誤恢復。 模型本身的能力固然重要,但包裹在模型外面的工程基礎設施,才是決定產品體驗的關鍵。
Claude Code 的每一個設計決策都體現了這種思維:用 defer_loading 把 token 留給真正有用的內容,用 DenialLog 的 46 行程式碼保證使用者體驗不被權限彈窗破壞,用五級壓縮策略讓 Agent 在任意長的任務中都能穩定工作,用強隔離的 SubAgent 實現安全的平行協作。
建構可靠的 AI Agent,本質上是一道工程題,不是一道 AI 題。