Lucent's Blog

当时明月在 曾照彩云归

人生不相见,动如参与商。


Claude Code 真正的秘密:模型之外,还有一套完整的 Agent 操作系统

从 Claude Code 源码里,我看到了下一代 Agent 的真正形态

不是更聪明的模型,而是更成熟的系统。

最近看了 Claude Code 源码。

npm 包里还原出的完整 TypeScript 源码, 4756 个文件。

刚看到这个数字的时候,我的第一反应不是“太夸张了”,而是:

Claude Code 已经不是一个“会写代码的 AI 工具”了。

它更像一套完整的 Agent Operating System。

它不只是一个 prompt 加几个 tool,而是一整套:

  • 主循环与状态机

  • 工具执行与权限治理

  • 多 Agent 分工

  • 上下文预算系统

  • 长期记忆

  • 插件与 MCP 生态

  • 后台任务、恢复、清理、生命周期管理

真正厉害的地方,不是它会不会写代码,而是它如何让模型稳定、可控、持续地工作。

Claude Code 整体架构

Claude Code 的核心不是一次调用模型,而是一套持续循环的 Agent Runtime。

工具执行 Pipeline

Claude Code 最成熟的地方,不是工具多,而是每次调用都经过完整治理。

多 Agent 分工

多 Agent 的意义,不是更聪明,而是把“探索、实现、验收”彻底拆开。

Claude Code 的三层安全网

安全最厉害的地方,不是有多少层,而是每一层都不能绕过另一层。

上下文预算系统

Claude Code 不把上下文当仓库,而是把它当成一种预算。

为什么 Claude Code 看起来像一个“操作系统”

很多 AI Coding 产品,打开源码后你会看到:

  • 一个 main

  • 一个 prompt

  • 几个 tool

  • 一些 util

但 Claude Code 的顶层目录已经完全不是这个规模,其核心模块包括:

  • commands/

  • tools/

  • services/

  • hooks/

  • skills/

  • tasks/

  • bridge/

  • memory/

  • permissions/

  • compact/


其中几个核心文件的规模非常夸张:

文件

行数

main.tsx

4683

query.ts

1729

toolExecution.ts

1745

AgentTool.tsx

1397

runAgent.ts

973


这意味着它已经不是“写几个 prompt 驱动模型”,而是在认真构建一套长期运行、可恢复、可治理的 Agent Runtime。
我觉得可以把 Claude Code 理解成这样一层架构:

用户输入
   ↓
主循环(query.ts)
   ↓
Prompt 编排 + 上下文预算
   ↓
模型输出
   ↓
工具执行 Pipeline
   ↓
权限 / Hook / 安全检查
   ↓
结果注入下一轮
   ↓
继续循环,直到任务完成


它真正的核心不在模型,而在“模型之外”。

Agent 的本质,不是一轮问答,而是一个状态机

Claude Code 最重要的文件之一,是 query.ts。

它并不是简单地:

1.收到用户输入
2.调一次模型
3.输出结果


而是内部维护了一个 while(true) 的主循环。
每一轮都会经历:

1.压缩上下文
2.检查 token 预算
3.拼接 system prompt
4.调模型 API
5.流式解析输出
6.执行工具
7.注入结果
8.判断是否进入下一轮


这个设计特别重要。
因为 Agent 真正面对的问题,不是“第一次能不能答出来”,而是:

  • 上下文太长怎么办?

  • 工具调用失败怎么办?

  • 模型输出到一半超限怎么办?

  • 一轮没做完,要不要继续?

  • 某个步骤失败后,应该从哪里恢复?

很多 AI Demo 都能完成第一轮,但一进入长任务、多步骤、复杂上下文,就会开始崩。

Claude Code 的做法,是把整个过程设计成一个可恢复、可继续、可重试的状态机。

换句话说:

Prompt 决定模型“怎么说”,主循环决定系统“怎么活”

它把 Prompt 从“一段话”变成了一套装配系统

Claude Code 里最有意思的一点,是它的 system prompt 并不是一次性拼成的大字符串。

它被拆成了很多 section。

静态部分:

- 身份定位
- 行为规范
- 工具语法
- 风格要求
- 风险动作规范


动态部分:

- 当前会话状态
- Memory
- MCP instructions
- 当前目录、系统环境
- 语言偏好
- 当前可用工具


而且中间还有一个边界:

SYSTEM_PROMPT_DYNAMIC_BOUNDARY


为什么要这么做?

因为 Anthropic 在做 prompt cache。

如果两次请求的 system prompt 前半部分完全一致,那么 API 可以直接复用缓存。

即:

1.不会变的内容,放前面
2.会变化的内容,放后面
3.尽量让前缀保持字节级一致

这个思路特别像现代 Web 的缓存设计。

以前我们会觉得 prompt 是“写得好不好”。

Claude Code 告诉我们:

Prompt 也是一种运行时资源,它需要被缓存、拆分、复用、按需加载。

真正成熟的工具系统,不是“模型想调就调”

Claude Code 一共有 42 个工具。

但比工具数量更重要的是:它有一条完整的工具治理流水线。

当模型决定调用一个工具时,它不会直接执行。

而是要先经过:

找到工具
→ 输入 schema 校验
→ 自定义输入校验
→ 风险分类
→ 前置 Hook
→ 权限判断
→ 修改输入
→ 真正执行
→ 后置 Hook
→ 记录日志和追踪

这个设计非常重要。

因为一旦模型开始能:

  • 改文件

  • 跑 Shell

  • 调数据库

  • 操作浏览器

  • 调用外部系统

你就已经不再是在做聊天机器人,而是在做一个具备执行能力的软件代理。

这时候,如果没有治理层,系统迟早会翻车。

Claude Code 有几个特别值得学习的细节。

1. 默认 fail-closed

如果一个新工具忘了声明“是否只读”,系统默认它会写。

如果忘了声明“是否并发安全”,系统默认它不安全。

即:

忘了配置,就按最严格处理。

这是一种非常典型、也非常成熟的安全思维。

2. Shell 工具会提前做风险预判

比如 BashTool。

在正式弹权限框之前,它就已经启动了一个风险分类器,提前判断:

  • rm -rf 是否危险

  • curl | bash 是否危险

  • 某个命令是否可能破坏系统

而且这个风险判断和 Hook、权限检查是并行进行的。

最终用户感觉到的,是系统响应更快了。

本质上,它是在利用“等待模型输出的时间”去提前做风险分析。

这是一种很典型的工程优化。

多 Agent 最重要的价值,不是更聪明,而是分工

现在很多产品都在讲 Multi-Agent。

但 Claude Code 的多 Agent,我觉得是少数真正讲清楚“为什么要多 Agent”的案例。

它至少有几种角色:

  • Explore Agent:负责探索

  • Plan Agent:负责规划

  • General Agent:负责执行

  • Verification Agent:负责验证

为什么要拆开?

因为一个 Agent 同时做:

  • 研究

  • 规划

  • 实现

  • 验收

最后往往每一件事都做不好。

最典型的是:实现者很容易高估自己。

它刚写完代码,就会下意识觉得“应该没问题了”。

所以 Claude Code 专门设计了一个 Verification Agent。

它的任务只有一个:

想办法把前面那个 Agent 写的东西搞坏。

它会被要求:

  • 真正去运行

  • 真正去点击

  • 真正去 curl

  • 真正去验证 stdout / stderr

  • 真正记录命令和结果

最后只能给出:

  • PASS

  • FAIL

  • PARTIAL

它甚至还专门防止模型偷懒。

比如:

  • “代码看起来没问题”

  • “测试大概能过”

  • “这个应该没事”

这些都不算验证。

Claude Code 会直接要求:

别猜,去跑。

我觉得这是这套系统最有价值的地方之一。

因为 AI 最容易犯的错,从来不是“不会”,而是“差不多”。

安全设计最厉害的地方:每一层都不能绕过另一层

Claude Code 的安全层一共有三层:

  1. 风险分类器

  2. Hook

  3. 权限决策

其中最成熟的一点是:

它们互相协作,但互相不能绕过。

比如:

  • Hook 说 allow

  • 但配置文件里写了 deny

最终还是 deny。

再比如:

  • Hook 可以修改输入

  • 但不能让一个危险命令静默通过

这套设计的价值在于:

即使某一层写错了,系统也不会整体失控。

很多产品的问题恰恰相反。

插件系统一强大,安全边界就被打穿了。

Claude Code 的做法是:

Hook 可以很强,但不能越权。

这是一个成熟系统最重要的特征之一。

真正的插件系统,不是“接进来”,而是“让模型知道它存在”

Claude Code 支持:

  • Skill

  • Plugin

  • MCP

但它和很多系统最大的不同在于:

它不只是把工具接进来。

它还会把:

  • 工具说明

  • Skill 描述

  • MCP instructions

  • 当前有哪些能力

  • 什么时候该用

全部注入给模型,模型会知道:

“我现在有哪些工具,以及什么场景应该用它们。”

这一点其实特别关键。

很多系统明明接了十几个插件,但模型不知道什么时候该调用。

于是插件虽然存在,但等于不存在。

Claude Code 真正厉害的地方,是它让模型“感知到”自己的能力。

生态的最后一公里,不是工具接入,而是能力可见。

Token 不只是长度,而是一种预算

Claude Code 对上下文的处理方式,也特别值得学习。它没有简单地“超了就截断”,而是有四层压缩机制。

四层压缩机制

1.Snip Compact:剪掉历史里最不重要、最长的部分

最轻的一层。

它不会改动整个上下文,只会把特别长、特别旧、又不太重要的消息尾部裁掉,比如:

  • 很长的日志

  • 很长的文件 diff

  • 很长的工具输出

  • 很久以前的回复

例如原本:

Tool Output:
...
第 1 行
第 2 行
...
第 500 行

Snip Compact 可能会变成:

Tool Output:
第 1-20 行
...
[省略 460 行]
...
第 481-500 行

它只是“剪掉冗余部分”,上下文结构和原意还保留着。

适合解决:

  • 工具输出太长

  • 某一条消息特别占 token

2.Micro Compact:只压缩被重复引用、重复出现的内容

比 Snip 更聪明一点。

Claude Code 里很多 tool_use / tool_result 会反复在后面被引用。Micro Compact 不会重新总结整段,而是:

  • 找到之前已经出现过的工具结果

  • 用一个更短的引用或摘要替换

  • 只保留后续真正新增的内容

比如原本连续几轮里都带着:

之前 FileRead 读取了 a.ts 的完整内容

后面可能会变成:

引用 FileRead#42 的结果(a.ts 内容)

或者:

沿用之前的 tool_result,仅保留本次新增 diff

所以它本质上是在做:

“不要重复把同一段内容塞进上下文”

适合解决:

  • 多轮工具调用

  • 重复读取同一个文件

  • 长对话里不断重复同样的信息

3.Context Collapse:把一大段旧上下文折叠成摘要

这是第一次真正“总结”。

当会话已经很长,前面很多轮虽然重要,但短时间内不会直接用到时,Claude Code 会把那一整块历史折叠成一段摘要。

例如原本有十几轮:

用户先让 Agent 调查 bug
然后又读取了 6 个文件
又修改了两个函数
又跑了测试
...

Collapse 后会变成:

历史摘要:
- 已确认 bug 出在 auth.ts
- 已修改 login() 和 refreshToken()
- 已跑过单元测试,仍有一个失败
- 当前剩余问题:session 超时逻辑

即:

  • 保留“发生了什么”

  • 丢掉“具体每一句怎么说的”

这是第一次会明显损失细节,但能省很多 token。

适合解决:

  • 长任务

  • 多轮协作

  • 前面做过很多事情,但只需要记住结论

4.Auto Compact:真的快超上限时,强制做一次全面压缩

这是最后一道、最重的一层。

当前面几层都还不够时,Claude Code 会主动触发一次“全量压缩”。

它会:

  • 大规模总结历史

  • 只保留当前任务最关键的信息

  • 把旧的 tool output、长日志、长 diff 都折叠掉

  • 甚至可能只留下“当前状态”和“下一步”

比如原来 10 万 token 的上下文,压到只剩:

当前任务:
修复支付失败问题

当前状态:
- 已确认问题在 retry 逻辑
- payment.ts 已修改
- 剩余:验证 timeout 场景

重要文件:
- payment.ts
- retry.ts

它更像是:

“把整个会话重新浓缩成一份工作备忘录”

因为压缩很重,所以 Claude Code 会尽量先尝试前面三层;只有真的快撞到模型窗口上限时,才会触发 Auto Compact。

快速理解

可以把四层理解成下面这个层级:

Snip Compact = 剪掉一点边角料
Micro Compact = 不重复讲已经讲过的内容
Context Collapse = 把旧历史压成摘要
Auto Compact = 整个会话重新浓缩成一份工作笔记

其中前两层几乎不影响内容,后两层会逐渐牺牲细节来换取 token 空间。

支撑压缩运行的机制

如果上面四层压缩还是不够,还有下面四种机制。

1.Reactive Compact

最后一层“应急压缩”

前面的 Snip / Micro / Collapse / Auto 都做完之后,如果真正调用模型时还是超了 token 上限,Claude Code 才会在失败后立刻回退,再做一次更激进的压缩。

它不是提前做,而是:

“调用失败了,发现真的塞不下,那就现场再压一次。”

通常会:

  • 继续删掉更多旧消息

  • 把长工具输出直接变成一句话

  • 丢掉已经不太相关的历史

  • 只保留当前任务最关键的信息

所以 Reactive Compact 更像:

“紧急模式下的二次压缩”

一般只有在特别长、特别复杂的任务里才会触发。

2.Tool Result Summary

工具结果摘要。

很多工具返回的内容会非常长,比如:

  • grep 找到 500 行

  • git diff 改了很多文件

  • ReadFile 读出整个文件

  • 测试日志几十 KB

Claude Code 不会把这些原始结果永远塞在上下文里,而是会生成一个更短的摘要。

例如原本:

ReadFile(auth.ts)
返回 800 行代码

后面可能被替换成:

Tool Result Summary:
auth.ts 包含:
- login()
- refreshToken()
- session timeout 逻辑
- 发现 retry 判断有问题

或者:

测试结果摘要:
- 12 个测试通过
- 1 个测试失败
- 失败位置:payment retry timeout

也就是说,它保留“工具得出了什么结论”,不保留完整原始输出。

可以理解为:

Tool Result 是原始材料

Tool Result Summary 是摘要版工作记录

3.Session Memory

会话记忆。

这是 Claude Code 在一次长任务里维护的一份“长期状态”。

它不会记录所有聊天内容,而是只保留:

  • 当前目标是什么

  • 已经做了什么

  • 哪些文件被改过

  • 哪些问题已经解决

  • 下一步准备做什么

比如在修 bug 的过程中,Session Memory 可能会变成:

当前目标:修复登录失败

已完成:
- 定位到 auth.ts
- 修复 refreshToken
- 测试通过

待完成:
- 检查 session timeout

它更像一个“任务看板”或“工作备忘录”,用于在上下文被压缩后,依然知道:

我现在做到哪一步了?

很多时候 Auto Compact 最后保留下来的核心,其实就是 Session Memory。

4.Token Budget

Token 预算。

Claude Code 不会等上下文爆掉才开始压缩,而是一直在计算:

  • 当前已经用了多少 token

  • 还剩多少 token 给下一轮

  • 工具输出还能不能继续塞

  • 是否需要提前压缩

例如一个模型窗口是 200k token,它可能内部会留:

160k 用于历史上下文
20k 留给工具结果
20k 留给模型下一轮输出

所以它实际上一直在做预算管理:

如果我现在再读取一个大文件,会不会超?

如果再加一个测试日志,要不要先压缩旧内容?

这就是为什么 Claude Code 看起来不会“突然崩掉”——因为它在每一步之前,都在做 token 预算。

快速理解

可以把它理解成:

Token Budget = 整个上下文窗口的资源分配器
Session Memory = 当前任务的长期状态
Tool Result Summary = 长工具输出的摘要版
Reactive Compact = 真超限后的紧急压缩

它把 token 当成一种预算,不是所有内容都值得塞进上下文。

有些内容:

  • 可以压缩

  • 可以摘要

  • 可以缓存

  • 可以按需加载

  • 可以只保留引用

Claude Code 最重要的一个理念是:

上下文不是知识仓库,而是工作内存。

这句话我觉得特别重要。

因为很多 Agent 后面都会死在上下文膨胀上。

它的记忆系统最成熟的一点:知道什么不该记

Claude Code 的记忆系统,不是“什么都存”。

它明确规定真正应该记住的,只有这些:

  • 用户偏好

  • 行为反馈

  • 项目背景

  • 外部系统指针

而这些东西,不应该记:

  • 代码结构

  • 文件路径

  • git 历史

  • 当前代码里的模式

因为这些都可以重新从代码里推导出来。

如果硬塞进记忆,只会越来越乱、越来越过时。

这个思路特别成熟。

真正有价值的记忆,应该是:

不可推导、但未来还会再次需要的信息。

比如:

  • 用户讨厌某种写法

  • 某个项目里有一个很隐蔽的坑

  • 某个服务虽然能调用,但有已知问题

我从 Claude Code 身上学到的 7 条原则

最后,我觉得 Claude Code 最值得抄的,不是具体实现,而是背后的方法论。

1. 不要相信模型会“自己做对”

你希望它先读代码、不要乱改、不要乱加功能,就必须写成制度。

2. 角色一定要拆开

探索、规划、实现、验证,不应该是同一个 Agent。

3. 工具必须有治理

模型不能直接碰系统能力,中间一定要有权限、校验、风险分析。

4. 上下文是一种预算

不要什么都塞进去。

5. 安全机制必须互锁

任何一层都不能绕过另一层。

6. 插件生态的关键是“能力可见”

不是你接了什么,而是模型知道自己能做什么。

7. 真正的产品化,在于处理“第二天”

第一天能跑不难。

难的是:

  • 第二天任务怎么恢复

  • 后台进程怎么清

  • 状态怎么同步

  • 会话怎么续

  • 成本怎么追踪

这些问题不解决,再聪明的 Agent,也只是 Demo。

结语

Claude Code 最让我震撼的地方,不是它用了多强的模型。

而是它让我第一次非常清楚地意识到:

下一代 Agent 的竞争,不会只发生在模型层。

真正决定上限的,是模型之外的系统工程。

谁能更好地管理上下文、治理工具、约束行为、拆分角色、控制安全、维护生命周期,谁就更有机会做出真正能长期工作的 Agent。

模型会越来越强。

但真正把模型变成产品的,永远是系统。

下一篇

最近看 Claude Code 的设计思路时,我发现它和很多 AI 产品有一个非常不同的地方: 它并不是在追求“功能越多越好”,而是在刻意克制。 你会发现,Claude Code 明明已经具备非常强的能力,却始终没有把所有工具、所有按钮、所有高级功能一次性摆在用户面前。甚至官方新增一个工具都非常谨慎…

阅读