乐于分享
好东西不私藏

我读 Claude Code 源码,第一感觉:它不是 CLI,是本地 Agent 运行时

我读 Claude Code 源码,第一感觉:它不是 CLI,是本地 Agent 运行时

全文约 2154 字,读完大约 4 分钟。

我读 Claude Code 源码,第一感觉:它不是 CLI,是本地 Agent 运行时

我第一次翻 Claude Code 的源码分析文档时,原本以为会看到一个比较复杂的命令行聊天工具。

结果越往下看,越觉得这个判断太轻了。

它当然有 CLI,有输入框,有模型调用。但真正撑住它的,不是“在终端里问 Claude 一句话”,而是一整套本地 Agent 运行时。

这个差别很重要。

如果你也想从 0 到 1 手搓一个 Agent,第一步最好不要急着写聊天框,也不要先纠结提示词。你应该先问一个更工程化的问题:

判断卡
一个 Agent 到底靠什么持续工作?
不是一次模型回复,而是入口、状态、工具、权限、记忆和扩展能力能不能形成闭环。

先看它的骨架

从项目分析文档看,Claude Code 的主链路大概是这样:

-------------+     +-------------+     +----------------+
| CLI 入口    | --> | TUI / REPL   | --> | Query 执行内核  |
+-------------+     +-------------+     +----------------+
                                           |
              +----------------------------+----------------------------+
              v                            v                            v
       +-------------+              +---------------+             +-------------+
       | Tool/权限层  |              | Memory/持久化  |             | MCP/Remote |
       +-------------+              +---------------+             +-------------+

这不是一个“main 函数里调 API”的结构。

entrypoints/cli.tsx 负责早期分流。比如版本号、dump system prompt、remote control、daemon 这类快路径,可以在还没启动完整应用时就退出。

main.tsx 才是完整运行时的编排中心。它要处理初始化、模型选择、权限模式、工具池、MCP、内置技能、agent 定义、远程能力,最后再进入 REPL。

再往里,用户输入不是直接丢给 API,而是进入 query.tsQueryEngine.ts 这一层。

这里开始,Claude Code 才真正像一个 Agent。

为什么我说它是运行时

我现在判断一个 AI Coding 工具,会先看它有没有“运行时意识”。

因为模型本身只负责生成下一段内容。它不知道你的终端状态,不知道工具执行到哪里,不知道哪些文件刚被读过,也不知道某个 Bash 命令该不该执行。

这些事情都要由运行时接住。

Claude Code 的分层里,至少有六个关键角色:

层级它解决的问题手搓 Agent 时的启发
CLI 引导层先判断走哪条启动路径不要所有命令都启动完整应用
TUI / REPL 层接住用户输入和界面状态UI 不应该和执行内核绑死
Query 内核组织模型、工具、上下文循环Agent 的核心是主循环
Tool / Permission控制本地动作能不能发生工具不是函数,是带边界的协议
Memory / Persistence保存会话和长期上下文长任务必须能恢复
MCP / Remote接外部能力和远程协作扩展层要晚于核心层稳定

我比较喜欢这个分层,因为它很现实。

很多人做 Agent demo,第一版会直接写成:

用户输入 -> 调模型 -> 解析工具调用 -> 执行命令 -> 返回结果

这个流程可以跑,但很快会出问题。

命令执行失败怎么办?工具能不能并发?用户拒绝权限怎么办?对话太长怎么办?上一次读过的文件怎么保留?某个项目的长期规则放哪里?

这些问题一来,demo 就会被迫长出一堆补丁。

Claude Code 的源码给我的提醒是:别把这些当补丁,它们一开始就应该是运行时的一部分。

Query 执行内核才是心脏

如果只看表面,用户在终端输入一句话,模型回一句话。

但真正的主循环更像这样:

用户消息
  -> 组装 system prompt / memory / tools
  -> 调用模型
  -> 收集 tool_use
  -> 执行工具
  -> 生成 tool_result
  -> 把结果回流给模型
  -> 必要时 compact / hooks / session 写入
  -> 进入下一轮

这里最关键的点是“回流”。

Agent 不只是调用一次工具,而是要把工具结果重新变成对话的一部分,让模型在下一轮继续判断。

这也是我自己做内容系统和自动化 Agent 时最容易踩坑的地方:一开始总觉得“能调用工具”就够了,后来才发现真正难的是让工具结果、用户意图、项目状态和下一步计划稳定接起来。

一个 Agent 如果没有可靠的执行内核,就会变成很聪明但很健忘的脚本。

Tool 不是函数,是协议

Claude Code 的 Tool 设计也很值得单独拆。

Tool.ts 里,一个工具不只是 call()。它还要描述输入输出、权限、安全属性、是否只读、是否破坏性、能不能并发、怎么渲染使用过程、异常怎么回给模型。

这就说明一件事:

结构图
模型不是直接操作电脑。
模型只提出 tool_use。
运行时负责校验、权限、执行、包装结果,再把 tool_result 交回模型。

这个边界非常关键。

如果你自己手搓 Agent,千万不要把工具做成一堆随便调用的函数。至少要给每个工具补四类信息:

工具协议字段要回答的问题
输入 schema模型传来的参数是否合法
权限规则这个动作要不要问用户
并发属性它能不能和其他工具同时跑
结果包装结果怎么变回模型能理解的消息

看起来麻烦,但这就是 Agent 从玩具变成系统的分界线。

运行时还要管“忘记”和“变长”

很多人理解 Agent,会过度关注“它会不会用工具”。

但长期任务里,真正让系统崩掉的经常不是工具,而是上下文。

Claude Code 对这件事的处理很复杂:它会计算上下文预算,会给 summary 预留输出 token,会在接近阈值时触发 auto-compact,还会在压缩后重新注入文件、计划、技能和工具状态。

这背后有一个朴素判断:

风险卡
长上下文不是免费午餐。
你不能把历史无限塞给模型,也不能粗暴截断。
更靠谱的做法是:保留当前任务需要的状态,把旧历史压成可继续工作的摘要。

这对手搓 Agent 很有启发。

如果你的 Agent 只跑三轮,可以不管这些。但只要你希望它连续改项目、跑测试、修 bug、再继续重构,Context 管理和 Session 管理就不是高级功能,而是基础设施。

我会怎么学它

读到这里,我对 Claude Code 的第一篇结论很明确:

它最值得学的,不是某个神奇提示词,也不是某个单独工具实现,而是它把 Agent 拆成运行时系统的方式。

如果我从 0 做一个本地 Agent,第一版不会追求功能多。我会先搭一个很薄但边界清楚的骨架:

1. CLI / SDK 两种入口
2. 一个独立 Query 主循环
3. 标准 Tool 协议
4. 权限判定和用户确认
5. Transcript 持久化
6. 最简单的 context 压缩策略
7. 后续再接 Skills / MCP / 多 Agent

这个顺序比“先做一个很酷的聊天界面”更笨,但更稳。

我现在做 Jerry聊AI,其实也在用类似思路搭自己的内容系统:先让输入、素材、写作、审稿、发布、复盘形成闭环,再逐步加自动化和扩展能力。

Agent 也是一样。

真正有价值的不是一次惊艳回复,而是它能不能在真实工作流里持续、可控、可恢复地做事。

这也是我读 Claude Code 源码后,最想先写下来的第一句话:

别把 Agent 当聊天框做。先把它当一个运行时做。

后面这个系列,我会继续拆它的 Query Engine、Tool Call、权限、Sandbox、Context、Memory、Skills 和 MCP。不是为了复述源码,而是看清一个成熟本地 Agent 到底要长出哪些骨头。

如果你也在用 AI 写代码,或者想自己手搓 Agent,可以关注我。这个系列我会按源码机制一篇篇拆,尽量讲到能让普通程序员照着搭出自己的第一版骨架。