01

Claude Code 是什么

你输入一条消息,它给你回复——但中间发生了什么?

你在终端输入一条消息

想象你有一个超级聪明的同事,坐在你旁边。你跟他说一句话,他不仅回答你,还能自己打开文件、运行命令、修改代码——然后回来跟你汇报结果。

Claude Code 就是这样的一个存在,只不过它住在你的终端里。它不是网页应用,不是 IDE 插件——它是一个完整的 TUI 程序,直接跑在你电脑上。

💡
为什么这很重要?

因为 Claude Code 跑在你本地,所以它能直接访问你的文件系统、运行 shell 命令、读写代码——这些是普通聊天机器人做不到的。理解这一点,你就知道为什么它有时候能做很厉害的事,有时候又会"动手动脚"改你的文件。

💻
终端原生应用

通过 npm install -g @anthropic-ai/claude-code 安装,在你的 CLI 中运行,不需要浏览器或 IDE

💬
对话式交互

你打字、它回复,看起来像聊天——但每一轮对话背后都是一系列复杂操作

🔧
能执行操作

不仅能说,还能做:读文件、写文件、运行命令、搜索代码——它是一个真正的 Agent

Claude Code 不是聊天机器人

大多数人以为 Claude Code 就是"在终端里跟 AI 聊天"。但实际上,它是一个完整的本地 Agent 平台,有六层架构,每一层各司其职。点击下面的组件,了解每一层做了什么。

Layer 1 — CLI 引导层
cli.tsx
Layer 2 — TUI / REPL 交互层
🖼 TUI 渲染
Layer 3 — Query / Agent 执行内核
🧠 Agent Loop
Layer 4 — Tool / Permission 层
🔧 工具 & 权限
Layer 5 — Memory / Persistence 层
💾 记忆系统
Layer 6 — 扩展层 (MCP / Skills)
🔌 MCP / Skills

点击任意组件查看详细说明

🎯
核心理解

Claude Code 不是"API 包装器"。它有自己完整的执行引擎、权限系统、记忆机制和扩展框架。理解这六层,你就理解了后面所有内容的基础。

代码翻译:入口的秘密

让我们看看 Claude Code 源码的真实入口。这段来自 TypeScript 文件 src/entrypoints/cli.tsx,它是一切的起点。左边是代码,右边是逐行翻译的中文解释。

TypeScript async function main(): Promise<void> { const args = process.argv.slice(2); // Fast-path for --version/-v: // zero module loading needed if ( args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') ) { console.log(`${MACRO.VERSION} (Claude Code)`); return; } // For all other paths, load the // startup profiler const { profileCheckpoint } = await import('../utils/startupProfiler.js'); profileCheckpoint('cli_entry'); }
中文翻译
定义一个异步函数 main(),没有返回值
把命令行参数取出来(前两个是 node 和脚本路径,所以从第 3 个开始)
 
快速通道:如果只是查版本号
不需要加载任何其他模块,直接输出版本就退出
 
这是性能优化的典范:99% 的启动时间花在加载模块上,但查版本根本不需要那些模块
MACRO.VERSION 是编译时注入的版本号
 
常规通道:如果不是查版本
用动态 import() 按需加载启动分析器
记录一个时间戳,用于分析启动性能
🔎
设计智慧

注意 import() 是动态导入——只有真正需要时才加载模块。这种"快速通道 + 惰性加载"的模式贯穿整个 Claude Code 源码。每一个 if 分支都是一条短路,让你不需要的功能零成本。

完整旅程:从输入到回复

当你在终端输入 claude 并打出一句话,背后发生了一系列精心编排的步骤。这是一条从启动到回复的完整数据流。

cli.tsx
init.ts
🔒
setup.ts
🔧
getTools()
🖼
REPL
API
🔄
Agent Loop
Click "Next Step" to begin
Step 0 / 8
1
cli.tsx → 快速通道检查

入口函数先做最轻量的检查:你只是在查版本号吗?如果是,零模块加载,秒回。否则进入完整启动流程。

2
init.ts → 环境初始化

加载配置系统、设置遥测(source map)、初始化进程信号处理。还会设置 NODE_OPTIONS 等运行时参数。

3
setup.ts → 信任检查 & 认证

检查当前目录是否受信任(首次使用会弹出确认)、验证 API 认证信息、加载安全策略、检测 Git 仓库。

4
getTools() + getMcpTools() + initSkills()

收集所有可用工具:15+ 个内置工具(读文件、写文件、搜索、Bash 等)+ MCP 外部工具 + 预定义技能。这些工具就是 Agent 的"手脚"。

5
launchRepl() → 界面渲染

用 React + Ink 渲染终端 UI。显示欢迎信息、加载对话历史、准备好接受你的输入。从此刻起,你看到的就是一个完整的 REPL 界面。

6
你输入消息 → query() → API 调用

你的消息被组装成 API 请求(包含系统提示词、对话历史、可用工具列表),发送到 Anthropic 云端。AI 模型在那里"思考"并返回结果。

7
工具执行 → Agent Loop 循环

如果 AI 回复中包含工具调用请求("我需要读取 X 文件"),Claude Code 执行该工具,把结果返回给 AI,AI 继续思考——这个循环会一直进行,直到 AI 不再请求工具,给出最终回复。

🎯
为什么理解这个流程很重要?

当 Claude Code 出问题时,你可以根据这个流程快速定位:是启动阶段出了问题?还是 API 调用失败?还是工具执行被权限拦截了?理解流程就是拥有 debug 的地图。

知识检验

试试看你是否理解了 Claude Code 的架构。选择你认为正确的答案。

1. 当你在终端输入 claude --version 时,cli.tsx 入口函数做了什么?

2. Claude Code 的 Agent Loop 是什么?

3. 如果让你设计 Claude Code 的 CLI 引导层,它的核心设计原则应该是什么?

4. 场景题:Claude Code 想要修改你项目中的一个文件。根据六层架构,哪个层会负责检查这次修改是否被允许?

02

认识核心角色

每一次 Claude 回复的背后,都有一整组组件在协同工作。
认识它们,就像认识一出戏剧里的演员表。

演员表 — 六个核心角色

想象 Claude Code 是一出舞台剧。你的每一次对话就是一场演出,而这些文件就是各自分工明确的演员。它们各司其职,缺一不可。

🎤
为什么要认识它们?

当某个功能不正常时,知道"这是哪个演员的台词"能让你快速定位问题。理解了分工,你就能对 AI 说"把这个逻辑放在 X 里,而不是 Y"。

🚪
cli.tsx — 入口分流器

程序的起点。它读取你的命令行参数,快速判断你要做什么(交互模式、--print 单次执行、--resume 恢复会话),然后把控制权交给正确的路径。

main.tsx — 总控中心

初始化一切:注册所有工具、建立 MCP 连接、设置权限上下文、加载技能。准备就绪后,启动 REPL。

💬
REPL.tsx — 交互界面

你看到的聊天窗口。管理消息列表、输入框、权限弹窗、进度展示。所有状态都在这里汇聚和展示。

query.ts — 执行引擎

核心循环:发消息给 AI → 收到工具调用 → 执行工具 → 结果回流给模型 → 循环。这个异步循环是整个系统的心跳。

🔧
Tool.ts — 工具协议

每个工具的统一接口:权限检查、输入校验、执行、结果格式化。BashTool、ReadTool、EditTool 都实现这个协议。

🧠
Memory系统 — 长期记忆

文件夹 + Markdown。跨会话记住你的偏好、项目知识、常用规则。下次启动时,Claude 会先读取这些记忆,就像演员上场前温习剧本。

组件对话 — 一次典型交互的幕后

点击下方的按钮,看这些组件在一次典型的用户交互中如何"群聊"。每条消息就是一个组件在说自己的台词。

0 / 7 messages

代码翻译 — Tool 接口长什么样?

左边是真实的源码片段,右边是逐行翻译成"人话"的版本。这就是所有工具(BashTool、ReadTool、EditTool 等)共同遵守的协议

src/Tool.ts export type Tool< Input extends AnyObject = AnyObject, Output = unknown, P extends ToolProgressData = ToolProgressData, > = { aliases?: string[] searchHint?: string call( args: z.infer<Input>, context: ToolUseContext, canUseTool: CanUseToolFn, parentMessage: AssistantMessage, onProgress?: ToolCallProgress<P>, ): Promise<ToolResult<Output>> }
逐行翻译
Tool 是一个泛型类型 — 每个工具可以定义自己的输入格式、输出格式和进度数据类型。
Input — 工具的输入参数,用 Zod Schema 定义。默认是任意对象。
Output — 工具执行后返回的结果类型。
P — 进度上报的数据类型(比如文件读取百分比)。
aliases — 工具的别名,AI 可以用不同的名字调用同一个工具。
searchHint — 帮助 AI 理解何时该用这个工具的搜索提示。
call() — 核心方法:工具真正干活的地方。
args — AI 传过来的参数,已经过 Schema 校验。
context — 当前执行上下文(对话信息、文件状态等)。
canUseTool — 权限检查函数:"我能不能执行这个操作?"
parentMessage — 触发这个工具调用的 AI 消息引用。
onProgress回调函数,工具执行过程中可以上报进度。
返回 Promise — 异步执行,最终返回包装好的 ToolResult。
💡
设计启示

所有工具共用同一个接口,意味着添加新工具只需要实现 call() 方法定义输入 Schema。系统的其他部分(权限、调度、结果处理)完全不用改。这就是接口隔离的威力。

AppState — 共享状态总线

REPL 不仅仅是一个界面,它还是整个应用的状态总线。所有关键数据都挂在它上面,任何组件都可以通过这些共享状态来了解"现在系统处于什么情况"。

📄
messages

所有对话消息的数组。包括你的输入、AI 的回复、工具调用的结果。这是对话历史的完整记录。

🔒
toolPermissionContext

当前的权限上下文 — 哪些工具可以自动执行,哪些需要用户确认。动态更新,取决于你的设置和对话进展。

🔗
mcpClients

所有已连接的 MCP(Model Context Protocol)服务器。每个 MCP 服务器提供额外的工具和资源,扩展 Claude 的能力。

🤖
agentRegistry

可用的 Agent 定义列表。Agent 是带有特定人格和能力的子 AI,可以并发启动来处理子任务。

🔔
notifications

通知队列。当工具完成、权限被请求、或 Agent 汇报时,通知会排队显示在这里。

🎤
类比

如果 Claude Code 是一个剧组,AppState 就是后台的公告板。所有演职人员(组件)都看同一块公告板,知道"现在演到第几幕"、"谁在台上"、"接下来轮到谁"。

场景测试 — 你会找谁?

下面是三个真实场景。根据你刚学到的组件分工,选择最合适的答案。

1. 用户发送消息后,Claude 返回了一个需要读取文件的 tool_use。哪个组件负责检测到这个 tool_use 并安排执行?
2. BashTool 准备执行 rm -rf /tmp/test,系统需要先确认这个命令是否安全。这个权限检查的逻辑属于哪个组件的职责?
3. Claude Code 启动时需要加载 MCP 服务器提供的扩展工具。这个"建立连接并注册扩展工具"的初始化工作由谁负责?
03

工具执行流水线

从 AI 建议到真实执行 —— 每一次工具调用都要经过同一条质检流水线

总链路:一次工具调用的完整旅程

当 Claude 说 "让我运行这个命令" 的时候,它并不是直接在你的电脑上执行。相反,这个请求要经过一整条 流水线, 每个环节都有自己的职责 —— 就像工厂里的质检站,零件必须逐个通过才能出厂。

点击 "下一步" 开始动画
AI
Model
模型输出
Q
query.ts
收集调用
O
orchestrate
分区调度
X
execute
执行引擎
R
result
结果封装
L
loopback
回流循环
Step 0 / 6
核心洞察

工具调用不是直接的函数调用 —— 它们要经过 Schema校验权限检查Hook钩子 然后才真正执行,执行结果再通过 tool_result 流回模型。理解这条链路,是调试 "为什么我的工具没执行" 的关键。

并发 vs 串行:partitionToolCalls() 的智慧

模型可能一次性输出多个工具调用。但并不是所有工具都能同时执行 —— 有的只读不写,安全无副作用;有的会修改文件或运行命令,必须排队。这就是 partitionToolCalls() 要解决的问题。

并发安全 工具

标记为 isConcurrencySafe 的工具 —— 如 FileRead、Glob、Grep —— 只读不写,可以安全地 批处理 并行执行。

🔒
串行 工具

会修改状态的工具 —— 如 FileEdit、Bash —— 必须排队执行,防止并发冲突(两个工具同时改一个文件)。

💡
分区规则

partitionToolCalls() 把工具调用列表分成若干"批次"(Batch)。相邻的并发安全工具合并在同一批次里并行执行;一旦遇到非并发安全的工具,就开一个新批次串行执行。这样既保证了安全,又最大化了效率。

代码解读:partitionToolCalls

下面这段来自 src/services/tools/toolOrchestration.ts 的代码,就是分区逻辑的核心。左边是源码,右边是逐行中文解释。

TypeScript function partitionToolCalls( toolUseMessages: ToolUseBlock[], ctx: ToolUseContext ): Batch[] { return toolUseMessages.reduce(( acc: Batch[], toolUse ) => { const tool = findToolByName( ctx.options.tools, toolUse.name ) // 解析输入参数 const parsedInput = tool.schema ? parseInput(toolUse.input) : { data: toolUse.input } // 判断该工具是否并发安全 const isConcurrencySafe = Boolean(tool?.isConcurrencySafe( parsedInput.data )) // 如果当前工具并发安全 且 上一个批次也是并发的,合并进去 if ( isConcurrencySafe && acc[acc.length - 1] ?.isConcurrencySafe ) { acc[acc.length - 1].blocks .push(toolUse) } else { // 否则开一个新批次 acc.push({ isConcurrencySafe, blocks: [toolUse] }) } return acc }, []) }
中文解读
函数签名:接收工具调用消息数组 + 上下文,返回批次 (Batch) 数组
reduce 遍历:逐个处理每个工具调用,累积分区结果
findToolByName:根据名字在已注册的工具列表中找到对应的工具对象
parseInput:用工具定义的 Schema 解析输入参数,校验格式
isConcurrencySafe:调用工具对象上的方法,判断这次调用是否可以安全并发
合并条件:如果当前工具是并发安全的,而且上一个批次也是并发安全的,就把它追加到上一个批次里
新建批次:否则(不安全或类型不同),创建一个全新的批次
返回结果:一个 Batch 数组,每个 Batch 要么全是并发的、要么是单个串行的
📑
为什么是 reduce 而不是两次循环?

因为分区逻辑需要"看前一个":只有相邻的并发安全工具才能合并。reduce 天然适合这种"一边遍历一边累积"的模式。一次遍历就完成分区,时间复杂度 O(n)。

单次工具执行:6 个检查站

当一个工具调用被分到某个批次后,它还要经过 6 个 "质检站" 才能真正执行。每个检查站都可能阻止执行 —— 这是 Claude Code 安全模型的核心。

1
Schema 校验

输入参数是否符合该工具定义的 JSON Schema?比如 file_path 必须是字符串,command 不能为空。不符合就直接报错。

2
validateInput —— 工具自定义校验

某些工具有额外的自定义校验逻辑。比如 FileEdit 工具会检查 old_string 是否真的出现在文件里。

3
Pre-tool Hooks

用户配置的前置钩子在这里执行。例如你可以配置一个 hook,在每次 Bash 执行前自动检查是否包含危险命令。

4
Permission 检查

ask / deny / auto —— 权限规则决定这个工具调用是否需要用户确认。如果是 ask,会弹出确认对话框;如果是 deny,直接拒绝执行。

5
tool.call() —— 真正执行工具逻辑

通过了前面所有检查站后,才调用工具自身的 call() 方法。每个工具都实现了自己的 call(),比如 Read 工具读取文件内容、Bash 工具启动子进程。

6
生成 tool_result

执行结果被格式化为 tool_result 消息块。无论成功还是失败,都会生成结果 —— 错误信息同样会反馈给模型。

🛡
安全设计理念

注意第 1-4 步都是"防御性"的 —— 它们只做检查,不执行任何实际操作。真正的执行只有第 5 步。这种 "先检查、后执行" 的模式是 Claude Code 安全模型的核心原则。

结果回流:工具执行结果的旅程

工具执行完毕后,结果并不是 "到此结束"。它要经过一个叫 回流 的过程,重新回到 异步生成器 的 query 循环里。

1

tool.call() 返回结果

2

封装为 tool_result 块

3

追加到对话历史

4

发给模型重新推理

?

继续调用工具 / 回复用户

模型看到 tool_use 的结果后,会做出判断:

🔄
继续调用工具

如果结果不完整或需要更多信息,模型会再输出新的 tool_use,重新进入流水线。

回复用户

如果信息足够,模型直接输出文本回复,query 循环结束,用户看到结果。

报告错误

如果工具执行失败,错误信息也会回流到模型,模型会据此调整策略或向用户报告。

🔁
这就是 "Agentic Loop"

工具调用 -> 结果回流 -> 模型推理 -> 可能再次调用工具 —— 这个循环可以反复进行多次。这就是 Claude Code 能完成复杂任务的原因:它不是一次性给答案,而是一步步探索、验证、调整,直到完成目标。

测验:工具执行流水线

场景 1:模型同时输出了 FileRead、Glob、FileRead 三个工具调用。partitionToolCalls() 会怎么处理?
场景 2:你配置了一条规则让 Bash 工具需要用户确认。在流水线的哪一步会弹出确认对话框?
场景 3:一个工具执行成功并生成了 tool_result。接下来会发生什么?
04

记忆与上下文系统

Claude Code 不会每次从零开始。它有一套归档系统。

四层记忆系统

想象一个记者的工作台:每日速记本用来记当天发生的事, 参考卡片盒存长期有用的知识, 团队公告板共享给所有人看, 个人日记本只写自己的心得。 Claude Code 的记忆系统也是四层,每层有不同的生命周期可见性

🗂️
Auto Memory

自动提取的长期记忆,存放在 memory 目录MEMORY.md索引, 具体记忆写在独立的 *.md 文件里。

📝
Session Memory

当前会话摘要,辅助 compact 和长会话持续运行。 阈值触发后才启用,由后台 subagent 维护,权限严格(0o600)。

🤖
Agent Memory

绑定到特定 agent 类型的持久记忆。分 user / project / local 三个 scope。 在 agent 的 system prompt 中直接注入,从第一轮就知道自己的记忆目录。

👥
Team Memory

团队共享的 repo 级知识同步。带 pull / push、 watcher、 checksum、secret scanning 的受控同步层。

💡
核心设计理念

记忆不是数据库,是 Markdown 文件夹。用户可以直接打开目录查看、编辑、删除。透明、可治理、可组合。

代码翻译:记忆常量

源码 src/memdir/memdir.ts 里定义了记忆系统的核心参数。 这几行代码决定了模型"看什么、看多少"。

src/memdir/memdir.ts // 记忆索引文件的名称 export const ENTRYPOINT_NAME = 'MEMORY.md' // 索引文件最多 200 行 export const MAX_ENTRYPOINT_LINES = 200 // 索引文件最大 25KB export const MAX_ENTRYPOINT_BYTES = 25_000 // 构建记忆 prompt — 注入到 system prompt export function buildMemoryPrompt(params: { displayName: string memoryDir: string extraGuidelines?: string[] }): string { // 同步读取 MEMORY.md(来自 React render 路径) const entrypoint = params.memoryDir + ENTRYPOINT_NAME const raw = fs.readFileSync(entrypoint, { encoding: 'utf-8' }) // 硬截断保护 — 防止 prompt 爆炸 const t = truncateEntrypointContent(raw)
中文解读
ENTRYPOINT_NAME — 记忆系统的入口文件叫 MEMORY.md,不是 database,不是 JSON,就是一个 Markdown 文件。
MAX_ENTRYPOINT_LINES = 200 — 索引最多 200 行。超了就截断。为什么不是 1000?因为这段文本每次都要塞进 system prompt,200 行已经是慷慨的预算了。
MAX_ENTRYPOINT_BYTES = 25KB — 体积上限 25KB。双重保护:行数和字节数同时约束。
buildMemoryPrompt() — 把 MEMORY.md 的内容读出来,截断后拼成 prompt 字符串,在每次 API 调用时注入到 system prompt。模型永远能看到这份索引。
同步读取 — 用 readFileSync 而非 readFile。因为某些调用来自 React render 路径,不能用 await。这是工程上的务实取舍。
truncateEntrypointContent() — 硬截断保护。防止 MEMORY.md 被写爆后拖垮整个 prompt。
📚
为什么要用文件而不是数据库?

因为文件是用户可以直接打开看的。透明、可治理、可手动编辑。用户可以删掉一条错误记忆,也可以手动补充一条。这比一个黑盒 KV 存储更能让人信任。

文件即记忆

Auto Memory 的存储不是写死的,而是通过路径计算函数动态解析。 以下是最常见的本地安装时的目录布局:

目录结构 ~/.claude/projects/<project-hash>/ ├── MEMORY.md ← 索引文件,最多 200 行 └── memories/ ├── user_role.md ← 关于用户的记忆 ├── feedback_style.md ← 反馈偏好记忆 ├── project_context.md ← 项目上下文知识 └── tech_stack.md ← 技术栈偏好
各文件职责
MEMORY.md — 索引入口。模型每次 API 调用都能看到它的前 200 行。里面是每条记忆的标题和一行描述,不是正文。
memories/*.md — 每条记忆是独立 Markdown 文件。模型在需要细节时,通过 相关性召回 机制选出少量文件注入上下文(最多 5 个)。
写入方式 — 双步法:先写 topic 文件到 memories/,再在 MEMORY.md 添加索引行。更新也是两条:编辑 topic 文件,更新索引描述。
治理 — 模型被要求删除过时记忆、避免重复、合并相似记忆。但最终依赖模型的遵守程度,所以用户手动检查仍然重要。
agent-memory (Agent Memory 目录) 以 agent 类型名为子目录
user scope: ~/.claude/agent-memory/<agentType>/ 跨项目通用
project scope: .claude/agent-memory/<agentType>/ 项目内共享
local scope: .claude/agent-memory-local/<agentType>/ 当前机器/环境
⚠️
记忆治理是用户责任

系统设计了规则防止记忆膨胀(200 行上限、相关性召回),但错误记忆、过时记忆、重复记忆仍会持续影响 agent 行为。建议定期检查 MEMORY.md 和 memories 目录。

上下文组装:每次 API 调用都装了什么

每次向模型发 API 请求时,Claude Code 会把以下材料按层叠入 上下文窗口。 这不是随意堆砌,而是有明确的组装顺序。

1
System Prompt

技能描述、规则文件(CLAUDE.md)、工具 schema、角色定义。这是每一轮对话的"宪法"。

2
MEMORY.md 索引

前 200 行的记忆索引。模型永远能看到这份清单,知道"自己知道什么"。

3
Relevant Memories(相关性召回)

按相关性选出最多 5 个记忆文件的内容。不是全塞,是轻量检索器用 sideQuery 做选择。

4
Session History(对话记录)

当前会话的历史消息。如果经过 compact,这里会变成精简后的摘要 + 最近几轮完整对话。

5
Tool Results(工具执行结果)

上一轮工具调用的返回结果。文件内容、命令输出、搜索结果等都在这里。

打包成 messages[] 发给模型

所有层叠好之后,组成一个消息数组,发送给 Anthropic API。模型看到的就是这个完整的上下文。

🎯
为什么关心这个?

理解上下文组装的顺序和限制,你就知道为什么 Claude Code 有时候会"忘记"之前说过的话——不是因为记忆坏了,而是那条信息被 compact 掉了,或者没有被写入记忆文件。把重要的事写进 MEMORY.md,是让模型持续记住的关键。

Compact — 上下文压缩

对话越来越长,上下文窗口终归会满。 Claude Code 不用暴力截断,而是设计了一套精密的 compact 机制。

📈
触发机制:双重防线

shouldCompact() 检查当前消息的 Token 长度。 当接近上下文窗口上限时(预留约 13,000 Token 缓冲区),自动触发压缩。 用户也可以随时手动输入 /compact 触发。

🔎
压缩过程:不是简单截断

系统先把图片和冗余附件剔除(脱水),然后交给一个 Forked Subagent 生成摘要。 摘要完成后,旧消息被替换成一条短的 Summary Message。 但关键状态会被重新注入:当前查看的文件、激活的工具列表、进行中的 Plan。

💪
Session Memory 维持连续性

如果会话已经很长,Session Memory 会自动提取摘要。 Compact 时直接读取这份摘要作为断点,不再额外调 API 浪费 Token。 这是整个 compact 设计中最巧妙的工程细节之一。

src/services/compact/autoCompact.ts // 熔断器:连续失败 3 次就停止 const MAX_CONSECUTIVE_FAILURES = 3 // 为 Summary 预留的 Token const MAX_OUTPUT_FOR_SUMMARY = 20_000 // 触发缓冲区 const BUFFER_TOKENS = 13_000 if (consecutiveFailures >= 3) { return { wasCompacted: false } }
设计解读
熔断器 — 连续 3 次压缩失败就完全停止。这防止了"上下文已经爆了,但还在不断请求压缩、不断失败"的死循环。据数据,这个小判断每天省下约 250K 次死锁 API 调用。
20K 预留 — 从 200K 总窗口中扣掉 20K,留给摘要生成用。确保压缩请求本身不会因为太大而失败。
13K 缓冲 — 不是等到满了才压缩,而是提前 13K Token 就开始。这是一个安全距离,让压缩请求有时间完成。
PTL 兜底 — 如果压缩请求本身超限(Prompt Too Long),会剥洋葱式地砍掉 20% 最旧的消息重试。最后的救命稻草。

测验:你会用记忆系统吗?

Scenario 1

你希望 Claude Code 记住"我喜欢用 TypeScript 写后端"。它把这个信息保存后,下次新会话时模型是如何看到这条记忆的?

这条记忆会被怎样注入到模型上下文中?
Scenario 2

你定义了一个自定义 agent 叫 code-reviewer,希望它在所有项目中都能记住"团队偏好用函数式写法"。你应该选哪个 scope?

Agent Memory 的 user scope 意味着什么?
Scenario 3

你正在做一个大型重构任务,对话已经进行了 50 轮,上下文快要满了。系统自动触发了 compact。

Session Memory 在 compact 中的核心作用是什么?
05

安全沙箱与扩展能力

一个能在你电脑上执行命令的 AI 是危险的。Claude Code 如何保护你——以及你如何扩展它。

沙箱不是开关,是链条

想象一个银行金库:不是一道门,而是门卫 → 存款箱 → 警报系统多层防护。Claude Code 的 沙箱 也一样——四个环节环环相扣,缺一不可。

🛡
关键洞察

沙箱不是安全边界——权限系统才是真正的防线。沙箱只是其中一层隔离手段。

1
shouldUseSandbox() — 决策层

判断当前命令是否需要进入 隔离 环境。它会检查命令内容、用户设置和 特性开关, 返回 true 或 false。

2
convertToSandboxRuntimeConfig() — 翻译层

把高层的用户设置("我要用沙箱")翻译成底层 运行时 配置——告诉底层隔离工具具体怎么限制文件访问、网络权限等。

3
bashPermissions — 规则层

定义哪些命令自动放行、哪些需要拒绝、哪些需要询问用户。即使进了沙箱,这层 权限 规则依然生效。

4
Shell.ts + cleanupAfterCommand() — 执行层

真正的隔离执行。命令在受限环境中运行,执行完毕后自动 清理 临时文件和进程句柄,不留痕迹。

?

需要沙箱?

2

翻译配置

3

权限检查

4

隔离执行 + 清理

权限系统 — 四种命运

每一条命令在执行前都要经过 权限 系统的审判。你的 .claude/settings.json 中可以精确控制每条规则:

allow 自动允许 — 这个命令被无条件放行,不询问用户
deny 永远拒绝 — 不管什么情况,这条命令绝对不会执行
ask 每次都问你 — 弹出确认对话框,由你亲自决定放行与否
sandbox 在沙箱中执行 — 命令可以跑,但被隔离在受限环境里
📋
实战配置

.claude/settings.jsonpermissions 字段中,你可以为特定命令模式设置规则。例如:"Bash(git log *)": "allow" 让所有 git log 命令自动通过。

Skills — 技能系统

Skills 是 Claude Code 的插件机制。一个 Markdown 文件 + YAML frontmatter = 一个技能。技能被 注入 到 system prompt,不需要修改源码。

~/

用户级技能

~/.claude/skills/ — 跨项目通用,所有项目都能用

./

项目级技能

.claude/skills/ — 团队共享,跟随项目仓库

内建技能

Claude Code 自带,开箱即用

发现路径的优先级:用户级 → 项目级 → 内建。加载过程使用 memoize 缓存 + Promise.all 并行读取:

TypeScript // loadSkillsDir.ts — 核心加载逻辑 export const loadSkillsDir = memoize( async (dir: string) => { const files = await readDir(dir) const skills = await Promise.all( files.map(f => parseSkillFile(f)) ) return skills.filter(Boolean) } )
中文解读
memoize — 只加载一次,后续调用直接返回缓存结果
readDir — 扫描技能目录中的所有文件
Promise.all — 并行解析所有技能文件,不串行等待
filter(Boolean) — 过滤掉解析失败的文件
💡
为什么这样设计?

技能不修改源码、不编译、不安装依赖——纯文本声明式扩展。这降低了扩展门槛,也让团队协作时可以通过 Git 同步项目级技能。

MCP — 模型上下文 协议

MCP(Model Context Protocol)让 Claude Code 连接外部工具服务器。每个 MCP 服务器暴露一组工具,Claude Code 按约定格式调用。工具名的命名遵循严格的规则:

TypeScript // buildMcpToolName.ts export function buildMcpToolName( serverName: string, toolName: string ): string { return `mcp__${serverName}__${toolName}` } // 实际例子: // mcp__filesystem__read_file // mcp__puppeteer__screenshot // mcp__github__create_issue
中文解读
mcp__ — 固定前缀,标识这是 MCP 工具
filesystem — 服务器名称,区分不同工具来源
__ — 双下划线分隔符
read_file — 该服务器上的具体工具名
这种命名保证了全局唯一性——不同服务器的同名工具不会冲突
📁

filesystem

安全文件读写,限制在指定目录范围内

🎨

puppeteer

浏览器自动化——截图、填表、爬取

📦

自定义服务器

用 npm 包或 Docker 镜像运行自己的 MCP 工具

源码解读 — shouldUseSandbox

这段代码决定一个命令是否需要进入沙箱。注意那个关键注释——排除列表是用户便利功能,不是安全边界

TypeScript // shouldUseSandbox.ts type SandboxInput = { command?: string dangerouslyDisableSandbox?: boolean } // NOTE: excludedCommands is a user-facing // convenience feature, NOT a security boundary. function containsExcludedCommand( command: string ): boolean { if (process.env.USER_TYPE === 'ant') { const disabled = getFeatureValue_CACHED_MAY_BE_STALE<{ commands: string[] substrings: string[] }>('tengu_sandbox_disabled_commands', { commands: [], substrings: [] }) for (const sub of disabled.substrings) { if (command.includes(sub)) return true } } // ... }
中文解读
SandboxInput — 输入参数:命令内容和是否强制关闭沙箱
dangerouslyDisableSandbox — 命名暗示:这是危险操作
NOT a security boundary — 源码注释明确说:排除列表不是安全边界
USER_TYPE === 'ant' — Anthropic 内部用户的特殊分支
CACHED_MAY_BE_STALE — 函数名诚实标注:缓存可能过期
substrings — 子串匹配,包含特定字符串的命令被排除
⚠️
源码注释的诚实

注意 getFeatureValue_CACHED_MAY_BE_STALE 这个函数名——开发者故意把"缓存可能过期"写进函数名,而不是藏起来。这种命名方式本身就是一种文档。还有那个 dangerouslyDisableSandbox,用 dangerously 前缀警告调用者三思。

多 Agent 形态 — 一个代码库,六种运行方式

Claude Code 不仅仅是你在终端里用的那个聊天界面。同一套代码可以以六种不同形态运行:

💻
REPL / TUI

默认终端交互模式——你在命令行里跟 Claude 对话

🤖
Headless / SDK

无 UI 模式——编程调用,用于自动化流水线和 CI/CD

🔗
MCP Server

对外暴露能力——其他 AI 客户端可以通过 MCP 协议 调用 Claude Code 的工具

🌐
Bridge / Remote

远程控制 模式——连接远程机器上的 Claude Code 实例

⚙️
Daemon

后台常驻——以 守护进程 方式运行,随时接受任务

Background Sessions

后台任务 ——启动后可以切换去做别的事,回来查看结果

🔍
架构意义

这不是"六个产品",而是"一个核心 + 六个入口"。所有形态共享同一套工具系统、 权限 系统和上下文管理。这就是为什么理解核心架构如此重要——掌握了核心,就掌握了全部形态。

知识检验

Scenario 1

一个命令通过了沙箱检测,被允许在隔离环境中执行。这意味着它是安全的吗?

Scenario 2

你想给 Claude Code 添加一个自定义技能。你需要做什么?

Scenario 3

mcp__filesystem__read_file 这个工具名中,各部分的含义是?