Claude Code 智慧體是如何設計實作的?

大家好,我是雙越。[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) 作者。

我正致力於兩個專案的開發和升級,有興趣的可以私訊我,加入專案小組。

  • 【劃水 AI】 Node 全端 AIGC 知識庫,包括 AI 寫作、多人協同編輯。複雜業務,真實上線。
  • 【智語】 AI Agent 智慧體專案。一個智慧面試官,可以優化履歷、模擬面試、解答題目等。

本文介紹 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,難點不在於呼叫模型,而在於如何管理模型周圍的一切。


整體架構

架構全景圖

各模組職責速覽

  • 入口層:解析 CLI 參數,完成初始化,依條件分發到五種執行模式之一。
  • Agent 主迴圈:整個系統的驅動引擎,一個 while(true) 迴圈,負責協調所有其他模組。
  • QueryEngine:與 Anthropic API 通訊的唯一入口,封裝了所有網路細節。
  • 工具系統:外掛式架構,40+ 內建工具 + 無限擴充的 MCP 外部工具。
  • 權限控制:三種全域模式 × 四種工具權限等級的矩陣管控。
  • 上下文壓縮:五級梯度壓縮策略,防止長任務因上下文溢出而崩潰。
  • Memory 系統:三層架構,用「指標索引」代替「全量注入」,高效管理長期知識。

入口層

初始化階段

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(預設兜底)。

React/Ink 終端渲染器

Interactive 模式下,UI 層由 Ink 驅動。Ink 的核心思想是把 React 的元件樹渲染到終端機——你可以用寫 Web 元件的方式寫終端 UI。

這套渲染器採用遊戲引擎式的髒檢查最佳化:只重繪發生變化的行,而非每次刷新整個螢幕。這確保了在模型串流輸出時,螢幕不會產生閃爍或撕裂。

架構上,UI 層和業務層透過共享的 AppState 物件通訊,彼此不感知內部實作:

  • UI 層負責捕獲鍵盤輸入、渲染訊息泡泡、展示串流 token
  • 業務層(Agent 主迴圈)負責呼叫 QueryEngine、執行工具、管理狀態
  • 共享狀態包括:messages[]isLoadingcurrentToolCalltokenUsage

這種分離讓兩層可以獨立測試和替換,也是整個系統保持可維護性的基礎之一。


Agent Loop 主迴圈

七個階段

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 狀態機

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 的作用

一句話說明

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 內建了完整的重試策略:

  • 網路錯誤:指數退避重試,最多 3 次,間隔 1s → 2s → 4s。
  • 429 Rate Limit:解析回應標頭中的 Retry-After,精確等待對應時間後重試,不做無效輪詢。
  • 500/502/503 伺服器錯誤:同樣指數退避,與網路錯誤共享重試計數。
  • 逾時:單次請求超過 120 秒則逾時,觸發重試邏輯。

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,風險最高。接受 commandtimeoutworkdir 三個參數,在指定目錄執行任意 shell 命令。有黑名單保護(禁止 rm -rf / 等危險命令),預設 30 秒逾時強制中止。這是工具系統裡能力最強的工具,權限系統的大部分複雜度都是為了管控它而存在的。

FileReadTool:權限等級 read,風險最低。接受 pathoffsetlimit 三個參數,讀取指定檔案的內容。單次最多回傳 2000 行,超出自動截斷並提示,防止大檔案直接塞滿上下文視窗。它是 ask 模式下唯一無需使用者確認即可自動執行的工具類別。

AgentTool:權限等級 execute,性質特殊。接受 taskcontexttools 三個參數,在內部以 sub-agent 模式啟動一個全新的 Claude Code 子程序,將任務交給它獨立完成,最終把子 Agent 的輸出作為 tool_result 回傳給父 Agent。這是多 Agent 協作架構的核心入口。

工具呼叫流程

關於平行呼叫:Anthropic API 允許模型在一次回應中回傳多個 tool_use 區塊。主迴圈用 Promise.all 並行執行所有工具,然後將所有 tool_result 一起追加到 messages[]。這是 Claude Code 能平行讀取多個檔案、或同時啟動多個子 Agent 的底層機制。

延遲載入 defer_loading

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)              // 按條件判斷
  })
}

核心工具(bashread_fileglobgrep)標記 defer_loading: false,始終注入。上下文相關工具(如 web_fetch)和 MCP 外部工具標記 defer_loading: true,只有當 load_when(ctx) 回傳 true 時才注入。實務上,每輪呼叫只注入 8-12 個工具,節省了約 96% 的工具 schema token 消耗。


權限控制

全域權限模式

Claude Code 提供三種全域權限策略,透過 --permission-mode 參數或 CLAUDE.md 設定:

  • auto 模式:所有工具呼叫自動執行,不詢問使用者。適合 CI/CD 流水線或完全信任的自動化場景,但出錯時沒有任何攔截機制。
  • ask 模式(預設):write/execute/network 等級的操作需要使用者確認,read 等級自動放行。日常開發推薦使用,在效率和安全之間取得平衡。
  • manual 模式:所有操作(包括 read)都需要確認。極度謹慎的場景使用,但會嚴重降低效率。

工具權限等級

每個工具在定義時靜態宣告自己的 permission_level,共四個等級:

  • read:唯讀操作,FileReadToolGlobToolGrepTool。不修改任何狀態,ask 模式下自動放行。
  • write:修改磁碟檔案,FileEditToolFileCreateToolask 模式下首次需要確認。
  • execute:執行任意命令,BashToolAgentTool。影響範圍最廣,需要明確授權。
  • network:發起網路請求,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 並不隨意

AutoCompact 不是讓模型「隨便總結一下」,而是生成固定結構的摘要,確保關鍵資訊一定被保留:

shell 體驗AI程式助手 程式碼解讀複製程式碼const AUTOCOMPACT_PROMPT = `
你是一個對話歷史壓縮助手。將以下對話壓縮為結構化摘要。
必須包含以下章節,不得省略:

## 已完成的任務
(列出本次會話中已經完成的所有操作,要具體)

## 關鍵發現
(程式碼結構、重要檔案位置、已知問題、重要約束等)

## 目前狀態
(此刻正在做什麼,進行到哪一步)

## 待完成事項
(還需要做什麼,按優先順序排列)

## 重要決策
(已經做出的技術決策和原因,避免重複討論)

壓縮後長度不得超過 20,000 token。
`

固定章節的設計有一個深層用意:每次壓縮後,模型都能從同樣結構的上下文裡找到它需要的資訊,行為保持一致。 如果摘要格式每次不同,模型在壓縮後的表現可能會出現難以預測的漂移。這也是 AutoCompact 的「優雅」之處——它不只是縮短了上下文,而是重新整理了上下文,讓 Agent 能以穩定的狀態繼續工作。


三層 Memory 架構

架構設計

上下文壓縮解決了「歷史如何瘦身」,但還有另一個問題:專案相關的長期知識(認證邏輯、資料庫 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 的索引管理任意大小的知識庫,按需取用,不預先占用上下文空間。


SubAgent

SubAgent 架構設計

關鍵設計決策:父子 Agent 完全隔離。 子 Agent 拿不到父 Agent 的 messages[],也感知不到其他子 Agent 的存在。父子之間的介面只有兩個:輸入是 task + context 文字描述,輸出是子 Agent 的最終回覆文字。

這個強隔離帶來三個好處:① 上下文乾淨——子 Agent 的多輪工具呼叫不污染父 Agent 的上下文;② 可平行——多個子 Agent 互不依賴,可以真正同時執行;③ 可替換——父 Agent 不關心子 Agent 內部實作,只要最終結果符合預期。

AgentTool

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 整合

MCP 是什麼

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 題。


原文出處:https://juejin.cn/post/7638807947525816366


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝12   💬4   ❤️1
464
🥈
alicec
📝1   ❤️2
87
#4
我愛JS
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登