Claude Code 解剖,对照自己的实现

outlier 14 分钟 5501 字 0 0
系列教程Agent从入门到放弃 第 33 / 34 章
  1. 01Agent 从入门到放弃:教程大纲
  2. 02①大模型、Chatbot、Agent,一次把名字理清楚
  3. 03②Agent 的最小定义,一个 while 循环
  4. 04③Agent 简史,从 ReAct 到 Claude Code
  5. 05④Workflow 还是 Agent?大部分场景你不需要 Agent
  6. 06Agent 能力地图,为什么编码 Agent 最先成熟
  7. 07Token,模型眼里的世界
  8. 08Next-token prediction,一个字一个字地猜
  9. 09采样,temperature、top-p 与"同题不同答"
  10. 10Transformer 与 Attention,不推公式的直觉版
  11. 11Context Window,模型的内存条
  12. 12训练三阶段,能力是怎么被"装"进去的
  13. 13推理模型,让模型先想再答
  14. 14用原理解释怪行为,原理篇结业考
  15. 15从 Prompt 工程到 Context 工程
  16. 16System Prompt 写作,角色、约束与示例
  17. 17Context Rot,为什么长对话越聊越笨
  18. 18项目记忆,CLAUDE.md 与 AGENTS.md
  19. 19检索,RAG 还是 Agentic Search
  20. 20Function Calling 协议拆解
  21. 21工具设计,写给模型用的 API
  22. 22MCP 入门,工具界的 USB-C
  23. 23用 Go 手写一个 MCP Server
  24. 24MCP 进阶与边界
  25. 25手写 Agent ①,调通 API,跑起多轮对话
  26. 26手写 Agent ②,第一个工具,read_file 与 list_files
  27. 27手写 Agent ③,让它会改代码,edit_file
  28. 28手写 Agent ④,给它一个终端,run_bash 与权限
  29. 29手写 Agent ⑤,上下文管理
  30. 30手写 Agent ⑥,流式输出、错误处理与重试
  31. 31手写 Agent ⑦,Subagent,给 Agent 加分身
  32. 32手写 Agent ⑧,Skills 与 MCP 接入
  33. 33Claude Code 解剖,对照自己的实现
  34. 34自动化,Hooks、自定义命令与 Headless 模式
查看教程目录

开场:处处都是熟面孔

写完 mini-agent 的那个周末,我重新打开了用了一年多的 Claude Code,想看看自己造的东西和成品差多远。

这一次的感觉完全不一样了,像是在看一台自己拆过一遍的机器。

以前我看它的工具列表,只觉得"哦,它能读文件、能改文件"。现在我看到它的编辑工具要求 old_string 在文件里唯一,会心一笑:第 26 篇那个唯一匹配。看到它改文件之前必须先读过,我想起了 checkFresh。看到它在上下文快满时自动压缩,我知道它背后多半有一份类似"任务目标、已完成、下一步"的摘要模板。看到它派出一个子 Agent 去调研,界面上缩进显示子 Agent 的工具调用,我想:这不就是 childUI 吗。

当然,也有很多我没做的东西:计划模式、待办清单、操作系统级的沙箱、更细的权限规则、IDE 集成……每一样,背后都是一个我在 mini-agent 里遇到过、却没来得及解决的问题。

这一篇,我们把 Claude Code 拆开,和 mini-agent 逐项对照;再看看 Codex 在沙箱和权限上做了哪些不同的取舍。最后你会发现,网上流传的那些"Claude Code 最佳实践",几乎都能从它的设计里自然地推出来。读完本篇,你应该能回答这几个问题:

  • 一个成品 coding agent,和我们手写的 mini-agent,哪些地方一样,哪些地方不一样?
  • 计划模式和待办清单是什么?它们为什么有用?
  • Codex 和 Claude Code 在安全设计上有什么不同的取舍?
  • 那些"最佳实践",背后的道理是什么?
说明:成品 Agent 迭代极快,下面讲到的具体工具名、命令、模式名称,都是截至写作时(2026 年 10 月)的情况,以官方文档为准。这一篇重点是设计思路,思路比名字稳定得多。

一、一张对照表

先看全景:

维度mini-agentClaude Code对应篇目
核心循环Agent.Send同样的循环02、24
读文件read_file:带行号、分段读带行号、分段读,还能读图片、PDF 等25
改文件edit_file:字符串替换、唯一匹配、改前必须读过同样的思路,另有专门写新文件的工具26
搜索用 run_bash 跑 grep专门的搜索工具(按内容、按文件名)18
终端run_bashBash 工具,可以选择套一层系统沙箱27
权限三种模式、白名单、黑名单、人工确认多种模式,加上可配置的允许 / 拒绝规则27
计划没有计划模式:先只读调研、出方案,批准后再动手—
待办没有待办清单工具—
上下文截断、预算、自动压缩、/compact、/reset自动压缩、手动压缩、清空、查看上下文构成28
子 Agenttask:全新上下文、只读工具、可并行子 Agent,可以自定义不同类型的子 Agent30
项目记忆读工作目录下的 AGENTS.md分层的 CLAUDE.md,可以引用其他文件17
扩展skills、MCPskills、MCP、hooks、自定义命令、插件31、33
自动化headless 模式 -pheadless 模式、SDK33、34
界面终端 REPL终端、IDE、桌面端、网页24

你会发现,表格上半部分几乎是一一对应的。这不是巧合:第 02 篇说过,所有 coding agent 剥到最里面,都是同一个循环;而循环里的那些零件,读、改、搜、跑、管上下文,面对的是同样的问题,自然会收敛到相似的答案。

差距主要在下半部分:计划、待办、沙箱、扩展机制、界面。下面挑几个最有意思的展开说。

二、工具集:为什么搜索要单独做成工具

mini-agent 搜代码,是让模型用 run_bash 跑 grep。Claude Code 则有专门的搜索工具:一个按内容搜(底层通常是 ripgrep),一个按文件名模式找文件。

为什么要单独做?这里面有好几层考虑:

  • 权限更细:搜索是只读的,可以永远免确认;而 run_bash 什么都能干,要按命令内容逐条判断。把搜索单独拎出来,权限系统就简单了。第 27 篇我们花了不少力气处理"grep 是白名单,但 grep ... > file 不是"这种情况,专门的工具天然没有这个问题;
  • 输出可控:专门的工具可以按相关性排序、限制返回的条数、自动跳过 .gitignore 里的文件,不会一次吐出几万行(第 28 篇);
  • 跨平台:不同系统上的 grep 参数不完全一样,专门的工具行为是一致的;
  • 模型更不容易用错:参数是结构化的(搜什么、在哪个目录、什么文件类型),而不是让它自己拼一条命令。

这是第 20 篇"按任务切分工具"的一个例子。 搜索是编码 Agent 最高频的动作之一,值得一个专门的工具。

同样的道理,Claude Code 的工具描述里会明确告诉模型:读文件用读文件的工具,不要用 cat;改文件用编辑工具,不要用 sed。mini-agent 的 run_bash 描述里也有这句话(第 27 篇)。专门的工具有专门的保护,绕过它们,就绕过了保护。

三、权限:几种模式,加上规则

3.1 模式

第 27 篇我们给 mini-agent 做了三种权限模式:default、accept-edits、yolo。Claude Code 的思路几乎一样,只是档位更多一些。截至写作时,大致有这么几类:

flowchart LR P["计划模式
只读,不许改"] --> D["默认
有副作用的要问"] D --> E["自动接受编辑
改文件不问,命令照问"] E --> B["跳过所有确认
只在沙箱里用"] style P fill:#e3f2fd,stroke:#1565c0 style B fill:#ffcdd2,stroke:#b71c1c

"计划模式"是 mini-agent 没有的那一档,下一节专门讲。

3.2 规则

除了模式,Claude Code 还能配置允许和拒绝的规则,比如"go test 开头的命令永远允许""任何对 .env 文件的读取都拒绝",规则可以写在项目的配置文件里,团队共享。

这相当于把 mini-agent 写死在代码里的白名单和黑名单(DefaultAllow、denyPatterns),变成了用户可以配置的东西。思路完全一样:常见的安全操作免确认,危险的操作永远拒绝,其余的问人。

四、计划模式:先想清楚,再动手

4.1 是什么

计划模式(plan mode)是这样工作的:

  1. 在这个模式下,Agent 只能用只读的工具:读文件、搜索、列目录,不能改文件、不能跑有副作用的命令;
  2. 它调研完之后,写出一份实施方案:要改哪些文件、每个文件改什么、按什么顺序、怎么验证;
  3. 方案交给你审。你可以修改、补充、否决;
  4. 你批准之后,它才退出计划模式,开始动手。
sequenceDiagram participant U as 你 participant A as Agent(计划模式) U->>A: 给订单加部分退款功能 Note over A: 只读:读代码、搜索 A-->>U: 方案:改 3 个文件……
先改 model,再改 service,最后加测试 U->>A: 第二步不对,退款金额要在 service 层校验 A-->>U: 修改后的方案 U->>A: 批准 Note over A: 退出计划模式,开始改代码

4.2 为什么有用

把前面几篇的原理串起来,计划模式有用的原因很清楚:

  • 第 07 篇:先推理后行动。 模型没有草稿纸,让它先把方案写出来,就是给了它一张草稿纸。直接动手的 Agent,往往是"边改边想",改到一半发现方向不对,又改回去;
  • 第 04 篇:能写死的就写死。 一份批准过的方案,相当于把一个开放式的 Agent 任务,变成了一个接近 workflow 的东西:步骤已经定了,Agent 只需要按步骤执行;
  • 人在最便宜的时候介入。 在方案阶段发现"方向错了",改一句话就行;在代码改了二十个文件之后才发现,代价就大得多了;
  • 只读保证了安全。 调研阶段它什么都改不了,你可以放心地让它到处看。

4.3 mini-agent 能不能做

能,而且不难。思路是:

  1. 在 permission.Mode 里加一种 ModePlan:只有 readOnlyTools 和白名单里的只读命令放行,其他一律拒绝,拒绝理由写"当前是计划模式,只能调研,不能修改";
  2. 在 system prompt 里加一段:计划模式下,调研完要输出一份方案;
  3. 加一个 /plan 命令进入、/go 命令退出。

这是一个很好的练习,留给你。

五、待办清单:把计划外化

5.1 是什么

Claude Code 有一个"待办清单"工具。在处理一个多步骤的任务时,Agent 会先调用它,写下一个清单:

☐ 读 order/model.go,了解订单结构
☐ 在 Order 里加一个 RefundedAmount 字段
☐ 在 service 里实现 PartialRefund
☐ 加测试
☐ 跑 go test ./...

每完成一步,它就再调用一次这个工具,把那一项勾掉,必要时增删条目。用户在界面上能实时看到进度。

5.2 为什么有用

这个工具"什么都不做":它不读文件,不改代码,只是把一个列表存下来,再原样显示出来。那它为什么有用?

答案在上下文里:

  • 第 16 篇:长任务里,模型会忘记目标。 一个列表写在上下文里,模型每一圈都能看到"还剩哪几步";
  • 第 10 篇:越靠近末尾的内容越容易被注意到。 每次更新清单,最新的版本就出现在消息历史的末尾附近,比十几圈之前用户说的那句话醒目得多;
  • 第 39 篇会讲:长程任务的一大难题是目标漂移。 一份不断被刷新的清单,是对抗漂移最简单的办法;
  • 对用户也有用:你能看到它打算做什么、做到哪了,跑偏的时候能第一时间发现。

它是把模型脑子里的计划,变成了上下文里的一段文字。 第 02 篇说过:Agent 里没有"规划模块",所谓计划,最终都要变成消息历史里的一段文本,才能被模型看见。待办清单就是这句话最直接的产品化。

mini-agent 也可以加一个:一个 todo 工具,参数是一个列表,Run 里什么都不用做,只要把列表格式化成文本返回,再在终端里显示出来。另一个练习。

六、上下文管理:和第 28 篇如出一辙

Claude Code 的上下文管理,和第 28 篇的思路几乎一样:

  • 自动压缩:上下文接近上限时,自动把历史总结成摘要;
  • 手动压缩:一个命令,还可以附带指引,比如"压缩时重点保留关于数据库迁移的讨论";
  • 清空:开始一个新任务;
  • 查看上下文的构成:system prompt、工具定义、记忆文件、MCP 工具、对话历史各占多少。第 14 篇的拆解实验,第 16 篇的动手作业,都是在用这个功能。

手动压缩时"附带指引"这个功能,mini-agent 没有,但加起来很容易:/compact 后面如果跟了一段文字,就把它拼进第 28 篇那段压缩 prompt 的末尾,比如"另外,务必保留关于数据库迁移的所有决定"。第 28 篇说过,自动压缩最大的问题是"你不知道它丢了什么";能附带指引,就等于让你提前告诉它"这个不能丢"。

还有一点值得一提:它的项目记忆文件 CLAUDE.md 是分层的(第 17 篇):用户级、项目级、子目录级,处理哪个目录就加载哪个目录的那一份。mini-agent 只读工作目录下的一份 AGENTS.md。

七、Codex:另一种安全取舍

7.1 沙箱优先

OpenAI 的 Codex 在很多方面和 Claude Code 很像:同样的循环、类似的工具、同样认 AGENTS.md。但在安全设计上,它走了一条有点不同的路:沙箱优先。

截至写作时(以官方文档为准),Codex 的命令默认运行在操作系统级的沙箱里:

  • 沙箱限制了它能写的目录,通常只允许写工作目录;
  • 沙箱默认可以禁止网络访问;
  • 在这个沙箱里,很多操作可以不问用户,因为就算做错了,影响也被限制在沙箱之内;
  • 需要越出沙箱的操作(比如访问网络、写工作目录以外的地方),才需要用户批准。

它把"安全"分成了两个维度来配置:沙箱的范围(只读 / 可以写工作目录 / 完全放开)和审批的策略(什么时候问用户)。

7.2 两种思路的对比

flowchart TB subgraph A["问人优先(mini-agent 的默认)"] A1["每个有副作用的操作
先问人"] --> A2["人批准了,
在真实环境里执行"] end subgraph B["沙箱优先"] B1["在受限的沙箱里
直接执行"] --> B2["要越出沙箱时
才问人"] end style A fill:#e3f2fd,stroke:#1565c0 style B fill:#e8f5e9,stroke:#2e7d32
问人优先沙箱优先
安全靠什么人的判断操作系统的隔离
打扰程度高,经常要确认低,大部分操作不用确认
人疲劳之后容易无脑点"允许",形同虚设不受影响
实现难度低高,要对接各个操作系统的沙箱机制
漏洞人看走眼沙箱本身有漏洞,或者配置得太宽

问人优先的最大问题是确认疲劳:一个任务要确认二十次,到第五次你就不看了,直接按 y。这时候"人在回路"就成了一句空话。沙箱优先的思路是:与其指望人每次都认真看,不如让大部分操作即使做错了也没关系。

这两种思路也在互相靠拢:Claude Code 也加入了给命令套一层系统沙箱的能力,Codex 也保留了审批策略。第 27 篇说过,mini-agent 没有做操作系统级的沙箱,它的答案是"放开权限时,在容器里跑",本质上是把沙箱这件事交给了你。

7.3 在 mini-agent 上也能"沙箱优先"

mini-agent 自己没有沙箱,但你可以从外面给它套一个。配套仓库里有一份 .devcontainer/ 配置,它本来是给 Codespaces 用的,让你在浏览器里就能跑起整个仓库。换个角度看,它也是一个现成的隔离环境:在容器里用 -mode yolo 启动 mini-agent,它能改的只有容器里的文件,删光了重建一个就是。

这其实就是 7.1 节"沙箱优先"的一个粗糙版本:隔离的边界由容器来画,而不是由每一次确认来画。 它粗糙在两个地方:一是容器默认能联网,第 38 篇说的"往外发数据"依然可能;二是你挂载进容器的目录,比如你的源代码,它照样能改。真正的沙箱优先,会把"能写哪些目录、能不能联网"都做成可以配置的东西。

八、最佳实践,其实是设计的推论

网上流传着很多"用好 Claude Code 的技巧"。现在回头看,几乎每一条都能从我们前面讲过的原理里推出来:

最佳实践为什么有用原理在哪篇
给它写测试,告诉它怎么验证结果可验证,Agent 才能自己纠错05、35
把构建命令、约定、坑写进记忆文件上下文之外的,它一概不知道14、17
复杂任务先让它出计划模型没有草稿纸,先推理后行动07
一个任务一个会话,做完就清空长对话会腐烂16
长任务写交接文档自动压缩会丢东西,交接文档你能审16、28
告诉它具体的文件路径让它看到关键信息,比改措辞有用14
大范围调研交给子 Agent隔离上下文,主对话保持干净30
别在真实环境里跳过所有确认黑名单永远不完整27
小步提交,经常看 diff它可能改了你没打算改的地方26
别只接一堆 MCP server每个工具定义都在吃上下文23
它说"改好了",自己再跑一遍测试它的自信和它的正确率无关02、05

理解了设计,最佳实践就不用背了。 遇到一个新产品、一个新功能,你也能自己推出该怎么用它。

8.1 反过来:用原理认出过时的"技巧"

这张表还有一个反向的用法:认出那些已经过时、甚至有害的技巧。

网上很多"提示词秘籍"是在两三年前写的,那时的模型、那时的产品和今天很不一样。拿原理去对一对,很多就站不住了:

  • "在 prompt 里用全大写的 MUST 和三个感叹号强调":第 15 篇说过,新模型对指令越来越敏感,用力过猛反而可能让它在不该过度执行的地方过度执行。把规则写具体、给出原因,比加感叹号有用;
  • "每次都写'请一步一步思考'":第 12 篇说过,推理模型已经会自己想了,这句话往往只会让正式回答里多出一段重复的推理;
  • "一次把整个代码库喂给它,让它全面了解":第 10、18 篇说过,窗口装得下不等于看得清,让它按需搜索往往效果更好;
  • "它答错了就让它重新生成,总有一次对的":第 08 篇说过,随机性可以用来探索,但一个要反复重来才能碰对的任务,多半是上下文缺了东西。

技巧会过时,原理不会。 判断一条技巧还值不值得用,就问它背后的原理在今天的模型和产品上还成不成立。

九、动手:同一个任务,两个 Agent

这一篇在配套仓库里没有对应的代码。动手环节是对比体验:

mini-agent 要接真实模型:先 export DEEPSEEK_API_KEY=sk-...。

实验 1:对照工具列表

在你常用的编码 Agent 里(Claude Code、Codex 或别的),找到查看它工具列表的方法(很多产品可以直接问它"你有哪些工具",或者在设置里看)。对照第一节的表格,标出:

  1. 哪些工具在 mini-agent 里有对应的?
  2. 哪些是 mini-agent 没有的?它们解决的是什么问题?

实验 2:同一个任务

找一个你自己项目里的小任务,比如修一个小 bug、加一个简单的函数,分别交给 mini-agent 和成品 Agent。记录:

mini-agent成品 Agent
转了几圈
读了哪些文件
你确认了几次
结果对不对
花了多少钱

(mini-agent 的费用用 /cost 看;第 37 篇会细讲。)这是第 40 篇结业任务的预演。

实验 3:试试计划模式

如果你用的 Agent 有计划模式,拿一个需要改好几个文件的任务,先在计划模式下让它出方案,你修改一处,再批准执行。和直接让它动手对比一下:结果更好了吗?你在什么时候发现了问题?

实验 4:给 mini-agent 加上计划模式或待办清单

按 4.3 节或 5.2 节的思路,给 mini-agent 加上其中一个。改完之后跑一遍 go test ./agent/...,别把已有的东西改坏了。

十、踩坑记录:用成品时的五个误区

误区 1:觉得成品"有魔法"。
它的核心和你写的 mini-agent 是同一个循环。它更好用,是因为每个零件都打磨得更细,而不是因为有什么神秘的机制。理解了这一点,遇到它犯傻的时候,你就知道该去哪里找原因。

误区 2:把所有确认都关掉,"反正它挺靠谱的"。
它大部分时候确实靠谱。但第 27 篇的惊魂记告诉我们,出事往往就在那一次。放开权限,要么在沙箱里,要么在容器里。

误区 3:只用它写代码,不让它验证。
它能跑测试,但你得告诉它测试怎么跑、哪些测试要通过。没有验证的 Agent,和第 02 篇那个"不看结果的剧本模型"没有本质区别。

误区 4:跳过计划,直接让它干大活。
大的改动,先出方案。在方案阶段改一句话,比在代码阶段返工便宜得多。

误区 5:遇到问题就换模型。
很多问题出在上下文里:它没看到关键文件、记忆文件过时了、对话太长了。先看看它看到了什么,再考虑换模型(第 13 篇:管好上下文,加上验证)。

一句话总结

成品 coding agent 和我们手写的 mini-agent 是同一个循环,读、改、搜、跑、管上下文这些零件几乎一一对应;差距在计划模式、待办清单、沙箱、可配置的权限规则和更多的扩展机制;Codex 走的是沙箱优先的路,用隔离代替频繁的确认;而那些最佳实践,都是从设计里自然推出来的。

flowchart LR M["mini-agent
循环 + 工具 + 权限 + 上下文"] -->|"再加上"| P["计划模式、待办清单、
沙箱、规则、hooks、IDE……"] P --> C["成品 Agent"] style M fill:#e8f5e9,stroke:#2e7d32 style C fill:#fff3e0,stroke:#e65100

评论

还没有评论,来说点什么吧。

发表评论

Ctrl + Enter 快速提交
Enter 搜索Esc 关闭