前言
Claude Code 有很多行为,用着用着会觉得理所当然,但仔细一想会发现不太对劲。
比如改文件,为什么不能直接整文件覆盖,非要提供"要改的旧内容"?比如权限,为什么写文件要确认,但读文件不用,而访问某个网址又变成了域名级别的判断?比如 shell 执行,为什么 grep 和 rm 的体验完全不同——一个默认折叠输出,一个可能弹确认框?
这些行为都不是随机的。它们背后有一套一致的设计逻辑,每个决定都是在某种取舍之下做出的。这本书讲的就是这套逻辑。
读者预设是:你已经在用 Claude Code,不需要解释基础用法。你想知道的是"它为什么这样做",而不是"它能做什么"。
每章围绕一个功能域,重点放在实现原理和设计取舍上。源码路径偶尔出现,但不是重点——理解设计比认识文件路径更有用。建议先读架构总览,再按兴趣进入各章。
架构总览
理解一个系统最快的方式,是跟着一个具体操作走一遍,看它在系统里经历了什么。
一次文件编辑发生了什么
假设你让 Claude Code 修改一个文件。从你按下回车,到 diff 出现在界面上,中间经过了这些:
首先,你的输入被判断为自然语言任务,送给模型处理。模型决定要调用 FileEditTool。
调用之前,FileEditTool 先问一句:这次编辑,用户允许吗?它检查你的权限规则,如果这个路径在允许范围内,直接继续;如果没有匹配规则,弹出确认框等你决定。
权限通过后,工具执行编辑:校验 old_string 唯一存在、路径安全、文件没被外部改动,然后写入。
写入之后,工具把结果打包成消息,包括 diff、成功状态、LSP 诊断信息。消息进入消息流,经过排序和分组,最终显示在你面前。
如果这次编辑很耗时,或者是子 agent 做的,整个过程被包装成一个 task,有自己的 id 和状态,你可以随时查看进度。
这一次操作,就触碰了系统里几乎所有的层。
从这个例子看到的结构
把上面的流程抽象出来,就得到了系统的基本分层:
- 入口层:判断你说的是什么、当前是什么运行模式
- 命令层:slash command 的注册和执行
- 工具层:实际干活——FileEditTool、BashTool、WebFetchTool 等
- 权限层:横切所有工具,每次操作前的决策
- 消息层:把工具输出变成统一的消息,进入会话流
- 任务层:长时间执行的统一容器
- 状态层:memory、session、context、config
这些层不是严格的调用栈,真实代码里会交织。但每层的职责是清楚的,出了问题一般能定位到哪一层在负责。
一个贯穿始终的设计选择
读完这本书,你会发现 Claude Code 一遍一遍地做同一件事:把某种能力从"让模型用 shell 命令临时解决"变成"专门的工具,带权限检查和展示协议"。
读文件可以跑 cat,但有 FileReadTool。搜索可以跑 grep,但有 GrepTool。联网可以跑 curl,但有 WebFetchTool。
每次这样做,系统就多了一层,但也多了对这件事的感知和控制:知道花了多少 token、知道用户允不允许、知道怎么展示给用户看。
理解了这个模式,后面每一章都会变得更好读。
各章导航
- 第 1-3 章:输入和命令——用户怎么和系统交互
- 第 4-6 章:核心工具——读、改、执行
- 第 7-9 章:控制层——plan mode、权限、任务
- 第 10-12 章:记忆——memory、session、context
- 第 13-15 章:向外延伸——web、MCP、plugins/skills
- 第 16-17 章:运行环境——config、后台模式
第 1 章 对话与命令输入
两种入口,两种职责
Claude Code 有两个输入入口:自然语言和 slash command。它们看起来并列,但在系统里承担的角色完全不同。
自然语言是"告诉 agent 要做什么",结果由模型决定。slash command 是"触发一个确定性功能",结果是可预期的。/plan 就是进入 plan mode,不管你怎么说,都是这个结果。
保留这两个入口,是因为一个好用的 coding agent 需要同时满足两种需求:处理模糊的、表达性的任务,以及执行高频的、确定性的操作。只有自然语言,熟练用户会觉得不够稳;只有命令,又退回了传统 CLI。
启动的时候发生了什么
Claude Code 启动时,第一件事不是加载主程序,而是判断"我现在是什么模式"。
--version这类查询 flag:不加载任何模块,直接打印退出。daemon、bridge、ps/logs/attach/kill这类特殊模式:走各自的初始化路径,不进入普通对话流程。- 普通对话:才继续加载完整的主程序。
这种分流设计的好处是:不同运行模式的初始化要求差异很大,分流比统一处理要干净得多。冷启动时间也因此更可控——很多模块用动态 import() 懒加载,不需要时根本不加载。分流其实比上面列的还多——根据启动参数不同,同一个包还可以作为 Chrome 自动化或 Computer Use 的 MCP server 启动,扮演完全不同的角色。
Slash command 是产品能力目录
commands.ts 里的命令注册表已经很大了,涵盖了 /plan、/memory、/compact、/permissions、/mcp、/review 等几十个命令。这个列表本质上就是 Claude Code 想对用户暴露的产品能力目录。
几个值得注意的设计细节:
-
命令不是散乱的函数,而是结构化对象。每个命令有类型、描述、参数定义和执行逻辑,统一注册管理。
-
很多命令通过 feature flag 条件注册。feature flag 为 false 时,模块根本不加载,也不会出现在命令列表里。这让同一份代码可以有不同的能力集,内部版和外部版的命令表可以差异很大。
-
插件和 skill 的命令也并入这个注册表,和内建命令统一管理。从用户角度看,它们的体验是一致的;从代码角度看,它们都是命令对象,只是来源不同。
第 2 章 消息流与会话界面
消息是统一展示协议,不是 UI 装饰
Claude Code 的界面看起来像聊天,但它的消息不是普通聊天消息。工具调用、文件变更、权限拒绝、diff、后台任务通知——所有这些都必须变成消息才能显示出来。
这个设计的关键在于:工具不是"返回数据,然后让 UI 猜怎么画",而是工具协议本身就包含展示方式。每个工具都要声明三件事:调用时显示什么、结果怎么显示、失败时显示什么。消息层和工具层之间有明确的协议边界,不是松散的耦合。
这就是为什么 Claude Code 的界面更像任务控制台:你能持续看到 agent 在做什么,而不是只看到最终答案。
消息到达界面之前经历了什么
原始消息和你看到的消息之间,有一个处理管道:
- normalize:把不同来源、不同格式的消息统一成标准结构
- reorder:调整显示顺序——某些消息在逻辑时序上和生成时序不一样
- grouping:把相关消息归组,比如一次工具调用和它的结果放在一起
- collapse:搜索结果、读取内容、后台任务通知这类输出默认折叠,不撑开界面
Brief 模式还有额外的过滤逻辑,会把很多中间过程的文本内容丢掉,只保留关键输出。
这个管道的存在说明一件事:显示给用户的会话轨迹是经过整理的,不是原始执行过程的直接映射。整理的目的是让人看得清楚,而不是完整记录每一个细节。
为什么要做成统一消息流
不这么做的后果是:每类功能都得自己搭 UI 状态,各搭各的,维护成本高,体验也会碎片化。
做成统一消息流的好处很实际:会话可以回放,因为所有执行历史都在消息里;状态可以审计,权限拒绝、工具调用、diff 都有记录;工具不需要自己实现展示逻辑,复用同一套容器就行。
还有一个间接好处:消息流的设计让 remote viewer 场景成为可能——另一个客户端可以订阅同一条消息流,实时看到 agent 在做什么,不需要重新执行任何东西。
第 3 章 Slash Commands 功能族
Slash command 解决的是什么问题
用自然语言操作 AI 有一个内在问题:结果不确定。你说"帮我进入规划模式",模型理不理解、怎么理解,每次可能不一样。
slash command 解决的就是这个问题。/plan 永远是进入 plan mode,/compact 永远是压缩上下文,跟模型怎么想没关系。它给熟练用户提供了一条绕过自然语言不确定性的高速通道。
这不是"AI 产品应该全用自然语言"和"还是用命令行吧"之间的妥协,而是两种模式各司其职的刻意设计。
命令是怎么工作的
slash command 有几种不同的执行方式,取决于命令的性质:
- 交给模型处理:把用户意图包装成 prompt,模型再去调用工具。
/review就是这样——命令把"审查当前改动"的意图构造成 prompt,模型决定读哪些文件、怎么分析。 - 本地直接执行:不经过模型,直接返回 UI 组件,结果是确定性的。
/permissions打开权限面板、/memory打开记忆文件,都是这类。 - 两者组合:先在本地做准备,再交给模型。
/compact就是先在本地计算当前上下文的分布,再用模型做实际的摘要压缩。
这种分类让命令系统很灵活:不是所有命令都需要走一遍 LLM,能在本地确定性完成的,就在本地完成。
Feature flag 和命令可见性
命令注册表里有一个重要机制:命令可以通过 feature flag 条件注册。
实际效果是:某些命令只在内部版出现,某些只在实验版出现,某些只有在特定组织下才可见。从代码角度看,所有这些命令都在同一份代码里,但不同的构建、不同的环境下,命令列表是不同的。
这让 Claude Code 可以在一份主干代码上并行演化多条产品路线,而不是维护多个 fork。代价是命令注册逻辑变复杂了,好处是功能迭代速度快得多。
插件和 skill 的命令怎么融入
用户通过插件安装的 skill,最终也会并入同一个命令注册表。从用户的角度,通过 skill 安装的自定义命令和 /compact 这样的内建命令,使用体验完全一样。
这是一个重要的设计决策:扩展能力和内建能力共享同一个接口层。用户不需要区分"这个命令是内建的还是安装的",这条界限在使用层是透明的。
第 4 章 读文件、搜内容、找文件
Claude Code 把文件操作拆成了三个专门工具:FileReadTool 读取文件内容,GrepTool 按关键词搜索,GlobTool 按路径模式查找文件。三者各司其职,也经常配合使用。
为什么读文件不直接用 cat
最简单的做法是:让 agent 自己跑 cat、grep、find。这完全可行,但 Claude Code 没有这么做。
原因是:直接用 shell 命令读文件,系统对这件事没有任何感知。不知道读了多少 token、不知道这个文件有没有超大、不知道路径是不是安全的,也没办法针对不同文件类型做特殊处理。出了问题,什么日志都没有。
所以 Claude Code 把读文件做成了专门的工具 FileReadTool,处理了一批 shell 命令处理不了的事情。
- 路径安全:
/dev/zero、/dev/random、/dev/tty等十几个特殊文件,读了要么无限阻塞进程,要么产生无穷输出,需要在读之前就识别并拦截。 - Token 预算:大文件不能整块塞进上下文。默认每次最多读 2,000 行或 25,000 tokens,超出的部分建议用 offset/limit 分段读。
- 文件类型处理:PDF(每次最多读 20 页)、Jupyter notebook、图片(超大图会逐级降质和缩小,直到塞得进上下文)——这些不能像普通文本一样处理,需要走各自的解析逻辑。
- 读取事件通知:读文件这个动作会通知给其他服务,比如 context 预算追踪,这样系统能知道"刚才读了什么,花了多少 token"。
还有一个用户感知不到的优化:如果一个文件自上次读取后没有被修改过,Read 工具会直接告诉模型"文件没变",不再重复读取内容,省下不必要的 token 消耗。
搜索有两个专门工具
grep 和 find 也是同样的道理——Claude Code 没有让 agent 直接用它们,而是做了两个专门的搜索工具:GrepTool 和 GlobTool。
GrepTool 是"找内容":你知道要找什么,但不知道它在哪个文件里。比如想找所有调用了某个函数的地方,或者想确认某个变量名在哪里被定义。搜索范围只限于你自己的代码,不会把 node_modules 这类依赖目录里的东西也翻出来。匹配太多时会截断,防止把上下文打爆。
GlobTool 是"找文件":你知道要找什么类型的文件,但不知道具体有哪些。比如想列出所有测试文件(**/*.test.ts)、所有 React 组件(src/components/**/*.tsx),或者某个模块的所有入口文件(src/**/index.*)。结果按修改时间排序,最近改动的排在前面。
三个工具配合使用
实际使用中,FileReadTool、GrepTool、GlobTool 这三个工具经常配合:先用 GlobTool 找到相关文件列表,再用 GrepTool 确认哪些文件里有目标内容,最后用 FileReadTool 读取具体内容。
这个分层查找的过程减少了不必要的 token 消耗——不需要把每个可能相关的文件都读进来,而是先缩小范围再精读。
第 5 章 改文件与 Diff 展示
精准替换,而不是整文件覆盖
Claude Code 改文件的主要方式是精准替换:你提供要改的旧内容和新内容,工具找到旧内容在文件里的位置,替换掉。
这个设计选择背后有一个重要约束:old_string 必须在文件里唯一存在。如果同样的内容出现了两次,工具会拒绝执行,而不是随便改其中一处。
这一条规则防止了很多"改错地方"的情况。如果模型写出了一段模糊的 old_string,系统直接拒绝,而不是悄悄改了一个错误的位置然后让用户事后发现。
不过如果精确匹配失败,系统还会做一次行尾符归一化的重试——把 CRLF 统一成 LF 后再找一遍。这解决了一个常见的跨平台问题:从 Windows 同事那拿到的文件,行尾符可能和你看到的不一样,编辑不应该因此失败。
整文件覆盖(FileWriteTool)也存在,但主要用于新建文件或者真的要完全重写的情况。一般的修改都应该用精准替换,改动范围更小,diff 更可读。
编辑之前要过的关
在真正写文件之前,FileEditTool 会做一批前置检查:
- 路径别名展开:先把
~/、相对路径等各种形式统一成绝对路径,防止路径别名绕过后续的权限规则。 - 权限检查:看用户的规则里这个路径是否允许写入。
- 必须先读过文件:如果这个文件从来没被 Read 工具读取过,编辑直接被拒。这是"先看再改"的硬性要求,确保模型不会凭想象修改一个它没看过的文件。
- 文件状态检查:如果文件在上次读取之后被外部程序修改了,工具会拒绝覆盖,而不是强行写入。这保护了用户在另一个窗口里做的改动。
- 大文件保护:超过 1GB 的文件直接拒绝,防止内存问题。
这些检查都在写入之前做完。如果任何一项不通过,操作失败,原文件不变。
Diff 是信任的基础
每次成功的编辑,diff 都会出现在消息流里。用户不需要自己去比对,也不需要运行 git diff,直接在会话界面就能看到改了哪几行。
这个设计不只是方便,而是系统可信度的基础。你不需要相信"agent 说它改对了"——你可以直接看改了什么。如果改得不对,当场就能发现,而不是等到跑测试才知道。
改完文件之后,如果代码有类型错误或语法问题,这些报错也会一起出现在消息流里,不需要再切到编辑器里手动检查。
第 6 章 Shell 执行
Shell 是最强也是最危险的能力
给 agent 开放 shell,它就能做几乎任何事:跑测试、安装依赖、启动服务、部署代码。但同样,rm -rf、curl | bash、访问生产数据库也都可以做。
Claude Code 没有因为这种风险就限制 shell 能力,而是把 shell 做成了一个正式的工具,带权限、带语义识别、带任务管理。
命令语义识别
BashTool 在执行之前会先分析命令的语义:这是只读命令还是有副作用的命令?
grep、find、cat、ls 这类命令被识别为只读操作,可以直接执行,结果在界面上也会自动折叠,不撑开会话流。
rm、mv、写文件、启动服务这类命令被识别为有副作用,会触发权限检查,可能需要用户确认。
这个分类不是完美的,但它让权限系统可以对不同类型的命令有不同的默认策略,而不是一刀切。
长任务怎么处理
有些命令跑完要几分钟甚至更长。如果同步等待,整个会话就卡在那里了。
Claude Code 的处理方式是把 shell 命令纳入任务体系。每个长任务都有一个 task id,输出写到专门的文件里。你可以:
- 让它在后台跑,继续做别的事
- 用
attach重新连上去看实时输出 - 用
kill终止它
默认超时是 2 分钟,最长可以给到 10 分钟。如果一条命令在 2 秒内没有产生任何输出,系统会考虑自动把它放到后台,不需要你手动操作。这样 shell 执行就从"等待返回"变成了"提交任务、查看进度",适合真正的长时间操作。
Sandbox 是另一层保护
除了权限规则,还有 sandbox 机制:某些命令可以在容器里执行,和主机环境隔离。
Sandbox 不是对所有命令都启用的,因为有性能成本。什么时候启用、启用哪种级别的隔离,取决于命令类型和当前的权限模式。权限规则和 sandbox 是两个独立的机制,可以组合使用:权限规则决定"这条命令允不允许运行",sandbox 决定"运行时有没有隔离"。
为什么不直接让模型自己组装 shell 命令
理论上可以:模型直接生成 shell 命令,通过 exec 执行,结果以文本返回。简单直接。
但这样做,系统对 shell 执行这件事完全没有感知。没有权限检查,没有任务管理,没有展示逻辑,没有长任务处理。出了问题,什么都捞不到。
把 shell 做成正式工具的代价是多了一层抽象,好处是 shell 执行变得可控、可见、可管理——这在 coding agent 里是刚需,不是可选功能。
第 7 章 Plan Mode
"先想清楚再动手"怎么变成运行时约束
做复杂任务之前先写一个计划,这个道理谁都懂。但如果只是在 prompt 里说"先规划再执行",实际上没有任何保证——模型随时可能边想边做,在你还没看清楚的时候就开始改代码了。
Plan mode 把这个约定变成了系统强制执行的状态。进入 plan mode 之后,写操作工具的权限上下文发生变化,模型想调用 FileEditTool 或者跑有副作用的 shell 命令,都会被拦住。这不是靠提示词约束,而是运行时约束。
进入和退出
/plan 切换到 plan mode。这不只是改了一个 UI 状态,而是写进了权限上下文,影响后续所有工具的执行策略。
在 plan mode 里,模型可以读文件、搜索、思考、生成计划内容,但不能写文件、不能跑有副作用的命令。计划内容存在磁盘上的一个文件里,不是临时的内存变量——这意味着你可以用 /plan open 在外部编辑器里直接修改这个文件,手工调整计划,然后让模型按调整后的计划执行。
确认计划没问题之后,退出 plan mode,模型重新获得写操作权限,进入执行阶段。你可以随时再次进入 plan mode 暂停,或者让整个执行过程跑完。
为什么是显式模式切换,而不是自动判断
另一种设计是系统自动判断当前是在规划还是在执行,用户不需要手动切换。看起来更智能,但实际上把控制权从用户手里拿走了——你不确定系统现在是什么状态,也不确定它什么时候会开始真正改东西。对于风险较高的操作,这种不确定感很不舒服。
显式模式切换的好处是状态对用户完全透明。你知道现在是规划状态,你决定什么时候进入执行状态。计划写到磁盘上,你可以审阅和修改,不是只能接受模型给出的方案。这是 Claude Code 在自动化程度和用户控制感之间的一个典型取舍。
第 8 章 Permissions 与 Approval
这个功能解决什么问题
给 AI agent 开放系统操作权限,本质上是一个信任问题。信任得太少,agent 没法干活;信任得太多,出了事没有后悔药。
Claude Code 面对这个问题的答案是:不做全局信任,做细粒度规则。
最直观的体验
用 Claude Code 改代码,有时会弹出一个确认框:"准备写入 /src/config.ts,是否允许?"
你可以:
- 点允许,这次通过
- 点允许,并勾选"以后 /src/ 下的文件都自动允许"
- 点拒绝,Claude Code 会记录这次拒绝,但任务不会整个中断
这个确认框就是 Approval,而背后决定"要不要弹出这个框"的逻辑就是权限系统。
权限是怎么工作的
每个工具在执行之前都会先问自己一个问题:我现在要做的这件事,用户允许吗?
这个问题的答案来自规则匹配,有三种结果:
- allow:直接执行,不打扰用户
- deny:直接拒绝,不执行
- ask:弹出确认框,等用户决定
规则按 deny > allow > ask 优先级匹配,第一条命中的生效。如果一条规则都没匹配上,默认进入 ask。
规则是用户自己写的(或者通过确认框积累下来的),长这样:
allow:Bash(git *:*) # git 命令全部放行
allow:FileEdit(/src/**:*) # src 目录下的文件可以随便改
deny:Bash(rm -rf *:*) # 这条命令直接拒绝
ask:WebFetch(*.external.com) # 外部网址每次都要问
为什么不做成一个总开关
最简单的设计是:要么"完全信任模式"(什么都不问),要么"严格模式"(每步都确认)。
但这两种都不好用:
- 完全信任:你不知道 agent 在干什么,改错了才发现
- 每步确认:做一件事要点几十次确认,根本没法用
Claude Code 选择的路是逐步积累授权。第一次做某件事问你,你点允许并选择"记住规则",下次同类操作就不再问了。用着用着,你的规则表就变成了一份个人化的"信任配置",反映你真实的风险偏好,而不是一刀切的全开或全关。
为什么权限判断在每个工具里,而不是一个统一的地方
读文件、写文件、跑 shell 命令、访问网址——这四件事的风险完全不同,需要判断的信息也不同:
- 写文件看的是路径(是不是 /etc/?是不是生产配置?)
- 跑命令看的是命令内容(是搜索还是删除?)
- 访问网址看的是域名(是可信站点还是随机 URL?)
如果放在一个统一的地方判断,要么判断逻辑过于复杂,要么精度不够。放在每个工具里,每个工具按自己的业务语义做判断,更准确,也更容易维护。
拒绝之后发生什么
权限被拒绝的时候,界面上能看到"这一步被拒了,原因是什么",不是悄悄失败。你可以修改规则之后重试,不用从头再来,整个任务也不会因为一次拒绝就中断。
拒绝不是终点,而是一个可以继续调整的节点。
类似的细粒度也体现在 Shell 命令上:find -exec 会被拦住但 find -name 不会,sed -i 被单独标记为有副作用。格式化工具执行完后,系统还会主动刷新编辑工具的文件缓存。甚至 Windows 路径攻击(UNC 路径触发凭证泄露)也在防御范围内。
权限系统的边界
权限系统能做的是:控制 Claude Code 本身发起的操作。
它管不了的是:你自己写的规则有没有漏洞,或者 MCP server 在它自己进程里做了什么。权限规则是针对 Claude Code 工具调用的过滤层,不是沙盒隔离。
如果你需要更强的隔离,sandbox(通过容器隔离执行环境)是另一层机制,和权限规则独立工作。
第 9 章 Task、Agent、Teammate
Task 是什么
当 Claude Code 需要执行一个超过几秒的操作——跑一个 shell 命令、启动一个子 agent、连接一个远程任务——它不会直接执行然后等结果,而是创建一个 task。
Task 是 Claude Code 对"长时间执行"的统一抽象。每个 task 有唯一 id、类型、状态,输出写到磁盘上的专用文件里。状态从 pending 到 running,最终到 completed、failed 或 killed。
这个抽象的存在让前台 UI 和后台执行解耦:UI 只读 task 状态和输出文件,不关心底层是 bash 还是 agent 还是远程进程在跑。
几种 task 类型
local_bash:本地 shell 命令,最简单,跑完就结束。local_agent:在本地启动的子 agent,有自己的上下文,可以独立读文件、改文件、跑命令,完成后把结果汇报给主 agent。这是 Claude Code 实现并行工作的基础——多个子 agent 同时处理不同部分,主 agent 汇总结果。remote_agent:在远程环境启动的 agent,通过 bridge 通信。适合在 CI/CD 环境或者和主机隔离的容器里执行任务。in_process_teammate:在同一个进程里运行的并行 agent。和local_agent的关键区别是:它有自己的消息循环,可以通过SendMessage和主 agent 实时通信,而不只是在完成后返回结果。适合需要持续协作的场景,代价是隔离程度更低。
Agent 能干什么
AgentTool 是 Claude Code 的"让另一个 AI 来做这件事"的工具。
调用时可以选择同步(等 agent 完成再继续)或者异步(提交任务,拿到 task id,继续干别的)。可以给 agent 分配一个独立的 git worktree,让它在自己的分支上工作,不影响主工作区。
子 agent 的能力和主 agent 是一样的,可以读写文件、跑命令,有自己的权限上下文。但它是一个独立的执行单元,不共享主 agent 的对话历史,只接受任务描述和必要的上下文。
为什么要有这一层抽象
没有 task 抽象的话,shell 命令、子 agent、远程任务都得各自管理自己的生命周期,各自实现进度显示、错误处理、输出收集。代码会很乱,用户体验也会碎片化。
有了统一的 task 层:
- 所有长时间操作都可以 attach、kill、查看 logs
- UI 只需要一套组件来显示任务状态,不管底层是什么
- 执行者可以替换(比如从 local_agent 换成 remote_agent),UI 不用改
还有一个自动行为:当一个会话里完成了多个 task 之后,系统会自动启动一个验证 agent 回头检查工作质量,不需要用户主动触发。
多了一层抽象,多了复杂度,但换来的是整个系统的可管理性。
第 10 章 Memory 功能
Memory 要解决的问题
每次开启新会话,Claude Code 对上次发生的事情一无所知。这对短任务问题不大,但对长期使用的人来说很麻烦——你得反复解释项目背景、重申偏好、重新介绍约定。
Memory 的目的是让这些信息持久化,下次用的时候不用再说一遍。
Memory 是一些 Markdown 文件
Claude Code 的 memory 没有数据库,没有向量检索,就是几个 markdown 文件。你可以直接用 /memory 打开来编辑,加、删、改都行。
这个设计的好处是透明:你能看到 memory 里存了什么,能直接控制它,不是一个不透明的黑箱在背后记住一些东西。
memory 文件有五个层级:
- 个人 memory:
~/.claude/CLAUDE.md,只对你自己生效,跨所有项目 - 项目共享 memory:项目根目录的
CLAUDE.md,提交到仓库,团队可见 - 项目个人 memory:项目根目录的
CLAUDE.local.md,不提交,只对你自己在这个项目里生效 - 企业 memory:通过管理平台下发的
CLAUDE.md,组织级别的统一约束 - team memory:通过 API 同步,团队所有成员共享(需要启用对应 feature)
系统启动时会把这些文件加载进上下文,所以 agent 能"知道"这些信息。
后台自动提取
除了手工编辑,会话结束后系统会自动做一次 memory 提取——把这次会话里值得长期保留的内容写入 memory 文件。
提取不是每轮都发生,而是有触发条件:会话累计消耗超过 10,000 tokens 之后才开始考虑,此后每增加 5,000 tokens 且至少发生 3 次工具调用就触发一次。这两个条件组合在一起,估计的是"这次会话发生了足够多的事情,值得提取"。
提取本身是由一个 forked subagent 在后台做的,不阻塞主对话。提取出来的内容会被整理进一个固定的模板结构——从 Session Title 到 Files and Functions 到 Worklog,一共 10 个段落,总预算 12,000 tokens。这个模板决定了"记什么、怎么组织",也解释了为什么自动提取的 memory 风格总是很统一。
为什么提取用 subagent,而不是简单地截取文本
直接把对话的最后几轮截下来存进 memory,实现最简单,但效果差。
有价值的信息往往散落在整个会话里:第 5 轮发现了一个重要约定,第 15 轮确认了一个技术决策,第 28 轮遇到了一个坑。简单截取只能拿到最近的内容,拿不到这些散落的关键点。
用 subagent 做提取的好处是:它能读完整个会话历史,判断什么值得保留、以什么形式保留,而不只是截取最近几轮。提取结果更准确,代价是比简单截取更耗资源。
Memory 不是万能的
Memory 文件会被加载进每次会话的上下文,但这也意味着它占用 token。如果 memory 文件很大、包含大量不相关的历史记录,反而会稀释上下文,让 agent 在噪声里找有用信息。
保持 memory 文件精炼、定期清理不再相关的内容,是让 memory 真正好用的关键。系统已经开始在这方面做尝试——有一个叫 auto-dream 的后台机制,会自动修剪过期的 memory 和合并重复条目——但目前还在早期阶段,大部分维护工作仍然得靠你自己判断。
第 11 章 Session、Resume、History
三个相关但不同的概念
Claude Code 有三种方式处理"过去发生的事",用途各不相同:
- Input History:你之前输入过的内容。上下方向键可以翻,跨会话也能用。就像 shell 的历史记录,只存输入,不存对话内容。
- Session Resume:恢复之前的一次完整对话,包括所有消息、工具调用、上下文。用
/resume或者启动时选择历史线程。 - Remote History:在 remote viewer 场景里,往上滚可以看到更早的消息,按需加载,不是一次性全部拉取。
为什么不把所有历史都放在上下文里
一个朴素的想法是:把整个对话历史都放在当前上下文里,这样 agent 什么都"记得"。
这在短会话里没问题。但长会话积累的历史很快就会超出模型上下文窗口的限制,而且把几百轮历史全塞进去,大部分都是不相关的噪声,反而让 agent 更难找到真正有用的信息。
Claude Code 的处理方式是分层存储:最近的消息在上下文里,更老的消息在磁盘上,需要的时候按需加载。Session Resume 是把一个旧的上下文重新激活,不是把所有历史合并进来。
远程历史的懒加载
在有远程会话的场景里,消息历史通过 API 分页加载。初次打开拉取最新的一页,往上滚到一定位置时再拉更老的一页,以此类推。
这里有一个 UX 细节值得注意:加载更老的消息时,新内容是往上插入的,但界面不应该因此跳动,让用户突然不知道自己在看哪里了。这通过 scroll anchor 机制解决——插入前记住当前可见消息的位置,插入后恢复到相同位置。
边界状态(正在加载、加载失败、到了最开头)用特殊的 sentinel message 表示,这样 UI 逻辑不需要单独维护这些状态,直接跟着消息流走。
第 12 章 Context 管理
上下文窗口是有限资源
模型能"看到"的内容有上限。对话历史、读进来的文件、工具调用结果、memory 文件、skill 定义——这些都在消耗这个上限。用完了,就没法继续了。
Claude Code 把这个资源的管理做成了显式功能,而不是让用户自己猜"为什么它突然忘了之前说的话"。
你能看到上下文花在哪里
/context 会显示当前上下文的使用分布:多少在对话历史里,多少在 memory 文件里,多少在 MCP tool 定义里,多少在 skill 里,还有多少剩余。
这个可视化的价值不是帮你优化 token 用量,而是让系统行为变透明。如果 agent 好像没注意到某件事,你可以看看那件事是不是已经被压缩掉了;如果上下文快满了,你知道该做什么,而不是等到突然报错。
Compact 是怎么工作的
Compact 有两种触发方式:
- 手动:
/compact立即触发,你可以在觉得上下文太乱的时候主动清理。 - 自动:当上下文 token 数接近有效窗口上限时(大约在上限前 13,000 tokens 的位置),系统自动触发一次 compact,然后继续对话。你会在消息流里看到一条提示,说明刚才发生了压缩——不是悄悄进行的。如果不想要自动 compact,可以在设置里关掉。如果 compact 连续失败 3 次,系统也会自己停下来,不再反复尝试。
压缩的逻辑不是随机丢弃,而是有优先级的:
- 最近几轮对话完整保留,不压缩
- 工具调用结果保留摘要,原始输出可以丢
- 被后续对话引用的内容保留,没有被引用的可以丢
- 纯文本的中间回复,多轮合并成摘要
被压缩的内容会打上标记,界面上能看到"这里有内容被摘要了",不是悄悄消失。
Context Collapse
Context collapse 比 compact 更激进:把多轮内容(包括工具调用链)折叠成一个 summary block。适合长任务的阶段切换点——当前阶段完成了,折叠历史,清空空间,新阶段开始时只保留一个简短的"之前做了什么"的摘要。
Context collapse 没有用户命令,完全自动触发:系统在每次 API 调用前判断是否需要折叠,也会在遇到上下文溢出错误时自动触发。Collapse 和 auto-compact 是独立的机制,可以同时运行——前者专门压缩 Read/Search 工具的输出,后者摘要整体对话历史,各管各的。手动 /compact 不受影响,仍然可用。
为什么压缩要可见
Compact 可以做成完全静默的——自动触发,不通知用户,对话继续。很多系统就是这样做的。
问题是:你不知道压缩发生了,也不知道什么被丢掉了。如果因此出了问题——agent 忘了一个重要约束,或者重复做了已经做过的事——你很难判断是 agent 的问题还是上下文压缩导致的问题。
Claude Code 选择让压缩可见:自动触发时会在消息流里留下记录,被压缩的内容会打上标记,/context 能看到上下文花在哪里。代价是多了一些需要了解的概念,好处是出了问题你知道从哪里查。
第 13 章 Web 访问与搜索
Agent 怎么获取外部信息
写代码经常需要查资料:看文档、找示例、确认某个 API 的参数。Claude Code 有两个内建工具处理这件事:WebFetchTool(拉取指定 URL 的内容)和 WebSearchTool(先搜索再拉取)。
两个工具解决的是不同的问题。WebFetchTool 适合你已经知道要看哪个页面的情况:提供 URL,拉取内容。WebSearchTool 则是你知道要找什么但不知道从哪里找:先搜索得到一批候选结果,再从中拉取最相关的内容。
这两个工具是内建的,受同一套权限和消息机制管控,不是让 agent 自己写 curl 命令去联网。不过两者的实现方式不同:WebFetchTool 在本地抓取页面,WebSearchTool 则是服务端工具——搜索请求发给 Anthropic 的 API 完成,CLI 只负责展示结果。
为什么要有 prompt 参数
调用这两个工具时都需要提供 prompt——"你想从页面里得到什么"。
这个设计有点反直觉:直接拿页面内容不就好了,为什么还要说明目的?
原因是上下文成本。一个完整的文档页面可能有几万 token,但你实际需要的可能只是其中一个函数的参数说明。有了 prompt,工具会用一次额外的模型调用对页面内容做过滤,只返回和 prompt 相关的部分,而不是把整个页面塞进上下文。即使不过滤,内容也会被截断到 10 万字符以内。
域名级权限控制
和所有其他工具一样,Web 工具在执行前也过权限系统。但和文件路径、shell 命令不同,Web 权限是按域名控制的。
实际上有 90 多个域名是预授权的——主要是各语言和框架的官方文档站,像 docs.python.org、react.dev、docs.aws.amazon.com 这些。访问这些站点不需要确认。其他域名要么每次确认,要么直接拒绝。
被拦截时,系统会给出快速规则建议,一键添加白名单,不用自己手写规则。
这两个工具不处理认证
WebFetchTool 和 WebSearchTool 都不会帮你登录、不会携带 session cookie、不会处理 OAuth。遇到需要认证的 URL,会直接失败,并提示应该用专门的 MCP tool 来处理。
这是一个清晰的边界划分:这两个工具适合抓公开的网页内容;需要认证的外部系统,应该通过 MCP Server 接入,认证逻辑放在 MCP Server 侧。
第 14 章 MCP
为什么需要一个扩展协议
Claude Code 内建的工具覆盖了文件、shell、web——这已经很强了,但总有它覆盖不到的地方:你的数据库、你们公司的内部 API、特定的 SaaS 工具。
一种解法是:把这些都硬编码进 Claude Code。但这显然不现实,外部系统千变万化,不可能全部内建。
MCP(Model Context Protocol)是另一种解法:定义一套协议,让外部系统自己实现这套协议,然后在运行时接入。Claude Code 只需要理解协议,不需要提前认识每个具体的系统。
MCP 工具和内建工具有什么不同
内建工具在编译时就确定了——代码里写死了有哪些工具、每个工具能做什么。WebFetchTool 就是典型的内建工具:功能固定,适合抓公开网页,但没有认证能力。
MCP 工具是运行时动态出现的。你连接一个 MCP server,Claude Code 把它声明的能力注册进来,之后就可以调用了。断开连接,这些能力就消失了。需要认证的系统——数据库、内部 API、需要登录的 SaaS——走 MCP 更合适:认证逻辑放在你的 MCP server 里,Claude Code 这边只看到一个普通的 tool 调用。
实现上,MCPTool 是一个模板——名字、描述、参数 schema、执行逻辑,都在连接时被 server 返回的信息覆盖。同一个模板,连接不同的 MCP server,就变成不同的工具。
Tool、Resource、Command 三种能力
说"MCP server 提供工具"其实只说了一半。协议定义了三种能力,区别在于谁来触发、发生在什么阶段:
- Tool:模型决定调用。任务执行过程中,模型判断需要做某件事,主动调用 tool,拿到结果再继续。比如查询数据库、发送消息。有参数、有返回值、可能有副作用。
- Resource:模型主动读取,用来获取背景信息。不产生副作用,更像一个可以按需读取的文件——比如数据库的 schema、项目文档的目录。风险比 tool 低,权限策略也可以更宽松。
- Command:用户触发,不是模型自己决定的。MCP server 暴露的 command 会直接并入 Claude Code 的命令注册表,用户可以像用内建的
/xxx一样调用它。
写 MCP Server 的几个关键点
认证在 server 侧处理。MCP server 是你自己控制的进程,把认证逻辑放在里面。Claude Code 这边不需要知道你的 API key 或者 OAuth token,它只看到工具调用和返回结果。
Tool schema 要精确。MCP 工具的参数 schema 最终决定模型怎么调用这个工具。太宽松(比如只有一个 query: string),模型会猜参数格式,结果不稳定。每个字段都应该有明确的类型和描述。
区分 tool 和 resource。能用 resource 表达的数据就不要做成 tool。resource 是只读的、无副作用的,权限系统可以给它更宽松的默认策略;tool 有副作用,应该触发确认。把 schema 查询做成 resource 而不是 tool,是一个常见的好实践。
测试权限边界。Claude Code 会对 MCP tool 调用做权限判断。测试时不能只测"正常调用成功",要测"调用被拒绝时 MCP server 怎么响应",以及"server 返回错误时 Claude Code 怎么展示给用户"。
第 15 章 Plugins 与 Skills
一个共同机制,两种扩展方式
Plugins 和 skills 是 Claude Code 的高层扩展方式,用来做"工作流复用"和"自定义 slash command",不是接入外部系统(那是 MCP 的事)。
两者的底层机制相同:写一个 markdown 文件,系统启动时扫描并编译成命令对象,并入命令注册表。用户用起来和内建命令一样,感知不到区别。
Skill 是什么
一个 skill 就是一个 SKILL.md 文件,加上可选的辅助文件(脚本、模板、参考资料)。
文件头部的 frontmatter 控制这个 skill 的行为:
whenToUse:告诉系统什么情况下应该用这个 skillallowedTools:限制 skill 只能调用哪些工具model、effort:指定用什么模型、花多大力气context: fork:在独立的 subagent 里执行,不污染主上下文
skill 加载时会被估算 token,纳入上下文预算——默认大约占上下文窗口的 1%。如果 skill 太多或者描述太长,系统会先按比例截短,实在放不下就只保留名称。所以 skill 文件不是越详细越好,精炼的描述反而能让更多 skill 共存。
skill 适合封装"Claude Code 自己能完成的工作流",比如"按这套标准审查 PR"、"用这个格式生成报告"。如果要接入 Claude Code 本身没有能力触达的外部系统(比如数据库、内部 API),那是 MCP 的事,不是 skill 能做的。
Plugin 是什么
Plugin 是 skill 的打包形式。一个 plugin 可以包含多个 skill 和命令,安装之后统一以 plugin 名称作为命令前缀,比如 /review:start、/review:summary。
和单独的 skill 相比,plugin 多了一个能力:携带 hooks。hooks 是绑定到系统事件的自动触发脚本,不需要用户主动调用。比如每次写文件之前自动跑一次 lint 检查,或者每次会话开始时自动加载某个配置。安装 plugin 之后,这些行为就静默地生效了。
可以绑定的事件远不只是"工具调用前后"。系统支持 28 种 hook 事件,覆盖了从会话启动、权限请求、子 agent 启动、task 完成、配置变更、文件变化到 worktree 创建等几乎所有值得拦截的时刻。这意味着 plugin 可以对系统行为做非常细粒度的定制,而不只是在工具调用前后加一段逻辑。
为什么扩展内容也要遵守系统规则
Skill 的 allowedTools 限制工具调用,skill 的 token 估算纳入预算,plugin 的 hooks 走统一的 hook 机制——这些不是限制扩展能力,而是让扩展内容能被系统治理。
如果 skill 可以绕过权限检查,一个写得粗糙的第三方 skill 就能在你不知情的情况下做任何事。把扩展纳入同一套规则,是扩展生态能健康运作的前提。
写 Skill 的几个关键点
whenToUse 写清楚。这个字段告诉系统什么情况下应该用这个 skill,写得模糊的话,系统要么永远用、要么永远不用。要具体描述触发场景,比如"当用户要审查 PR 时",而不是"代码相关任务"。
控制 token 体积。Skill 文件加载进上下文,如果很大,每次会话的上下文预算都会受影响。只放必要的指导,参考资料用单独的 references 文件,不要全塞进 SKILL.md。
allowedTools 不要留空。不限制 allowed tools 的 skill 可以调用任何工具,包括你没想到的。明确列出这个 skill 需要的工具,既是安全考虑,也让别人一眼看清楚它的能力边界。
考虑用 context: fork。如果 skill 执行的任务比较长、会产生大量中间输出,用 fork 让它在独立的 subagent 里执行,主对话的上下文不会被污染。
第 16 章 Config 与 Feature Flags
不只是参数配置
Claude Code 的配置系统做两件事:控制运行时参数(超时时间、模型选择、各种开关),以及通过 feature flag 控制哪些功能对哪些用户可见。后者让同一份代码可以对外表现出不同的产品形态——外部公开版、内部开发版、实验功能版、组织定制版,功能集各不相同,但底层都是同一套代码。
Feature Flag 怎么工作
Feature flag 的判断发生在命令注册、工具初始化的时候,不是运行时的 if/else。
Flag 为 false 时,模块根本不加载,命令不出现在注册表里,Tab 补全里也看不到。工具层同理——某些工具的存在本身,受 feature flag 控制。
这种设计有一个好处:不同构建版本的包体积可以差异很大。外部版不会携带任何内部功能的代码,而不只是把它们隐藏起来。
配置的层级
配置有五个层级,从高到低:企业策略(通过管理平台下发,优先级最高)、用户级(~/.claude/settings.json)、项目共享级(.claude/settings.json,提交到仓库)、项目本地级(.claude/settings.local.json,不提交)、以及服务端 feature flag。
高层级可以覆盖低层级。企业策略还可以锁定某些配置项,让用户无法修改——比如强制禁用某些工具、限制只能连接指定组织。这对企业部署很重要:组织可以强制安全策略,同时让用户保留个性化配置的空间。
一份代码,多条产品线
如果没有 feature flag,维护多个产品版本的常见做法是拉多个 fork——外部版一个、内部版一个、实验版一个,各自维护。问题是每次改一个核心功能,所有 fork 都要同步,成本越来越高。
Feature flag 的方案是:只有一份代码,差异全部做成开关,主干统一迭代,不同产品线通过配置区分。代价是代码里会散落着各种条件判断,理解某个功能的完整行为需要知道在哪些 flag 组合下它是开着的。但对于需要同时维护多个产品版本的系统来说,这个复杂度是值得的。
从源码里能印证这一点:大约 100 个 feature flag,还有 19 个不出现在 /help 里的隐藏命令——/ultraplan、/teleport、/autofix-pr、/buddy 等,都是正在演化中的产品线留下的痕迹。
第 17 章 Remote、Bridge、Daemon、Bg Sessions
Claude Code 不只是一个对话窗口
大多数时候你用 Claude Code 的方式是:打开终端,开始对话,关掉终端,结束。
但 Claude Code 还支持几种不同的运行模式:
- Bg Sessions(后台会话):任务放到后台运行,关掉终端也不会停。可以随时用
attach重新连上查看进度,用logs看历史输出,用kill终止。适合耗时较长的任务。 - Bridge:让另一个程序通过标准协议控制 Claude Code。VS Code 插件就是走这条路和 Claude Code 通信的。Bridge 模式有独立的权限回调机制,IDE 可以拦截权限确认并自行处理。
- Daemon:一个持续运行的后台进程,负责管理多个 Claude Code 会话的生命周期。适合需要同时跑多个会话、或者需要跨会话保持状态的场景。
- Remote Control:从远端向本地的 Claude Code 实例发送指令,适合嵌入 CI/CD 流程或自动化脚本。
为什么这些模式在入口层分流
这些运行模式有一个共同点:它们的初始化要求和普通交互模式不同。
Daemon 需要比普通模式更早加载某些配置。Bridge 需要注册特殊的权限回调。后台会话需要把输出重定向到文件,不往 terminal 写。
如果把这些都当成普通命令处理,初始化过程里就会充满"如果是 bridge 模式就做这个,如果是 daemon 就做那个"的判断,代码会很乱。
入口层分流让每种模式都走自己的初始化路径,互不干扰。普通对话模式不会加载 bridge 相关的代码,bridge 模式不会走普通对话的初始化流程。
这些模式和 task 体系的关系
后台会话、remote agent 这些都和第 9 章讲的 task 体系相关。remote_agent 类型的 task 就是在远程环境里运行的 agent,通过 bridge 或 remote 机制通信。
从用户角度看,管理一个后台 session 和管理一个后台 task 体验类似:都可以 attach、kill、查看 logs。这是因为底层用的是同一套机制。
这里面没有传统的 Unix daemon——bridge loop 本身就是持久进程,通过一个指针文件让其他进程发现活跃会话,绕开了经典 daemon 模式的复杂性。
结语:设计哲学与产品原则
读完 17 章,可以回过头来问一个问题:Claude Code 是一个什么样的系统?
三个反复出现的选择
把能力做成工具,而不是让模型自由发挥。
读文件可以跑 cat,改文件可以跑 sed,联网可以跑 curl。Claude Code 每次都选择了另一条路:做成专门的工具,带权限、带展示协议、带审计。系统变重了,但每个动作都在感知范围内。
让用户保持控制感,而不是追求最大自动化。
Plan mode 要显式切换,权限要逐步积累,context 压缩要可见。每一处设计都在抵抗"让系统完全自主"的诱惑。当 agent 能力足够强的时候,用户最需要的不是更多自动化,而是在关键节点的控制权。
扩展能力必须遵守系统规则。
MCP server 暴露的工具走同一套权限判断,skill 的 token 纳入预算,plugin 的 hooks 走统一机制。没有"高级用户可以绕过"的后门。
这三个选择是从使用体验就能观察到的。但打开源码之后,会发现这些选择背后有一套更明确的原则体系。
写在 Prompt 里的原则
Claude Code 的 system prompt、安全分类器和 agent 指令里,嵌入了一组设计原则。它们不直接决定某个功能长什么样,但塑造了整个系统的行为边界。
"Never delegate understanding."
这是 agent 委托子任务指南里的核心原则。把任务分出去可以,但不能把"理解"也一起分出去。如果主 agent 没有消化子 agent 返回的信息就直接让下一个 agent 执行,那中间就缺少了做判断的环节。配套的建议是:查找型任务给命令,探索型任务给问题——"prescribed steps become dead weight when the premise is wrong",预设步骤在前提错误时只会成为包袱。
"Questions are not consent." / "Silence is not consent."
安全分类器里关于用户意图的两条基本原则。用户问"我们能不能修一下这个?"不等于授权修复;用户在两次操作之间没有干预,不等于默许。这两条打掉了 agent 设计里最常见的假设。
由此衍生出一个不对称的安全策略:授权需要高证据门槛,因为误判的代价是危险操作;限制只需要低门槛,因为误判的代价只是暂停。宁可多停一下,也不要多做一步。
"Your job is not to confirm the work. Your job is to break it."
验证 agent 的系统指令开头。紧接着是一段坦诚的自我认知:"You see the first 80% — polished UI, passing tests — and feel inclined to pass. The first 80% is on-distribution, the easy part. Your entire value is the last 20%." 以及 "Volume of output is not evidence of correctness."——输出多不代表做对了。
这段话把 AI 的系统性缺陷写进了给 AI 自己看的指令里,用自我意识对抗系统性偏见。
安全模型看的不只是当下。
安全分类器里有一个叫 memory poisoning 的概念:通过向记忆文件写入精心构造的内容,影响未来会话的行为。当前会话没有直接危害,但它在记忆里埋了一颗种子,未来的会话读到这些内容后会据此行动。这意味着安全模型不能只判断"这个操作现在有没有害",还要判断"这个操作在未来可能产生什么连锁效应"。
产品设计者的选择
除了原则,源码里还能看到一些具体的产品决策,体现了这些原则怎么落地。
角色越窄,失控风险越低。
Claude Code 没有让一个通用 agent 包打天下,而是设计了多种专用角色。探索型 agent 被明确禁止一切写操作——不能创建文件、不能修改文件、不能用重定向符号。这不是因为 Claude 会主动捣乱,而是角色边界越清晰,任务偏离轨道的概率越低。
记忆整合借鉴了"睡眠巩固"。
第 10 章提到了 auto-dream 机制。它的设计灵感来自人类睡眠时的记忆巩固过程——不是实时整理,而是在条件满足时(足够时间 + 足够新会话)批量处理。整合分四个阶段:先读取现有记忆避免重复(Orient),再只读近期会话找重要信号(Gather),然后合并、去重、修正矛盾(Consolidate),最后更新索引保持精简(Prune)。这个流程解决的核心问题不是"存什么",而是"什么时候整合"和"怎么防止膨胀"。
专家 checklist 可以产品化。
内置技能 /simplify 同时启动三个独立 agent 审查同一份代码变更:一个找复用机会,一个查代码质量,一个看执行效率。三个 agent 完成后主 agent 汇总并直接修复,false positive 跳过不讨论。这是把"人工审查 checklist"封装成 agent 工作流的典型例子。
可以推断的走向
从这些设计选择和源码里的痕迹,能看出几个方向。
多 agent 协作是主要投资方向。 Task 体系里的 local_agent、remote_agent、in_process_teammate,AgentTool 的 worktree 隔离,team memory 的 API 同步——这些不是在做单人工具,而是在做多 agent 的协作基础设施。现在还相对早期,但基础架构的空间已经预留好了。
MCP 会成为核心扩展路径。 内建工具覆盖了文件、shell、web,这个边界不会无限扩张。外部系统的接入将越来越多地通过 MCP 完成。Claude Code 的能力边界将主要取决于有多少质量好的 MCP server,而不是内建了多少工具。
上下文管理还在演化中。 Auto compact、context collapse、分层 memory、auto-dream——这些机制并存,说明"如何在有限的上下文窗口里完成长任务"仍然是一个没有完全解决的问题。
最后
Claude Code 是一个在"给 AI 更多自主权"和"让人类保持控制感"之间持续做取舍的系统。
它的很多设计选择,在短期内会让系统看起来更保守——需要确认、需要显式操作、需要遵守规则。但这些约束积累下来,形成的是一套用户可以真正信任的 agent 基础设施。
从源码里读到的那些原则——不要外包理解、提问不是授权、验证不是确认、宁可多停一下也不要多做一步——不只适用于 Claude Code。它们是这个阶段所有 agent 系统都需要面对的设计问题。
这可能是比"更聪明的模型"更难解决的问题。