Claude Code 源码研究【第二弹】:智能体框架与大模型相互成就

在上一篇“Claude Code 源码研究:一个 while(true) 循环让大模型自己干活”之后,继续我们的研究——
01
自然语言引导能保证模型每次都听话吗?
Claude Code 不靠 if-else 控制模型选哪个工具,而是靠 40 份精心撰写的”工具说明书”做软引导。这立刻引出一个问题:软引导靠谱吗?模型不听话怎么办?
答案很直接:不能保证。自然语言引导是概率性的,不是确定性的。
说明书里写了”不要用 cat 读文件,用 FileRead”,模型大多数时候会照做,但没有任何机制能保证它 100% 遵守。大模型的本质是概率采样,概率采样就意味着总有”意外”的可能。
但 Claude Code 的设计者显然不是天真的理想主义者。他们在自然语言引导之外,布了三层防线。
第一层,“拦”:每个工具调用在执行前,都必须通过权限检查。危险操作(写文件、执行命令)会弹窗让用户确认。
模型想干什么不重要,用户不批准就执行不了。
第二层,“查”:把 shell 命令解析成抽象语法树,识别管道、重定向、子命令嵌套中的危险模式。
rm -rf /
会被拦住,
echo hello | rm -rf /
也会被拦住。
第三层,“关”:构建文件系统沙箱,在操作系统层面限制 Agent 的活动范围——哪些目录可读、哪些可写、哪些域名可访问,全部白名单控制。这是一道物理隔离。
劝(Prompt提示)、拦、查、关,四层叠加,任何单层都不是 100%。
自然语言引导可能失效,权限系统可能被用户自己按了”全部允许”,AST 解析可能遇到没见过的命令构造,沙箱可能在某些平台上有实现差异。但四层的穿透概率是各层失效概率的乘积,趋近于零。
这是经典的 Defense in Depth ——纵深防御(军事术语,也是信息安全的基本原则)。不依赖任何单一防线的完美,而是通过层层叠加让整体风险降到可接受的水平。
就好像你家的安全不是靠一把锁。是门禁 + 门锁 + 监控 + 邻居会报警。每一层都可能失效,但小偷要同时突破所有层,难度指数级上升。
Claude Code 对”模型不听话”这件事的态度,不是”努力让它 100% 听话”,而是”假设它一定会不听话,然后确保不听话的后果可控“。这比盲目信任自然语言引导成熟得多。
02
如何评估工具执行结果的正确性?
Agent 调了一个工具,工具返回了结果。下一步自然要判断:这个结果对不对?要不要重试?
答案出乎意料:Claude Code 完全不评估工具执行结果的正确性。代码层面,零判断。
工具执行完之后,结果被原封不动包装成 tool_result,塞进消息历史,发回给模型。
没有”检查返回值是否合理”的逻辑,没有”如果结果为空就重试”的分支,没有任何形式的结果验证。
那”评估”这件事谁来做?模型自己做。
下一轮循环里,模型看到了完整的对话历史——包括自己上一轮请求的工具调用和对应的执行结果。它自己判断结果是否符合预期,自己决定下一步做什么:
-
如果 grep 搜出来的结果不对,模型会换个关键词再搜;
-
如果文件编辑之后 npm test 失败了,模型会看报错信息、分析原因、修改代码、再跑测试;
-
如果一个 API 请求超时了,模型会决定是重试还是换个方案;
-
……
“评估”不是 Agent Loop 中一个独立的步骤,它融入了下一轮循环的决策过程。
这其实完全符合人的工作方式。一个工程师在终端里敲了 grep “TODO” *.py,发现结果只有一条,他不会启动一个”grep 结果正确性评估程序”——他直接看一眼,觉得不对就换个关键词重来。
Claude Code 的设计者抓住了本质——评估和下一步行动是一体的,不是两个分离的阶段——对大模型而言,理解工具输出并据此决策,是它的推理能力的一部分,而不是需要额外代码辅助的独立任务。
这里面有一个更深的设计哲学:不要替模型思考。
框架的职责是当好信使——忠实传递请求,忠实返回结果——而不是在中间插一脚,自作聪明地”帮”模型理解结果。
03
用户对结果不满意时如何反馈给 Agent?
Agent 给出了一个方案,用户觉得不对,说”这不是我要的,我要的是 X”。这个反馈怎么进入系统?有没有专门的”反馈处理模块”?
答案是:无法反馈。
用户的反馈就是一条普通的 user message。走的是和第一次输入完全相同的路径:processTextPrompt() → 构建消息 → 进入 Agent Loop。没有”反馈模式”,没有”纠错通道”,没有任何特殊处理。
用户说”这不对,应该用 async/await 而不是 callback”,这句话被当作一条新的 user message 追加到消息历史的末尾。
下一轮 API 调用时,模型看到的是完整的对话历史:原始需求、自己之前的方案、工具执行的结果、以及用户刚才的这句反馈。
模型靠自己的推理能力理解:这是一条纠正指令,不是一个全新的任务。然后它基于这个理解,调整方案,继续执行。
这意味着:纠错能力的上限,完全取决于模型的推理能力,而不取决于框架的设计。框架不做任何”帮助模型理解反馈”的事。不会把用户的话标记为”这是纠正”,不会提取其中的关键信息高亮显示给模型,不会触发什么”反思链路”。
这个设计一开始让我觉得过于简陋。但想深一步,它是对的。
原因在于:反馈的语义是无限丰富的。 “这不对”可能是纠正方向,”加个日志”可能是补充需求,”算了,先帮我看另一个文件”是话题切换,”嗯”可能只是确认。
如果框架试图用代码逻辑去分类和处理这些反馈,它要么做得太粗(分类错误),要么做得太细(写不完的规则)。
那么不如把反馈原样交给模型,让模型自己在完整上下文中理解语义。模型是目前最好的”自然语言理解引擎”,没有理由在它前面再套一层更弱的理解层。
这跟上一个问题的哲学一脉相承:框架不替模型思考。
04
每次给模型多少上下文?用户跳话题怎么办?
最后一个高频问题:Agent Loop 每轮调 API,发过去的消息历史有多长?如果用户前面在修 bug,突然说”帮我写个 README”——前面那些 bug 相关的上下文还会发过去吗?
答案简单粗暴:全部发过去。
每一轮 API 调用,Claude Code 把完整的消息历史——从第一条用户消息到最近一次工具执行结果——全部打包发送。不做话题筛选,不做相关性过滤,不做任何裁剪。
这是一个刻意的设计选择。Claude Code 的哲学是:宁可让模型在一大堆上下文里自己找到有用的信息,也不冒险替模型过滤掉它可能需要的东西。
为什么不过滤?因为”什么是相关的”这个判断本身极其困难。用户说”帮我写个 README”,看起来和前面的 bug 修复无关。但如果前面的 bug 修复涉及到项目的核心架构,而 README 正好要描述这个架构呢?如果框架自作主张把 bug 修复的上下文过滤掉了,模型就丢失了关键信息。
过滤的风险是不对称的:多给了无关信息,模型大概率能自己忽略;少给了关键信息,模型一定会犯错。
但这个策略有一个显而易见的问题:上下文会撑爆。
Claude 的上下文窗口虽然大(200K tokens),但几轮密集的工具调用之后,消息历史可以轻松超过这个限制。一次 FileRead 读一个大文件,可能就是几千 tokens;十轮循环下来,上下文可能已经膨胀到六位数。
Claude Code 为此实现了三级压缩机制,层层递进:
第一级:Auto-compact(主动压缩)
当上下文接近窗口上限时自动触发。Claude Code 会发起一次额外的模型调用(通常用更轻量的 Haiku 模型),把之前的对话历史压缩成一份结构化摘要。压缩后的摘要替代原始历史,作为后续对话的起点。
压缩的 prompt 里有一条特别要求:”Preserve All user messages”——所有用户的原始消息必须保留。因为在所有信息中,用户的意图变化是最不能丢的。工具的执行结果可以概括,模型的中间推理可以省略,但用户说过的每一句话,都可能影响后续任务的理解。
压缩后,消息历史的最前面会加一句:”This session is being continued from a previous conversation that ran out of context.”——告诉模型:你看到的不是对话的全部,前面有一段被压缩过的历史,请据此继续。
第二级:Reactive compact(紧急压缩)
当 API 直接返回 prompt-too-long 错误时触发。这说明连 auto-compact 都没来得及处理(或者压缩后仍然过长),需要更激进的压缩策略。
第三级:Context collapse(渐进折叠)
不是一次性压缩全部历史,而是逐步折叠早期的工具执行结果。最近几轮的细节保持完整,更早的轮次只保留摘要。有点像人的记忆——昨天的会议你记得细节,上个月的会议你只记得结论。
三级机制的设计逻辑很清晰:先预防(auto-compact),再应急(reactive compact),最后兜底(context collapse)。
跟前面讲的安全四层防线一样,是同一种工程思维:不依赖任何单一机制的完美,用多层叠加覆盖各种边界情况。
05
Agent 框架新范式
Claude Code 在框架层面做的事情,比大多数人想象的少得多——不评估工具结果,不处理用户反馈,不筛选上下文,甚至不保证自然语言引导的有效性。
它做的只是:确保信息准确传递、确保安全边界不被突破、确保上下文不会物理溢出。
所有”智能”的部分——理解任务、选择工具、评估结果、解读反馈、忽略无关信息——全部交给模型。
这不是偷懒,这是一种架构立场:在模型能力足够强的前提下,框架最大的价值不是替模型做决策,而是不妨碍模型做决策。
传统软件工程的思维是”尽可能用代码控制一切”——每个分支、每个判断、每个异常处理,都要写在代码里。
而 Claude Code 代表的新范式是”尽可能少地替 AI 做主”——把决策空间完整地交给模型,框架只负责基础设施:安全、传输、资源管理。
这个转变的前提当然是模型足够强。如果模型不靠谱,你不得不在框架里加各种护栏、规则引擎、决策树来弥补。
但当模型的推理能力越过某个门槛,这些精心构建的”辅助逻辑”反而变成了束缚——它们基于工程师对任务的有限理解,而模型可能有更好的判断。
这大概是 Claude Code 源码给我们最大的启发:好的 AI Agent 框架,不是做了了什么,而是克制住没做什么。


夜雨聆风