回顾
全书要点串联:一条消息穿过的各层,值得记住的设计,与站内其他终端 Agent 的对照,读代码时发现的文档出入与值得留意的行为,以及可迁移的经验和这份源码的短板。
读到这里,ZCode 的形状可以归成一句话:一个自研的 Agent 运行时,装进三种宿主。回合循环、工具、权限、压缩、子 Agent、工作流都在 apps/zcode-cli 里自己写;终端 TUI 在进程内直接调用它,桌面端与 Web 服务端把它当子进程,隔着 ZCode Protocol 驱动它。产品一侧的五十多万行代码,大部分在做“怎样呈现、怎样调度这个运行时”,而运行时本身只关心一件事:把一个会话跑好。这一篇把前面各部分串起来。
一条消息穿过的各层
对照一条消息的旅程:
- 宿主。TUI 经一组回调把输入交给 cli 的命令中心;桌面端与 Web 把它包成
v4/command信封,经 Host 写进 Agent 子进程的标准输入,先过CommandInbox的查重与串行(终端界面、ZCode Protocol V4、桌面应用)。 - 装配。一个会话一个
ZCodeApp,里面恰好一个AgentRuntime,端口由 bootstrap 按五层配置造好注入(bootstrap、AgentRuntime)。 - 受理。空闲时当场建立启动预留,忙碌时分成并进当前回合的引导与排到之后的队列(输入受理)。
- 回合。冻结本轮模型,初始化上下文与钩子,写入用户消息,然后在循环里反复“整理、请求、执行”(回合循环、系统提示词、上下文压缩)。
- 模型。适配层把三种 API 形态统一成一套流式事件,只读工具边流边执行,截断续写、断流恢复与两层重试兜住异常(一次模型请求、模型适配层)。
- 工具。每个调用走一遍校验、钩子、权限、带超时执行、结果预算与投影的流水线(执行器、权限、钩子)。
- 回流。事件经
appendEvent发给订阅者,消息与 part 并排写进 SQLite;TUI 直接渲染原始事件,协议层先归约成行与状态再攒帧推送(会话事件流、SQLite 会话库)。
值得记住的设计
运行时
- 端口与原型安装。
AgentRuntime只认contracts里的端口,近两百个方法分散在近百个文件里,再装到同一个类的原型上;宿主不注入审批端口,任何要问人的调用一律被拒(AgentRuntime)。 - 引导与排队是两条车道。运行中的新输入要么在下一个工具批次之后并进当前回合,要么等回合结束另起一轮;桌面端的“交互行为”设置决定走哪条(输入受理)。
- 长程优先。回合循环没有步数上限,工具调用的次数与重复只换来提醒;真正的刹车是上下文压缩与“压缩后迅速又满”的熔断、输出截断续写的次数、断流恢复的次数与用户取消(回合循环)。
- 断流时补齐工具结果。已经发出的工具调用若得不到结果,就合成一条“按失败处理”或“副作用未知、先检查现状”的结果,保证历史里每个调用都有配对(一次模型请求)。
- 事件只在内存,消息落库。唯一的事件存储是进程内的,流式事件按回合窗口淘汰;冷恢复时由持久化消息反推出等价事件,再喂给同一个投影(会话事件流)。
工具与安全
- 声明式工具契约。每个工具声明 schema、只读、并发安全、副作用范围、结果预算与超时,调度、权限与流式执行都读这些声明;Glob 与 Grep 默认不给模型,搜索改由 Bash 里被替换成 bfs 与 ugrep 的
find、grep承担(工具契约、读、写、改、搜)。 - 只读判定表与自动转后台。Bash 命令先经 unbash 解析,再按一套简单命令、多词命令、git 子命令与 flag 的规则判定是否只读,命令注册表由 Fig 的补全规格生成;普通前台命令到了超时并不被杀,而是原地转成后台任务,模型之后再去读输出(
sleep开头的命令与闲时回合除外)(Bash)。 - 把副作用摊开讲。四种权限模式、七个生命周期钩子、按声明摘要信任的工作区钩子;
NOTICE.md逐项写明了哪些功能会联网、数据落在哪里,并直说共享执行适配器没有操作系统沙箱(权限、钩子、执行边界)。
长程与多 Agent
- 目标模式另起一次请求验证。完成与否不让干活的那一轮自评,而是在它结束后另发一次不带工具的请求判断,没有轮数与时间上限(目标模式)。
- 动态工作流。模型写 TypeScript 编排脚本,编译器做类型检查、taint 分析、JSON Schema 合成与站点插桩,脚本在只有 ES 内建对象的
vm上下文里运行,靠日志按“站点加序号”回放实现中断后续跑(编译器、引擎、工具链)。 - 三条工作流线并存。固定八阶段的专家工作流存文件,早期的脚本工作流与现在的动态工作流各有一套 SQLite 表,后者由前者演化而来(专家工作流)。
宿主与协议
- 命令信封与两种投递档位。所有命令带客户端生成的
commandId做幂等,按修订号做并发检查;桌面每 30 毫秒一帧、流式全发,Web 与手机 150 毫秒一帧、只流正文,终态逐字节一致(ZCode Protocol V4)。 - 把产品行为画成状态空间。
packages/formal-proof枚举“某个状态下来了某种输入”的全部组合,协议的输入路由裁决表声称与它逐条对齐(怎么读这份源码)。 - 窗口级 Host。每个窗口一个 Host 进程,刷新页面不杀 Host,桌面、手机与远程工作区都挂在同一个 Host 的 attachment 上(桌面应用、远程工作区与手机远控)。
与站内其他终端 Agent 对照
| 维度 | OpenCode | Kimi Code | MiMo Code | MiniMax Code | ZCode |
|---|---|---|---|---|---|
| Agent 循环 | 自研 | 自研 | 沿用 OpenCode | 内置 pi-mono,外包回合系统 | 自研 |
| 终端界面 | OpenTUI + SolidJS | 复制 pi-tui | 沿用 OpenTUI | 另 fork 的 pi-tui 0.84.2 | OpenTUI 的 React 渲染 |
| 存储 | SQLite + Drizzle | 追加日志 + 自研索引 | SQLite | SQLite + Drizzle | Node 内置 node:sqlite + 手写仓储与迁移 |
| 入口 | TUI、Web、桌面、ACP 等 | TUI、Web、VS Code、ACP 等 | 以 TUI 为主 | TUI、exec、ACP | TUI、-p 无头、桌面、Web(后两者经 ZCode Protocol) |
| 与上游的关系 | 自身即上游 | 复制一个库 | 分叉后独立演进 | vendor 源码并记补丁台账 | 无上游循环,复制了 VS Code IPC、Fig 补全规格等组件 |
文档与代码的出入
README、AGENTS.md 与代码注释大体可信,但以下几处与代码不一致,正文都按代码写:
- 规矩与现状:CLI 的
AGENTS.md规定单文件不超过 400 行,全仓仍有 478 个非测试文件超标,架构检查只对唯一一个受管模块生效(仓库全景);两份AGENTS.md都强调测试,开源仓库却只有 4 个测试文件(怎么读这份源码)。 - CLI 文档:
apps/zcode-cli/README.md开头仍是起步模板的口吻,“零生产依赖”与列出的几个 npm 脚本都不存在;--help漏掉hooks、agent-server、--output-format与inspect,斜杠命令表 19 个只列了 15 个(命令行入口、ZCode 是什么)。 - 插件与钩子:README 说插件配置只展开
ZCODE_前缀的环境变量,实际任何环境变量都会展开,还认CLAUDE_前缀的别名(插件);钩子一节对 matcher、钩子类型与非 JSON 输出的描述与代码不符(钩子)。 - 工具描述:Read 说默认最多读 2000 行,实际只按字节与 token 截断;文件工具要求绝对路径,实际也收相对路径;WebFetch 说交给“小而快的模型”处理,实际用本轮的主模型;Edit 让模型改用并不存在的 NotebookEdit(读、写、改、搜、Web 工具)。
- 官方套餐网关:文件头注释说“用户自建 provider 不受影响”,实际按最终 URL 改写,指向官方端点的个人 Provider 同样走网关(模型适配层)。
- 记忆:系统提示词说记忆的
description用于召回时判断相关性,代码里没有按问题召回的逻辑,选哪条由主模型看索引自己决定(项目记忆)。 - 协议与工作流:根
AGENTS.md要求协议改动同步更新zcode-protocol/index.ts,V4 的 schema 其实在另一个目录;注释说“20 命令全部原生”,实际有 34 种;动态工作流运行时的 README 对失败裁决与线协议的描述已经过时(ZCode Protocol V4、动态工作流(二))。 - 配置:
features.compact能解析却关不掉自动压缩,microcompact 缺省根本不启用;logging.*、features.rewind等只解析不生效(上下文压缩、bootstrap)。
值得留意的行为
下面各条都是读代码得出的结论,多数没有实际运行验证;开源仓库几乎没有测试可以佐证。使用时以实测为准。
与安全边界有关
- 自定义命令里的 Shell 展开不经过权限系统,没有钩子、审批与 Plan 检查;TUI 空闲时拒绝,但忙碌时、App 协议与
-p下都会执行(技能与自定义命令)。 - 项目配置里的钩子要等工作区信任,但同一个文件里的
plugins.dirs照常合并,本地插件默认启用、插件钩子总能运行,仓库可以借此绕开工作区钩子信任(插件、钩子)。 - 项目级 MCP 默认可信并自动连接;TUI 一启动就轮询 MCP 状态,项目配置里的 stdio 命令在你输入第一句话之前就会启动(MCP)。
- Bash 的只读判定只看命令、不看路径:用绝对路径读工作区之外的文件,在
build模式下也不询问(Bash)。 - Plan 模式放行所有没标
destructiveHint的 MCP 工具,其中包括能执行任意 Node 代码的mcp__node_repl__js;yolo 的放行排在配置文件里的禁用名单之前(权限)。 - 动态工作流的 actor 子会话强制跑在 yolo 下,批准一次 run,等于放行其中所有 actor 的命令与文件编辑(动态工作流(三));内置的 Explore 子 Agent 同样以 yolo 运行并保留 Bash,“只读”只靠提示词约束(子 Agent)。
- SSH 远程工作区的连接配置里没有主机密钥校验回调,全仓也不读
known_hosts(远程工作区与手机远控);从代码看,pnpm dev:web的开发后端没设监听地址时监听所有网卡、也不带令牌(Web 与服务端);WebFetch 的出站护栏只看 URL 里的字面量地址,不做 DNS 解析(Web 工具)。
疑似缺陷
- 批执行器把
automationTurn传给了每组工具调用,却漏了offPeakTurn,闲时回合里拒绝 Bash 后台运行的保护因此不会触发(执行器)。 TurnError.turnPhase恒为processing_input;子 Agent 定义里的maxTurns被填上却从不读取(回合循环)。- WebFetch 的“始终允许”把整条 URL 存成规则,判定时却按主机名比对,存下的规则不会生效(权限)。
- 目标模式的完成验证一出故障就按通过处理:返回的不是合法 JSON、验证器想调用工具或请求出错,目标都直接记为完成(目标模式)。
- 专家工作流终审阶段的提示词只要求 Markdown,解析器却必须拿到 JSON 判决(专家工作流)。
- TUI 的
/fork会把检查点里的文件写回改动前的内容,而父子会话共用同一个工作目录,父会话的文件也跟着变(检查点、回退与分叉)。 - 记忆抽取的触发条件按空白计词,一整句不带空格的中文只算一个词(项目记忆)。
- 进程内的 TUI 没有协议层的队列提升,运行中排进队列的输入之后是否会被执行,没能在代码里找到答案(输入受理)。
可迁移的经验
把运行时做成进程,而不是库。 终端、桌面、Web、手机都要驱动同一个 Agent 时,一条带幂等、快照与增量续传的线协议,比让每个宿主都去 import 运行时更好维护;ZCode 的产品一侧因此可以完全不依赖 Agent 一侧的任何包。
受理与执行分开。 “这条输入能不能跑、跑在哪个回合里”应当在运行时内部原子地决定,外层只负责把它送进来;引导与排队在代码里是两条车道,界面才不会猜错用户的意图。
长程任务的刹车要写成具体条件。 与其给工具调用次数设上限,不如把刹车写成压缩熔断、续写次数、恢复次数与用户取消这样可解释的条件;每一条都有数字,出了问题知道是哪一道闸。
把副作用写进文档。 NOTICE.md 逐项列出联网场景、数据位置与默认值的差异,连“没有操作系统沙箱”“凭据加密的密钥可以从本机信息派生”也写在明处,这比一句“注意安全”有用得多。
让规则可以被机器检查。 AGENTS.md 写给编码 Agent 读,architecture-policy.yaml 把模块边界写成规则,formal-proof 把产品行为写成状态空间;它们的执行度参差不齐,但方向是对的:规则一旦能被脚本检查,就不再依赖人的记性。
这份源码的短板
- 没有测试可跑:公开时拿掉了几乎全部测试与 spec 文档,读者无法靠跑测试来确认理解,本书许多“疑似”只能停在读代码的层面。
- 规矩与现状有距离:400 行上限、I/O 只走 adapter、键盘优先、不靠错误文本判断,都在
AGENTS.md里写着,代码里都有例外;架构检查几乎只对一个模块生效。 - 声明多于实现:契约里有不少字段、事件类型与配置键没有读取方或产生方,读代码时得逐个确认它们是否真的生效。
- 开源版并不完整:Computer Use 是占位包,手机远控的 relay 与手机端、若干官方插件的内容都不在仓库里,一些功能只能看到宿主这一侧。
- 没有开发历史:仓库以一个提交整体公开,也没有 tag,读不到任何一个设计为什么变成今天这样,只能从注释里的“曾经”去拼。
继续读什么
- 想看另一种 TypeScript 生产实现:《OpenCode 源码解读》,以及在它之上二次开发的《MiMo Code 源码解读》。
- 想看借用上游循环、自建运行时的做法:《MiniMax Code 源码解读》、《Kimi Code 源码解读》。
- 想看 Rust 生产实现:《Grok Build 源码解读》。
- 想从零理解 Agent 该做什么:《从 LLM 到 Coding Agent》。
- 想了解钩子与插件格式的来处:《Claude Code 中文手册》、《Codex 中文手册》。