回顾

全书要点串联:一条消息穿过的各层,值得记住的设计,与站内其他终端 Agent 的对照,读代码时发现的文档出入与值得留意的行为,以及可迁移的经验和这份源码的短板。

作者 David更新于 47 篇(共 47 篇)

读到这里,ZCode 的形状可以归成一句话:一个自研的 Agent 运行时,装进三种宿主。回合循环、工具、权限、压缩、子 Agent、工作流都在 apps/zcode-cli 里自己写;终端 TUI 在进程内直接调用它,桌面端与 Web 服务端把它当子进程,隔着 ZCode Protocol 驱动它。产品一侧的五十多万行代码,大部分在做“怎样呈现、怎样调度这个运行时”,而运行时本身只关心一件事:把一个会话跑好。这一篇把前面各部分串起来。

一条消息穿过的各层

对照一条消息的旅程

  1. 宿主。TUI 经一组回调把输入交给 cli 的命令中心;桌面端与 Web 把它包成 v4/command 信封,经 Host 写进 Agent 子进程的标准输入,先过 CommandInbox 的查重与串行(终端界面ZCode Protocol V4桌面应用)。
  2. 装配。一个会话一个 ZCodeApp,里面恰好一个 AgentRuntime,端口由 bootstrap 按五层配置造好注入(bootstrapAgentRuntime)。
  3. 受理。空闲时当场建立启动预留,忙碌时分成并进当前回合的引导与排到之后的队列(输入受理)。
  4. 回合。冻结本轮模型,初始化上下文与钩子,写入用户消息,然后在循环里反复“整理、请求、执行”(回合循环系统提示词上下文压缩)。
  5. 模型。适配层把三种 API 形态统一成一套流式事件,只读工具边流边执行,截断续写、断流恢复与两层重试兜住异常(一次模型请求模型适配层)。
  6. 工具。每个调用走一遍校验、钩子、权限、带超时执行、结果预算与投影的流水线(执行器权限钩子)。
  7. 回流。事件经 appendEvent 发给订阅者,消息与 part 并排写进 SQLite;TUI 直接渲染原始事件,协议层先归约成行与状态再攒帧推送(会话事件流SQLite 会话库)。

值得记住的设计

运行时

  1. 端口与原型安装AgentRuntime 只认 contracts 里的端口,近两百个方法分散在近百个文件里,再装到同一个类的原型上;宿主不注入审批端口,任何要问人的调用一律被拒(AgentRuntime)。
  2. 引导与排队是两条车道。运行中的新输入要么在下一个工具批次之后并进当前回合,要么等回合结束另起一轮;桌面端的“交互行为”设置决定走哪条(输入受理)。
  3. 长程优先。回合循环没有步数上限,工具调用的次数与重复只换来提醒;真正的刹车是上下文压缩与“压缩后迅速又满”的熔断、输出截断续写的次数、断流恢复的次数与用户取消(回合循环)。
  4. 断流时补齐工具结果。已经发出的工具调用若得不到结果,就合成一条“按失败处理”或“副作用未知、先检查现状”的结果,保证历史里每个调用都有配对(一次模型请求)。
  5. 事件只在内存,消息落库。唯一的事件存储是进程内的,流式事件按回合窗口淘汰;冷恢复时由持久化消息反推出等价事件,再喂给同一个投影(会话事件流)。

工具与安全

  1. 声明式工具契约。每个工具声明 schema、只读、并发安全、副作用范围、结果预算与超时,调度、权限与流式执行都读这些声明;Glob 与 Grep 默认不给模型,搜索改由 Bash 里被替换成 bfs 与 ugrep 的 findgrep 承担(工具契约读、写、改、搜)。
  2. 只读判定表与自动转后台。Bash 命令先经 unbash 解析,再按一套简单命令、多词命令、git 子命令与 flag 的规则判定是否只读,命令注册表由 Fig 的补全规格生成;普通前台命令到了超时并不被杀,而是原地转成后台任务,模型之后再去读输出(sleep 开头的命令与闲时回合除外)(Bash)。
  3. 把副作用摊开讲。四种权限模式、七个生命周期钩子、按声明摘要信任的工作区钩子;NOTICE.md 逐项写明了哪些功能会联网、数据落在哪里,并直说共享执行适配器没有操作系统沙箱(权限钩子执行边界)。

长程与多 Agent

  1. 目标模式另起一次请求验证。完成与否不让干活的那一轮自评,而是在它结束后另发一次不带工具的请求判断,没有轮数与时间上限(目标模式)。
  2. 动态工作流。模型写 TypeScript 编排脚本,编译器做类型检查、taint 分析、JSON Schema 合成与站点插桩,脚本在只有 ES 内建对象的 vm 上下文里运行,靠日志按“站点加序号”回放实现中断后续跑(编译器引擎工具链)。
  3. 三条工作流线并存。固定八阶段的专家工作流存文件,早期的脚本工作流与现在的动态工作流各有一套 SQLite 表,后者由前者演化而来(专家工作流)。

宿主与协议

  1. 命令信封与两种投递档位。所有命令带客户端生成的 commandId 做幂等,按修订号做并发检查;桌面每 30 毫秒一帧、流式全发,Web 与手机 150 毫秒一帧、只流正文,终态逐字节一致(ZCode Protocol V4)。
  2. 把产品行为画成状态空间packages/formal-proof 枚举“某个状态下来了某种输入”的全部组合,协议的输入路由裁决表声称与它逐条对齐(怎么读这份源码)。
  3. 窗口级 Host。每个窗口一个 Host 进程,刷新页面不杀 Host,桌面、手机与远程工作区都挂在同一个 Host 的 attachment 上(桌面应用远程工作区与手机远控)。

与站内其他终端 Agent 对照

维度OpenCodeKimi CodeMiMo CodeMiniMax CodeZCode
Agent 循环自研自研沿用 OpenCode内置 pi-mono,外包回合系统自研
终端界面OpenTUI + SolidJS复制 pi-tui沿用 OpenTUI另 fork 的 pi-tui 0.84.2OpenTUI 的 React 渲染
存储SQLite + Drizzle追加日志 + 自研索引SQLiteSQLite + DrizzleNode 内置 node:sqlite + 手写仓储与迁移
入口TUI、Web、桌面、ACP 等TUI、Web、VS Code、ACP 等以 TUI 为主TUI、exec、ACPTUI、-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 漏掉 hooksagent-server--output-formatinspect,斜杠命令表 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,读不到任何一个设计为什么变成今天这样,只能从注释里的“曾经”去拼。

继续读什么

本页目录