讀 Claude Code 源碼學到的幾件事

作者:劉賀同學
日期:2026年5月1日 上午1:08
來源:WeChat 原文

整理版優先睇

速讀 5 個重點 高亮

Claude Code 源碼揭示嘅設計哲學:agent 內核要薄、系統 prompt 切半、工具調度複用 OS 思路、記憶同上下文壓縮逐級處理。

整理版摘要

呢篇文章係作者拆解 Claude Code 源碼(約51萬行 TypeScript)後嘅技術心得。作者背景係資深開發者,目的係從生產級 agent 源碼中提煉可複用嘅設計原則,而唔係單純講解點樣用 Claude Code。整體結論係:優秀 agent 設計多數唔係一次過諗出嚟,而係喺真實流量中透過事故反推、迭代優化出嚟。文章強調將複雜度外推、系統 prompt 分兩半利用緩存、工具調度複用操作系統思路、記憶分層唔記代碼、上下文壓縮用漸進式策略、工具描述按需加載、多 agent 協作透過權限差異表達,最後總結成8條原則。

作者從 agent loop 個 while(true) 開始,指出核心骨架簡單,但複雜度全部推咗去外圍模組。然後詳細拆解 system prompt 物理切兩半:前半靜態系統內置部分共用緩存,後半動態 per-session 用 boundary marker 切開。工具調度方面,複用 OS 讀寫鎖概念,讀讀並行、讀寫互斥,並且 fail-closed 默認。記憶系統冇用 RAG,而係分三層:索引常駐、正文按需、歷史 grep,而且唔記代碼只記偏好同判斷。上下文壓縮有5級漸進策略,從輕量裁剪到全量摘要,按 token 佔用率選擇。工具描述按需加載,MCP 工具默認只列名,用 ToolSearch 攞 schema。多 agent 協作透過工具權限差異實現:協調員有4個獨佔工具,工人 agent 冇。 …

  • agent 內核要薄:核心係 while(true) 循環,複雜度外推至可替換模組;系統 prompt 物理切兩半,前半共享緩存,後半動態。
  • 工具調度複用 OS 鎖:讀讀並行、讀寫互斥,fail-closed 默認,透過 env var 調節併發上限。
  • 記憶分層,唔記代碼:索引常駐(MEMORY.md),正文按需,歷史 grep;只記偏好同判斷,唔記事實。
  • 上下文壓縮漸進式:5級策略從 SnipReactive Compact,按 token 佔用率選擇,每級應對特定失敗模式。
  • 關鍵閾值留痕:所有數字用 env var 可覆蓋,決策原因用 'BQ 日期' 註釋釘在代碼邊;看到斷路器主動檢查同類失效模組。
值得記低
連結 github.com

claude-code-best/claude-code

Claude Code 源碼倉庫,本文所有代碼引用都來自 main 分支。

結構示例

內容結構

內容結構 text
src/entrypoints/cli.tsx    pre-flight 檢查 + daemon/runner 分支 + 調起 main()  → src/main.tsx           CLI 參數解析、子命令註冊、初始化(單文件巨型)    → src/replLauncher.tsx REPL 交互層,轉發      → src/QueryEngine.ts 外殼:系統 prompt 裝配、cost 追蹤、文件歷史        → src/query.ts     ★ agent loop 真身在這裏          → tools / services / hooks / ...
整理重點

agent 循環同設計哲學

Claude Code 個 agent loop 其實好簡單,就係一個 while(true) 循環:採樣→工具→反饋,反覆直到模型冇新工具調用。呢種 ReAct 範式個骨架好薄,但複雜度全部推咗去外圍模組,例如上下文壓縮、工具調度、記憶 hooks 等。

作者強調 生產級 agent 嘅設計多數唔係設計階段諗出嚟,而係從生產流量迭代中反推。例如 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 呢個閾值,係來自 BQ 查詢發現 1279 個會話連續失敗超 50 次,浪費 25 萬 API 調用之後先補嘅。

整理重點

System prompt 切半同工具調度

System prompt 用 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 切兩半:前半係 7 個靜態 section(IntroSystemDoingTasks 等),所有用戶共用緩存;後半 per-session 動態構造,包含當前時間、git 狀態、CLAUDE.md 等。呢個設計令到 prompt caching 發揮到極致。

工具調度複用 OS 讀寫鎖:讀讀並行、讀寫互斥。Read 工具(GlobGrep)同時跑,Edit/Write/Bash 默認串行。默認最多 10 個併發,可用 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 調整。出錯時 fail-closed 降級到串行。

  1. 1 系統 prompt 拆開靜態共用 + 動態構造,用 boundary marker 拼埋
  2. 2 工具按 isConcurrencySafe() 標識分區,連續 safe 的一批併發
  3. 3 fail-closed 默認,try/catch 確保異常都視作不安全
整理重點

記憶分層同上下文壓縮

Claude Code 冇用 RAG,而係用 分層緩存 + grep 搜索。第一層係索引文件 MEMORY.md,最多 200 行 25KB,只放指針(路徑 + 一句標題)。第二層係散落嘅 topic.md 文件,按需加載最多 5 個。第三層係更早對話嘅 .jsonl 歷史,用 Bash grep -rn 翻。

上下文壓縮有 5 級漸進策略Snip(留結構刪正文)、Microcompact(卸到磁盤)、Context Collapse(摘要摺疊)、Autocompact(整塊摘要)、Reactive Compact(API 413 兜底)。每級損失信息量遞增,按 token 佔用率選擇,先用輕量級。

整理重點

工具按需加載同多 agent 協作

工具描述都係 prompt 成本。Claude Code 用 deferred loading:內置工具(Read/Edit/Bash)默認 alwaysLoad,MCP 工具默認 defer,只列名。模型想用時先調 ToolSearchTool 攞 JSONSchema,再真正調用。

  • MCP 工具數量多時輕鬆佔 10%+ 上下文,按需加載幾乎必須
  • ToolSearch 支持精確 select,模型按名直接命中
  • 默認 defer + opt-out 比默認 opt-in 更健壯,新 MCP 自動享受

多 agent 協作透過 工具權限差異 實現:協調員獨佔 4 個工具(建團隊、解散、發消息、合成輸出),工人 agent 用剩餘標準工具集。主從層級自然形成,唔需要發明新協議。

Claude Code 源碼流出咗一段時間,趁五一假期抽時間拆解咗一次,收穫好大。

唔想讀技術細節嘅:直接跳到文末嘅「8 條原則總覽」(約 600 字)。中間幾節係源碼層嘅展開,按需要揀來讀。

本文所有代碼引用都來自開源項目 claude-code-best/claude-code[1]main 分支,TypeScript 源碼約 51 萬行。拆解基於 commit a2cfaf9(2026-04-28),package version 1.10.10,CLI 自報 MACRO.VERSION = 2.1.888。源碼倉庫活躍迭代緊,當前本地源碼已經演進到更新版本;行號、函數名、feature flag 列表可能隨版本變化——本文討論嘅設計原則唔依賴具體行號。

從邊度開始讀

代碼量大,但入口路徑較短。三步定位入口:

  1. package.jsonbin 字段。claude-code-best 倉庫裏面寫嘅係 "ccb": "dist/cli-node.js"——裝咗之後跑 ccb 命令調用呢個文件。
  2. dist/ 係構建產物,返去揾源碼。看 build.ts 裏面嘅入口配置(或者 vite.config.ts / package.json 裏的 main),對應到源碼側嘅 src/entrypoints/cli.tsx。呢個文件頂部有 #!/usr/bin/env bun shebang,係獨立可執行 entry。
  3. 順着 import 一路向下鑽。cli.tsx 尾尾一行 await import('../main.jsx') 調起 main();main.tsx 裏面 import { launchRepl } from './replLauncher.js';replLauncher 將控制權轉畀 QueryEngine;QueryEngine 調 query 裏面嘅 agent loop。
src/entrypoints/cli.tsx    pre-flight 檢查 + daemon/runner 分支 + 調起 main()
  → src/main.tsx           CLI 參數解析、子命令註冊、初始化(單文件巨型)
    → src/replLauncher.tsx REPL 交互層,轉發
      → src/QueryEngine.ts 外殼:系統 prompt 裝配、cost 追蹤、文件歷史
        → src/query.ts     ★ agent loop 真身在這裏
          → tools / services / hooks / ...

本文後面討論嘅工具調度、記憶、多 agent,都係由 query.ts 順住 import 再向下展開一層之後嘅內容。本文按呢個順序展開:先睇 agent loop 本身,再依次向外。


1. agent loop 就係個 while(true)

src/query.tsqueryLoop 係每次用戶提問之後 agent 跑起嚟嘅入口。函數主體係一個 while(true) 循環:

// src/query.ts
async functionqueryLoop(paramsQueryParams, ...) {
  let state = { messages, toolUseContext, turnCount1, ... }

  while (true) {
    // 1. 拼裝本輪 prompt(merge 靜態 system + system context + user context)
    // 2. 上下文壓縮判斷(防 token 超限)
    // 3. 調模型,流式拿響應
    // 4. 解析模型返回的 tool_use
    // 5. 跑工具,拿結果
    // 6. 把結果追加到對話歷史
    // 7. 沒新工具調用就結束,否則繼續
  }
}

每一輪「採樣 → 工具 → 回饋」反覆轉,直到模型唔再發起 tool_use。呢種 reasoning + acting 交替進行嘅循環,業界叫 ReAct 範式。

循環本身只係一個調度骨架,複雜度都喺佢每一步調用嘅下游模塊度。query.ts 頂部嘅 import 顯示:每一輪迭代之前要走 services/compact/autoCompact.ts 決定使唔使壓縮;模型返回之後要走 services/tools/toolOrchestration.ts 編排工具嘅執行順序;一輪收尾要走 query/stopHooks.ts 觸發記憶抽取、prompt 推薦、autoDream;token 用量要走 query/tokenBudget.ts 判斷使唔使主動繼續跑。

後面幾節會展開呢 7 步裏面嘅幾個關鍵設計:拼裝 prompt 點樣做(system prompt 切兩半、記憶分層、工具按需加載)、上下文壓縮點樣觸發、工具調度點樣併發。其餘幾步係常規模型調用 + 歷史追加,唔會專門展開。最後仲會講兩件超出 while 循環本身嘅事——一個係貫穿循環嘅工程方法(關鍵閾值都係事故反推嘅),一個係另一種 agent 運行模式(多 agent 協作)。

關鍵係將 agent 內核做薄、複雜度往下游推——好處係每段獨立可換(Claude Code 裏面多種壓縮策略並存就係呢種設計嘅產物),代價係追蹤一個動作要跨 query、compact、tool orchestration、hooks 等多個模塊。呢種 trade-off 適合要長期迭代嘅項目;如果係寫完唔改嘅 demo,langgraph 嗰種顯式 state machine 反而更清晰。


2. system prompt 切兩半,系統內置部分共享緩存

Anthropic API 支持 prompt caching——你喺請求裏面標記 prompt 邊一段穩定唔變,服務端就會將呢段嘅處理結果緩存起嚟,後續相同內容嘅請求直接複用,慳錢慳時間。Claude Code 將呢個機制用到咗極致。

agent loop 每輪第一步係「拼裝本輪 prompt」。本節展開其中一項設計:Claude Code 用一個特殊 marker 將 system prompt 切成靜態、動態兩半。src/constants/prompts.tsgetSystemPrompt() 尾尾嘅代碼結構展示咗呢件事:

return [
  // --- 靜態內容(可緩存)---
  getSimpleIntroSection(...),
  getSimpleSystemSection(),
  getSimpleDoingTasksSection(),
  getActionsSection(),
  getUsingYourToolsSection(...),
  getSimpleToneAndStyleSection(),
  getOutputEfficiencySection(),
  // === BOUNDARY MARKER ===
  SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
  // --- 動態內容(每次請求重建)---
  ...resolvedDynamicSections,
]

7 個靜態 section 函數顯式列喺 boundary 之上:Intro / System / DoingTasks / Actions / UsingYourTools / ToneAndStyle / OutputEfficiency。呢 7 段係系統內置嘅 prompt 內容,所有用戶共享同一份緩存。每一個函數返回乜嘢文字,打開 prompts.ts 同名函數就可以逐字睇到。boundary 之後嘅 resolvedDynamicSections 由 registry 裝配,每次請求按當前會話狀態重新構造(當前時間、git 狀態、CLAUDE.md、MCP 工具等)。

發請求嗰陣,src/utils/api.ts 揾到 marker 嘅位置,將 prompt 按位置切成兩段,分別打唔同嘅 cacheScope 標籤:

const boundaryIndex = systemPrompt.indexOf(SYSTEM_PROMPT_DYNAMIC_BOUNDARY)
// boundary 之前 → cacheScope: 'global'  系統內置部分,所有用戶共享緩存
// boundary 之後 → cacheScope: null       per-session,不緩存

成個流向:

system prompt 緩存機制

prompt cache 嘅正確用法係顯式切分——將 system prompt 拆成全局 const + 動態構造兩段,用一個 boundary 字符串拼埋一齊,唔好用模板字符串一次過。代價係決策被推畀開發者:一個用戶級變量錯放咗喺 boundary 之前,全部用戶嘅命中率都會冧。prompts.ts 頂部嗰條 WARNING 註釋(唔好亂咁挪 marker)就係為咗防止呢種事故。


3. 工具調度複用 OS 思路

模型喺一次回應裏面返回 4 個 tool_use(3 個讀 + 1 個改),呢啲工具係排隊執行定係併發執行?

src/services/tools/toolOrchestration.ts 實現嘅係「讀讀並行、讀寫互斥」,同操作系統中讀寫鎖嘅處理方式一致:

// 默認最多 10 個併發,可用 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 調整

// partitionToolCalls 的核心判斷
const isConcurrencySafe = parsedInput?.success
  ? (() => {
      try { return tool.isConcurrencySafe(parsedInput.data) }
      catch { return false }   // 拋異常按不安全處理 ← fail-closed
    })()
  : false                      // 參數解析失敗也按不安全處理

// 然後:連續 isConcurrencySafe 的合併到同一批,遇到 not safe 切批

邏輯:默認最多 10 個工具併發,連續若干個 isConcurrencySafe 嘅合併到同一批一齊跑,遇到一個唔 safe 嘅就切批,等前面嗰批跑完再開下一批。批與批串行,批內併發。Read 係 safe 嘅,Edit / Write / Bash 唔 safe,所以 3 讀 + 1 寫會被切成兩批:3 個併發 Read 跑完 → 1 個 Edit。

切批過程可視化:

工具調度切批邏輯

兩條帶走嘅嘢:第一係唔好自己發明併發模型,「讀讀並行、讀寫互斥」呢套現有方案覆蓋 90% 場景;第二係 fail-closed(出錯降級到最保守路徑)應該係 agent 工具調度嘅默認——睇 try / catch 嗰段,理論上 isConcurrencySafe() 唔會拋,但 Claude Code 仍包咗一層。

fail-closed 留下一筆技術債:某個工具誤拋會令原本可以併發嘅批次靜靜雞降級成串行,但冇可觀測性。自己實現嗰陣至少喺 dev 模式輸出併發降級原因。


4. 記憶要分層,而且唔記代碼

令 AI 喺長對話中保持上下文連貫,常見做法係 RAG——將項目數據做 embedding 存到向量庫,每次問答先檢索再回答。

Claude Code 唔用 RAG。佢嘅方案係分層緩存 + grep 搜索。

第一層係始終喺上下文裏面嘅索引文件,叫 MEMORY.md,每次對話都被完整加載。src/memdir/memdir.ts 用幾行常量定義咗佢:

export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
// ~125 chars/line at 200 lines. At p97 today; catches long-line indexes that
// slip past the line cap (p100 observed: 197KB under 200 lines).
export const MAX_ENTRYPOINT_BYTES = 25_000

MEMORY.md 最多 200 行、25KB,裏面只放指針(路徑 + 一句話標題),唔放正文。一份典型嘅 MEMORY.md 係咁樣:

# MEMORY.md

## 項目設置
- [項目架構](architecture.md) — 關鍵模塊依賴關係
- [部署流程](deploy.md) — staging 和 prod 的發佈步驟

## 已知坑
- [redis 集羣](gotchas/redis.md) — 別用 KEYS *,會卡主線程
- [webhook 重試](gotchas/webhook.md) — idempotency 必須走 deduper

每行就係一個指針,正文都喺外部 .md 文件度。正文係第二層——散落嘅 <topic>.md 文件,按需加載:新對話開始時由一個細模型揀最多 5 個同當前問題相關嘅加載入嚟。第三層係更早嘅對話,被存成 .jsonl 歷史文件,手動檢索:agent 需要嗰陣透過 Bash 工具用 grep -rn 翻歷史。

findRelevantMemories.ts 嘅 system prompt 裏面仲有一條揀選規則:

const SELECT_MEMORIES_SYSTEM_PROMPT = `...
- If a list of recently-used tools is provided, do not select memories that are
  usage reference or API documentation for those tools (Claude Code is already
  exercising them). DO still select memories containing warnings, gotchas, or
  known issues about those tools — active use is exactly when those matter.`

正在用某個工具嗰陣,唔加載佢嘅使用文檔,但加載佢嘅已知問題

三條帶走嘅嘢:索引—正文分離(IDE 代碼導航、OS page table 本質都係呢一招)、正在用嘅工具加載已知問題而唔係文檔(文檔可以邊用邊查,但已知問題唔會自動暴露)、記憶只記偏好同判斷唔記代碼。最後一條反過來用喺 CLAUDE.md 呢種長期指令文件:唔好寫「函數 X 喺第 30 行」,要寫「函數 X 改咗要跑 e2e,因為佢觸發 webhook」——前者係事實會過期,後者係判斷長期適用。


5. 上下文壓縮分 5 級觸發

agent loop 每輪第 2 步係「上下文壓縮判斷」。呢度嘅「上下文」指 messages 陣列(用戶消息 + assistant 消息 + 舊嘅 tool_use 結果),唔包含工具定義——工具描述嘅成本由 ToolSearch 單獨解決(下一節展開)。Claude Code 嘅壓縮唔係一次性算法,而係 5 級由輕到重嘅策略,按當前 token 佔用率決定用邊一級:

級別 強度 實現位置 做什麼
1. Snip services/compact/snipCompact.ts 舊 tool_use 結果只保留結構,唔保留正文
2. Microcompact services/compact/microCompact.ts 將體積大嘅工具結果卸載到磁盤緩存,引用替換正文
3. Context Collapse services/contextCollapse/index.ts 對中間對話做摘要摺疊
4. Autocompact services/compact/autoCompact.ts 超過閾值時成塊上下文做摘要壓縮
5. Reactive Compact 兜底 services/compact/reactiveCompact.ts API 返回 413 prompt too long 時緊急觸發

query.ts 頂部三段 conditional import 顯示呢啲級別都按 feature flag 加載:

const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./services/compact/reactiveCompact.js') : null
const contextCollapse = feature('CONTEXT_COLLAPSE')
  ? require('./services/contextCollapse/index.js') : null
const snipModule = feature('HISTORY_SNIP')
  ? require('./services/compact/snipCompact.js') : null

調用順序遵循「先輕後重」:每輪先睇下能否用 Snip / Microcompact 處理,再升級到 Context Collapse / Autocompact,Reactive Compact 係最後兜底。除咗 Snip 係純結構裁剪之外,每一級都係獨立嘅模型調用。

長上下文管理用漸進式策略——每一級應對一種特定失敗模式(見上表),損失信息量遞增,每輪只用一級。自己實現長會話 agent 嘅時候至少要有最重兩級——Autocompact 同 Reactive Compact;工具調用頻繁嘅話就再加 Snip 同 Microcompact。


6. 工具按需加載(ToolSearch)

先同「工具調度」劃清邊界:前面「工具調度」講嘅係模型已經決定要調邊啲工具之後嘅執行編排;呢度講嘅係更早一步——邊啲工具嘅 schema 出現喺 prompt 裏面畀模型睇到。兩件事作用喺 agent loop 嘅唔同時刻:呢度喺裝 prompt 嗰陣(請求前),工具調度喺模型回覆 tool_use 之後(回應後)。

agent 啟動嗰陣 prompt 裏面要列出可用工具嘅列表同 JSONSchema。工具唔多嘅時候全部列曬出嚟冇問題;但工具數量多嘅時候,prompt 會被工具描述塞滿。最常見嘅觸發場景係 MCP(Model Context Protocol,Anthropic 推嘅工具協議,畀 agent 接第三方工具源)——用戶接咗 5 個 MCP 伺服器、每個帶幾十個工具嗰陣,幾千 token 全部用咗喺工具描述上。Claude Code 嘅方案係 deferred loading:默認只列工具名,唔展開 schema。

packages/builtin-tools/src/tools/ToolSearchTool/prompt.ts 嘅判斷邏輯:

export function isDeferredTool(toolTool): boolean {
  if (tool.alwaysLoad === truereturn false                  // 顯式 opt-out
  if (tool.isMcp === truereturn true                        // MCP 工具默認 deferred
  if (tool.name === TOOL_SEARCH_TOOL_NAMEreturn false       // ToolSearch 自己不能 deferred
  // ...
}

內置工具(Read / Edit / Bash / Glob / Grep 等)默認 alwaysLoad: true,全部展開入 prompt。MCP 工具默認 deferred,prompt 裏面只睇到一行名。模型想調用一個 deferred 工具嗰陣,先調 ToolSearchTool,按 query 拎返完整 JSONSchema,之後先可以調用。

舉個具體例子(你接咗 Slack MCP,模型要發一條消息):

1. prompt 裏只有一行: slack_send (沒 schema)
2. 模型先發: ToolSearchTool({query: "select:slack_send"})
3. 拿回 slack_send 的完整 JSONSchema (channel + text 兩個參數)
4. 模型用 schema 發起真正調用: slack_send({channel: "#general", text: "..."})

三條帶走嘅嘢:工具描述都係 prompt 成本(多個 MCP 同時掛輕鬆佔 10%+ 上下文,按需加載幾乎係必須嘅);lazy load 嘅 query 接口要支持精確 select(模型睇到工具名就有 hint,可以按名直接命中,比純模糊搜索快一輪);默認 + opt-out 比默認 + opt-in 健壯(新接入嘅 MCP 自動享有 lazy load,alwaysLoad: true 係 escape hatch,只有少數關鍵工具需要寫)。


7. 關鍵限制都係事故反推嘅

讀源碼嗰陣留意帶具體日期同數字嘅註釋src/services/compact/autoCompact.ts 有呢一段:

// Stop trying autocompact after this many consecutive failures.
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272)
// in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

BQ 係 BigQuery 查詢嘅簡寫。呢個 3 唔係憑直覺揀定,而係源自 2026-03-10 嘅一次查詢:當時全網 1279 個會話連續 autocompact 失敗超過 50 次,其中一個會話連續失敗 3272 次仍然喺度重試,每日因此消耗 25 萬次 API 調用。

同樣嘅事故修補痕跡喺前面 MEMORY.md 嗰段限制度都有:註釋 p100 observed: 197KB under 200 lines 說明一個用戶寫咗 200 行索引但每行好長,總計 197KB,逼近上下文上限,所以字節限制係後來加上嘅,上線之初只有 200 行限制。

模式一致:上線時未設限制 → 用戶觸發邊界 → telemetry 暴露問題 → 代碼層補一道約束。

三條帶走嘅嘢:所有閾值留 env var 入口(補限制唔使發版);用 BQ <日期>: 呢種註釋將決策痕跡釘喺代碼身邊(比 design doc 活得耐,三年後睇到都知呢個 3 係有數據支撐嘅,唔應該亂改);見到一個斷路器主動檢查失效模式同構嘅姐妹模塊——MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 補完之後,5 級壓縮裏面另外四級源碼度睇唔到等價嘅連續失敗上限,可能只係未踩過同類事故,但失效模式(模型調用、timeout / rate limit / 返回不可解析)係一樣嘅。


8. 多 agent 協作 = 工具權限嘅主從差異

多 agent 協作通常被認為需要獨立嘅「協議狀態機」。Claude Code 嘅實現路徑唔同。

src/coordinator/workerAgent.ts 裏面核心機制只有十幾行:

const INTERNAL_ORCHESTRATION_TOOLS = new Set([
  TEAM_CREATE_TOOL_NAME,
  TEAM_DELETE_TOOL_NAME,
  SEND_MESSAGE_TOOL_NAME,
  SYNTHETIC_OUTPUT_TOOL_NAME,
])

function getWorkerTools(): string[] {
  return Array.from(ASYNC_AGENT_ALLOWED_TOOLS).filter(
    name => !INTERNAL_ORCHESTRATION_TOOLS.has(name),
  )
}

Claude Code 開啟 COORDINATOR_MODE 之後,主 agent 變成「協調員」,只能調一個叫 worker 嘅子 agent;worker 拎到一份「全標準工具集減去 4 個」嘅工具白名單。協調員用得、worker 用唔到嘅,只有呢 4 個:建立團隊、解散團隊、發送消息、合成輸出。worker 拎唔到呢 4 個,意味住佢唔可以再開新嘅子 agent、唔可以跨 agent 通信、唔可以直接出最終答案——只能完成具體任務、匯報返畀協調員。

用現有 primitive 嘅差分關係表達層級,比發明新協議成本低得多——主-從被實現成「主 agent 工具集 ⊃ 子 agent 工具集」,權限約束自然形成層級,複用 agent 已有嘅「用工具」心智模型。適用邊界:Claude Code 嘅多 agent 係單根樹(無 sibling 通信、無跨層 broadcast),90% 場景夠用;淨低 10%(多 worker 協商任務分配)只能令協調員做中間路由,token 開銷同延遲顯著增加。落地路徑:先確定工具嘅差分邊界,再考慮是否需要協議——遇到嗰 10% 嗰陣先設計協議,到時先至知道協議要係點樣。


9. 8 條原則總覽

呢 8 件事擺埋一齊睇,Claude Code 嘅設計重點唔係「令模型更聰明」,而係將模型放進一個有邊界嘅執行系統裏面:入口可控、工具可控、記憶可控、成本可控、失敗可控。拆成可以借用嘅設計原則,就係下面幾條:

  • agent 內核要薄。核心係 while(true) 嘅「模型採樣 → 工具執行 → 結果回灌」循環;壓縮、工具調度、權限、記憶、生命週期 hooks 等複雜度拆到外圍模塊,模塊之間保持可替換。
  • system prompt 物理切兩半。前半係系統內置部分(共享緩存),後半 per-session 動態,用一個明確嘅 boundary marker 切開。唔好用模板字符串一次過拼出嚟。
  • 工具調度複用 OS 思路。讀讀並行、讀寫互斥,fail-closed 默認 + env var 調上限。唔使自己設計 DAG。
  • 記憶要分層。索引常駐、正文按需、歷史 grep。唔記代碼,只記偏好同判斷。
  • 上下文壓縮用漸進式策略。由輕量裁剪到全量摘要按 token 佔用率逐級觸發,唔好一開頭就做全量摘要。
  • 工具描述按需加載。MCP / 外部工具默認只列名,模型用 ToolSearch 按需拎 schema。避免幾千 token 嘅工具描述塞滿上下文。
  • 關鍵閾值預留覆蓋入口、決策痕跡釘喺代碼身邊。數字用 env var 可覆蓋,決策原因用 BQ <日期>: 註釋。見到一個斷路器,主動檢查失效模式同構嘅姐妹模塊。
  • 多 agent 協作用工具權限差異表達。先確定「協調員獨佔邊幾個 primitive」,主從層級自然形成,唔使自己設計協議。

生產級 agent 嘅優秀設計,絕大多數唔係設計階段諗出嚟嘅,而係喺生產流量中迭代出嚟嘅MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 冇論文會教,佢來自 1279 個會話出錯之後嘅一次 BQ 查詢。Claude Code 真正稀缺嘅唔係 51 萬行代碼,而係每一處帶日期註釋背後大量真實會話嘅回饋循環——呢啲回饋已經經過實際驗證,結論可以直接借用。

引用連結

[1]claude-code-best/claude-code: https://github.com/claude-code-best/claude-code

Claude Code 源碼泄露有一段時間了,正好五一假期抽時間拆解了一遍,收穫很大。

不想讀技術細節的:直接跳到文末的「8 條原則總覽」(約 600 字)。中間幾節是源碼層的展開,按需要選讀。

本文所有代碼引用都來自開源項目 claude-code-best/claude-code[1]main 分支,TypeScript 源碼約 51 萬行。拆解基於 commit a2cfaf9(2026-04-28),package version 1.10.10,CLI 自報 MACRO.VERSION = 2.1.888。源碼倉庫活躍迭代中,當前本地源碼已經演進到更新版本;行號、函數名、feature flag 列表可能隨版本變化——本文討論的設計原則不依賴具體行號。

從哪裏開始讀

代碼量大,但入口路徑較短。三步定位入口:

  1. package.jsonbin 字段。claude-code-best 倉庫裏寫的是 "ccb": "dist/cli-node.js"——裝上後跑 ccb 命令調用這個文件。
  2. dist/ 是構建產物,回找源碼。看 build.ts 裏的入口配置(或 vite.config.ts / package.json 裏的 main),對應到源碼側的 src/entrypoints/cli.tsx。這個文件頂部有 #!/usr/bin/env bun shebang,是獨立可執行 entry。
  3. 順着 import 一路下鑽。cli.tsx 末尾一行 await import('../main.jsx') 調起 main();main.tsx 裏 import { launchRepl } from './replLauncher.js';replLauncher 把控制權轉給 QueryEngine;QueryEngine 調 query 裏的 agent loop。
src/entrypoints/cli.tsx    pre-flight 檢查 + daemon/runner 分支 + 調起 main()
  → src/main.tsx           CLI 參數解析、子命令註冊、初始化(單文件巨型)
    → src/replLauncher.tsx REPL 交互層,轉發
      → src/QueryEngine.ts 外殼:系統 prompt 裝配、cost 追蹤、文件歷史
        → src/query.ts     ★ agent loop 真身在這裏
          → tools / services / hooks / ...

本文後面討論的工具調度、記憶、多 agent,都是從 query.ts 順着 import 再向下展開一層之後的內容。本文按這個順序展開:先看 agent loop 本身,再依次往外。


1. agent loop 就是個 while(true)

src/query.tsqueryLoop 是每次用戶提問後 agent 跑起來的入口。函數主體是一個 while(true) 循環:

// src/query.ts
async functionqueryLoop(paramsQueryParams, ...) {
  let state = { messages, toolUseContext, turnCount1, ... }

  while (true) {
    // 1. 拼裝本輪 prompt(merge 靜態 system + system context + user context)
    // 2. 上下文壓縮判斷(防 token 超限)
    // 3. 調模型,流式拿響應
    // 4. 解析模型返回的 tool_use
    // 5. 跑工具,拿結果
    // 6. 把結果追加到對話歷史
    // 7. 沒新工具調用就結束,否則繼續
  }
}

每一輪"採樣 → 工具 → 反饋"反覆轉,直到模型不再發起 tool_use。這種 reasoning + acting 交替進行的循環,業內叫 ReAct 範式。

循環本身只是一個調度骨架,複雜度都在它每一步調用的下游模塊裏。query.ts 頂部的 import 顯示:每一輪迭代之前要走 services/compact/autoCompact.ts 決定要不要壓縮;模型返回之後要走 services/tools/toolOrchestration.ts 編排工具的執行順序;一輪收尾要走 query/stopHooks.ts 觸發記憶抽取、prompt 推薦、autoDream;token 用量要走 query/tokenBudget.ts 判斷要不要主動續跑。

後面幾節會展開這 7 步裏的幾個關鍵設計:拼裝 prompt 怎麼做(system prompt 切兩半、記憶分層、工具按需加載)、上下文壓縮怎麼觸發、工具調度怎麼併發。其餘幾步是常規模型調用 + 歷史追加,不專門展開。最後還會聊兩件超出 while 循環本身的事——一個是貫穿循環的工程方法(關鍵閾值都是事故反推的),一個是另一種 agent 運行模式(多 agent 協作)。

關鍵是把 agent 內核做薄、複雜度往下游推——好處是每段獨立可換(Claude Code 裏多種壓縮策略並存就是這種設計的產物),代價是追蹤一個動作得跨 query、compact、tool orchestration、hooks 等多個模塊。這種 trade-off 適合要長期迭代的項目;如果是寫完不改的 demo,langgraph 那種顯式 state machine 反而更清晰。


2. system prompt 切兩半,系統內置部分共享緩存

Anthropic API 支持 prompt caching——你在請求裏標記 prompt 哪一段穩定不變,服務端就把這段的處理結果緩存起來,後續相同內容的請求直接複用,省錢省時。Claude Code 把這個機制用到了極致。

agent loop 每輪第一步是"拼裝本輪 prompt"。本節展開其中一項設計:Claude Code 用一個特殊 marker 把 system prompt 切成靜態、動態兩半。src/constants/prompts.tsgetSystemPrompt() 末尾的代碼結構展示了這件事:

return [
  // --- 靜態內容(可緩存)---
  getSimpleIntroSection(...),
  getSimpleSystemSection(),
  getSimpleDoingTasksSection(),
  getActionsSection(),
  getUsingYourToolsSection(...),
  getSimpleToneAndStyleSection(),
  getOutputEfficiencySection(),
  // === BOUNDARY MARKER ===
  SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
  // --- 動態內容(每次請求重建)---
  ...resolvedDynamicSections,
]

7 個靜態 section 函數顯式列在 boundary 之上:Intro / System / DoingTasks / Actions / UsingYourTools / ToneAndStyle / OutputEfficiency。這 7 段是系統內置的 prompt 內容,所有用戶共享同一份緩存。每一個函數返回什麼文字,打開 prompts.ts 同名函數即可逐字看到。boundary 之後的 resolvedDynamicSections 由 registry 裝配,每次請求按當前會話狀態重新構造(當前時間、git 狀態、CLAUDE.md、MCP 工具等)。

發請求時,src/utils/api.ts 找到 marker 的位置,把 prompt 按位置切成兩段,分別打不同的 cacheScope 標籤:

const boundaryIndex = systemPrompt.indexOf(SYSTEM_PROMPT_DYNAMIC_BOUNDARY)
// boundary 之前 → cacheScope: 'global'  系統內置部分,所有用戶共享緩存
// boundary 之後 → cacheScope: null       per-session,不緩存

整個流向:

system prompt 緩存機制

prompt cache 的正確用法是顯式切分——把 system prompt 拆成全局 const + 動態構造兩段,用一個 boundary 字符串拼起來,別用模板字符串一把梭。代價是決策被推給開發者:一個用戶級變量錯放到 boundary 之前,全用戶的命中率都會崩。prompts.ts 頂部那條 WARNING 註釋(不要亂挪 marker)就是為了防止這種事故。


3. 工具調度複用 OS 思路

模型在一次響應裏返回 4 個 tool_use(3 個讀 + 1 個改),這些工具是排隊執行還是併發執行?

src/services/tools/toolOrchestration.ts 實現的是"讀讀並行、讀寫互斥",與操作系統中讀寫鎖的處理方式一致:

// 默認最多 10 個併發,可用 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 調整

// partitionToolCalls 的核心判斷
const isConcurrencySafe = parsedInput?.success
  ? (() => {
      try { return tool.isConcurrencySafe(parsedInput.data) }
      catch { return false }   // 拋異常按不安全處理 ← fail-closed
    })()
  : false                      // 參數解析失敗也按不安全處理

// 然後:連續 isConcurrencySafe 的合併到同一批,遇到 not safe 切批

邏輯:默認最多 10 個工具併發,連續若干個 isConcurrencySafe 的合併到同一批一起跑,遇到一個不 safe 的就切批,讓前面那批跑完再開下一批。批與批串行,批內併發。Read 是 safe 的,Edit / Write / Bash 不 safe,所以 3 讀 + 1 寫會被切成兩批:3 個併發 Read 跑完 → 1 個 Edit。

切批過程可視化:

工具調度切批邏輯

兩條帶走的事:一是別自己發明併發模型,"讀讀並行、讀寫互斥"這套既有方案覆蓋 90% 場景;二是 fail-closed(出錯降級到最保守路徑)應當是 agent 工具調度的默認——看 try / catch 那段,理論上 isConcurrencySafe() 不會拋,但 Claude Code 仍包了一層。

fail-closed 留下一筆技術債:某個工具誤拋會讓原本能併發的批次悄悄降級成串行,但沒有可觀測性。自己實現時至少在 dev 模式輸出併發降級原因。


4. 記憶要分層,且不記代碼

讓 AI 在長對話中保持上下文連貫,常見做法是 RAG——把項目數據做 embedding 存到向量庫,每次問答先檢索再回答。

Claude Code 不使用 RAG。它的方案是分層緩存 + grep 搜索。

第一層是始終在上下文裏的索引文件,叫 MEMORY.md,每次對話都被完整加載。src/memdir/memdir.ts 用幾行常量定義了它:

export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
// ~125 chars/line at 200 lines. At p97 today; catches long-line indexes that
// slip past the line cap (p100 observed: 197KB under 200 lines).
export const MAX_ENTRYPOINT_BYTES = 25_000

MEMORY.md 最多 200 行、25KB,裏面只放指針(路徑 + 一句話標題),不放正文。一份典型的 MEMORY.md 長這樣:

# MEMORY.md

## 項目設置
- [項目架構](architecture.md) — 關鍵模塊依賴關係
- [部署流程](deploy.md) — staging 和 prod 的發佈步驟

## 已知坑
- [redis 集羣](gotchas/redis.md) — 別用 KEYS *,會卡主線程
- [webhook 重試](gotchas/webhook.md) — idempotency 必須走 deduper

每行就是一個指針,正文都在外部 .md 文件裏。正文是第二層——散落的 <topic>.md 文件,按需加載:新對話開始時由一個小模型挑最多 5 個跟當前問題相關的加載進來。第三層是更早的對話,被存成 .jsonl 歷史文件,手動檢索:agent 需要時通過 Bash 工具用 grep -rn 翻歷史。

findRelevantMemories.ts 的 system prompt 裏還有一條挑選規則:

const SELECT_MEMORIES_SYSTEM_PROMPT = `...
- If a list of recently-used tools is provided, do not select memories that are
  usage reference or API documentation for those tools (Claude Code is already
  exercising them). DO still select memories containing warnings, gotchas, or
  known issues about those tools — active use is exactly when those matter.`

正在使用某個工具時,不加載它的使用文檔,但加載它的已知問題

三條帶走的事:索引—正文分離(IDE 代碼導航、OS page table 本質都是這一招)、正在使用的工具加載已知問題而非文檔(文檔可以邊用邊查,但已知問題不會自動暴露)、記憶只記偏好和判斷不記代碼。最後一條反過來用在 CLAUDE.md 這種長期指令文件:不要寫"函數 X 在第 30 行",要寫"函數 X 改了要跑 e2e,因為它觸發 webhook"——前者是事實會過期,後者是判斷長期適用。


5. 上下文壓縮分 5 級觸發

agent loop 每輪第 2 步是"上下文壓縮判斷"。這裏的"上下文"指 messages 數組(用戶消息 + assistant 消息 + 舊的 tool_use 結果),不包含工具定義——工具描述的成本由 ToolSearch 單獨解決(下一節展開)。Claude Code 的壓縮不是一次性算法,而是 5 級從輕到重的策略,按當前 token 佔用率決定使用哪一級:

級別 強度 實現位置 做什麼
1. Snip services/compact/snipCompact.ts 舊 tool_use 結果只保留結構,不保留正文
2. Microcompact services/compact/microCompact.ts 把體積大的工具結果卸載到磁盤緩存,引用替換正文
3. Context Collapse services/contextCollapse/index.ts 對中間對話做摘要摺疊
4. Autocompact services/compact/autoCompact.ts 超閾值時整塊上下文做摘要壓縮
5. Reactive Compact 兜底 services/compact/reactiveCompact.ts API 返回 413 prompt too long 時緊急觸發

query.ts 頂部三段 conditional import 顯示這些級別都按 feature flag 加載:

const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./services/compact/reactiveCompact.js') : null
const contextCollapse = feature('CONTEXT_COLLAPSE')
  ? require('./services/contextCollapse/index.js') : null
const snipModule = feature('HISTORY_SNIP')
  ? require('./services/compact/snipCompact.js') : null

調用順序遵循"先輕後重":每輪先看能否用 Snip / Microcompact 處理,再升級到 Context Collapse / Autocompact,Reactive Compact 是最後兜底。除 Snip 是純結構裁剪外,每一級都是獨立的模型調用。

長上下文管理用漸進式策略——每一級應對一種特定失敗模式(見上表),損失信息量遞增,每輪只用一級。自己實現長會話 agent 時至少要有最重兩級——Autocompact 和 Reactive Compact;工具調用頻繁的話再補 Snip 和 Microcompact。


6. 工具按需加載(ToolSearch)

先和「工具調度」劃清邊界:前面"工具調度"講的是模型已經決定要調哪些工具之後的執行編排;這裏講的是更早一步——哪些工具的 schema 出現在 prompt 裏讓模型看到。兩件事作用在 agent loop 的不同時刻:這裏在裝 prompt 時(請求前),工具調度在模型回覆 tool_use 之後(響應後)。

agent 啓動時 prompt 裏要列出可用工具的列表和 JSONSchema。工具不多時全部列出來沒問題;但工具數量大時,prompt 會被工具描述塞滿。最常見的觸發場景是 MCP(Model Context Protocol,Anthropic 推的工具協議,讓 agent 接第三方工具源)——用戶接了 5 個 MCP 服務器、每個帶幾十個工具時,幾千 token 全花在工具描述上。Claude Code 的方案是 deferred loading:默認只列工具名字,不展開 schema。

packages/builtin-tools/src/tools/ToolSearchTool/prompt.ts 的判斷邏輯:

export function isDeferredTool(toolTool): boolean {
  if (tool.alwaysLoad === truereturn false                  // 顯式 opt-out
  if (tool.isMcp === truereturn true                        // MCP 工具默認 deferred
  if (tool.name === TOOL_SEARCH_TOOL_NAMEreturn false       // ToolSearch 自己不能 deferred
  // ...
}

內置工具(Read / Edit / Bash / Glob / Grep 等)默認 alwaysLoad: true,全部展開進 prompt。MCP 工具默認 deferred,prompt 裏只看到一行名字。模型想調用一個 deferred 工具時,先調 ToolSearchTool,按 query 拿回完整 JSONSchema,之後才能調用。

舉個具體例子(你接了 Slack MCP,模型要發一條消息):

1. prompt 裏只有一行: slack_send (沒 schema)
2. 模型先發: ToolSearchTool({query: "select:slack_send"})
3. 拿回 slack_send 的完整 JSONSchema (channel + text 兩個參數)
4. 模型用 schema 發起真正調用: slack_send({channel: "#general", text: "..."})

三條帶走的事:工具描述也是 prompt 成本(多個 MCP 同時掛輕鬆佔 10%+ 上下文,按需加載幾乎是必須的);lazy load 的 query 接口要支持精確 select(模型看到工具名就有 hint,能按名字直接命中,比純模糊搜索快一輪);默認 + opt-out 比默認 + opt-in 健壯(新接入的 MCP 自動享受 lazy load,alwaysLoad: true 是 escape hatch,只少數關鍵工具需要寫)。


7. 關鍵限制都是事故反推的

讀源碼時關注帶具體日期和數字的註釋src/services/compact/autoCompact.ts 有這麼一段:

// Stop trying autocompact after this many consecutive failures.
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272)
// in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

BQ 是 BigQuery 查詢的簡寫。這個 3 不是憑直覺選定,而是源自 2026-03-10 的一次查詢:當時全網 1279 個會話連續 autocompact 失敗超過 50 次,其中一個會話連續失敗 3272 次仍在重試,每天因此消耗 25 萬次 API 調用。

同樣的事故修補痕跡在前面 MEMORY.md 那段限制裏也有:註釋 p100 observed: 197KB under 200 lines 說明一個用戶寫了 200 行索引但每行很長,總計 197KB,逼近上下文上限,所以字節限制是後來加的,上線之初只有 200 行限制。

模式一致:上線時未設限制 → 用戶觸發邊界 → telemetry 暴露問題 → 代碼層補一道約束。

三條帶走的事:所有閾值留 env var 入口(補限制不用發版);用 BQ <日期>: 這種註釋把決策痕跡釘在代碼身邊(比 design doc 活得久,三年後看到也知道這個 3 是有數據支撐的,不該亂改);看到一個斷路器主動檢查失效模式同構的姐妹模塊——MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 補完後,5 級壓縮裏另外四級源碼裏看不到等價的連續失敗上限,可能只是還沒踩過同類事故,但失效模式(模型調用、timeout / rate limit / 返回不可解析)是一樣的。


8. 多 agent 協作 = 工具權限的主從差異

多 agent 協作通常被認為需要獨立的"協議狀態機"。Claude Code 的實現路徑不同。

src/coordinator/workerAgent.ts 裏核心機制只有十幾行:

const INTERNAL_ORCHESTRATION_TOOLS = new Set([
  TEAM_CREATE_TOOL_NAME,
  TEAM_DELETE_TOOL_NAME,
  SEND_MESSAGE_TOOL_NAME,
  SYNTHETIC_OUTPUT_TOOL_NAME,
])

function getWorkerTools(): string[] {
  return Array.from(ASYNC_AGENT_ALLOWED_TOOLS).filter(
    name => !INTERNAL_ORCHESTRATION_TOOLS.has(name),
  )
}

Claude Code 開啓 COORDINATOR_MODE 之後,主 agent 變成"協調員",只能調一個叫 worker 的子 agent;worker 拿到一份"全標準工具集減去 4 個"的工具白名單。協調員能用、worker 不能用的,只有這 4 個:建團隊、解散團隊、發消息、合成輸出。worker 拿不到這 4 個,意味着它不能再開新的子 agent、不能跨 agent 通信、不能直接出最終答案——只能完成具體任務、彙報回協調員。

用現有 primitive 的差分關係表達層級,比發明新協議成本低得多——主-從被實現成"主 agent 工具集 ⊃ 子 agent 工具集",權限約束自然形成層級,複用 agent 已有的"用工具"心智模型。適用邊界:Claude Code 的多 agent 是單根樹(無 sibling 通信、無跨層 broadcast),90% 場景夠用;剩下 10%(多 worker 協商任務分配)只能讓協調員當中間路由,token 開銷和延遲顯著增加。落地路徑:先確定工具的差分邊界,再考慮是否需要協議——遇到那 10% 時再設計協議,屆時也才知道協議要長什麼樣。


9. 8 條原則總覽

這 8 件事放在一起看,Claude Code 的設計重點不是“讓模型更聰明”,而是把模型放進一個有邊界的執行系統裏:入口可控、工具可控、記憶可控、成本可控、失敗可控。拆成可借用的設計原則,就是下面幾條:

  • agent 內核要薄。核心是 while(true) 的“模型採樣 → 工具執行 → 結果回灌”循環;壓縮、工具調度、權限、記憶、生命週期 hooks 等複雜度拆到外圍模塊,模塊之間保持可替換。
  • system prompt 物理切兩半。前半是系統內置部分(共享緩存),後半 per-session 動態,用一個明確的 boundary marker 切開。不要用模板字符串一次性拼出。
  • 工具調度複用 OS 思路。讀讀並行、讀寫互斥,fail-closed 默認 + env var 調上限。不必自行設計 DAG。
  • 記憶要分層。索引常駐、正文按需、歷史 grep。不記代碼,只記偏好和判斷。
  • 上下文壓縮用漸進式策略。從輕量裁剪到全量摘要按 token 佔用率逐級觸發,不要一上來就做全量摘要。
  • 工具描述按需加載。MCP / 外部工具默認只列名字,模型用 ToolSearch 按需取 schema。避免幾千 token 工具描述塞滿上下文。
  • 關鍵閾值預留覆蓋入口、決策痕跡釘在代碼身邊。數字用 env var 可覆蓋,決策原因用 BQ <日期>: 註釋。看到一個斷路器,主動檢查失效模式同構的姐妹模塊。
  • 多 agent 協作用工具權限差異表達。先確定"協調員獨佔哪幾個 primitive",主從層級自然形成,不必先設計協議。

生產級 agent 的優秀設計,絕大多數不是設計階段想出來的,而是在生產流量中迭代出來的MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 沒有論文會教,它來自 1279 個會話出錯之後的一次 BQ 查詢。Claude Code 真正稀缺的不是 51 萬行代碼,而是每一處帶日期註釋背後大量真實會話的反饋循環——這些反饋已經經過實際驗證,結論可以直接借用。

引用連結

[1]claude-code-best/claude-code: https://github.com/claude-code-best/claude-code