收尾复盘
全书要点串联、与 llm-to-agent / Grok Build / OpenCode 的四方对照,以及可迁移的工程经验。
全景回顾
一条 prompt 从终端到模型再回到屏幕,经过的层:
TUI(pi-tui 差分渲染)
↓ node-sdk:进程内 bootstrap 引擎
klient(契约 + zod;内存传输也做 JSON 往返)
↓ agentPromptService.submit
Agent scope:IAgentPromptChannel → AgentLoopService
↓ agent/loop/machine(适配层)
human 状态机:agent → turn → tool
├── generate ← llmRequester → human/llm(四种协议基座)
└── 工具批次 ← toolExecutor + ToolScheduler(按资源冲突并行/串行)
↓
事件 → IEventBus → SDK → TUI;记录 → wire.jsonl十六条值得记住的设计
边界与契约
human/是纯内核:不 import 任何 v2 领域代码,只有三个适配层能碰它的实现,lint:imports强制。- klient 每个线上方法都有 zod 契约,schema 手工镜像引擎类型,由编译期断言钉住漂移。
- 传输无关:内存传输也做 JSON 往返,与 ipc 返回字节一致——同进程不是走捷径的理由。
- 服务挂在哪一级 Scope 在注册时写死(
registerScopedService(LifecycleScope.Agent, …)),看文件末尾就知道它的寿命。 - 功能经 Feature 接缝进来:
features/下每个功能往贡献点挂配置、工具、命令,核心不必知道它们。
类型即约束
- 回合结果是
completed/failed/cancelled的判别联合;请求层generate约定错误只经事件交付、从不抛出。 - 资源访问模型:读与搜索互不冲突,任一方写即冲突,
all与一切冲突;路径比较前小写折叠,在 Linux 上偏保守,方向安全。
并发与安全
- Edit 不唯一就报错,且错误信息带修复指引(设
replace_all=true,或给old_string多带上下文)。 - 权限的
fallback-ask兜底保证默认安全。 - skill 与插件说明注入提示词前做 XML 转义防注入。
- bash 解析有确定性预算,中止或出错就当「无法分析」降级。
可恢复性
- wire 记录有完整的版本迁移链(v1.1 到 v1.5),旧会话能打开。
- 重试与恢复是纯策略:
retry.ts/recovery.ts给出方案,turn 状态机执行——先试凭证恢复,再试媒体降级等替换消息。 - 用量在模型请求结束时立即记录,不等工具执行。
- 事件发射器用
safelyCallListener包住每个监听者,UI 出错不拖垮发射方。
工程纪律
- 无注释区 + 生成的清单 + 就近注册的 flag:三个包不许写注释,wire / state / config 三份清单由脚本生成,实验开关没有中央目录。
四方对照
| 维度 | llm-to-agent | Grok Build | OpenCode | Kimi Code |
|---|---|---|---|---|
| 定位 | 教学最小实现 | 生产实现 | 生产实现 | 生产实现 |
| 语言 | 教学用 | Rust | TypeScript + Effect | TypeScript |
| 规模 | 小 | 约 85 万行(非测试)/ 96 个 crate | 近 50 万行 / 36 个包(agent 相关约 17 万行) | 38 万行 / 19 个单元(引擎约 13.6 万行) |
| 前端 | 1 | 3(TUI、headless、ACP) | 8(TUI、Web、桌面、VS Code、ACP、GitHub、run、SDK) | 7(TUI、-p、Web、Remote Control、VS Code、ACP、SDK) |
| 依赖组织 | 无 | 显式传参 | Effect 的 Service、Layer 与 LayerNode | VS Code 式 DI × Scope |
| TUI | — | 自研 | OpenTUI + SolidJS | pi-tui(vendored 自 pi-mono) |
| 存储 | — | — | SQLite + Drizzle + 事件投影 | wire.jsonl 追加日志 + minidb 索引 |
| 引擎代数 | 1 | 1 | 2 代并存(V1 现役、V2 迁移中) | 2 代 → 1 代(v1 已删除) |
最大的结构差异来自前端数量。Grok Build 以 TUI 为中心,headless 与 ACP 只是同一个二进制的另外两种入口,agent 核心可以与 UI 贴得很近;Kimi Code 要喂七种形态,于是必须有 SDK 宿主层、klient 契约、transcript 独立包。
同为 TypeScript,Kimi Code 与 OpenCode 的依赖组织走了两条路:一边是 VS Code 式的装饰器 DI 加三级 Scope,一边是 Effect 的 Service 与 Layer。更值得对照的是换代:OpenCode 还在 V1 现役、V2 迁移的中途,Kimi Code 已经走完——服务端先切(v0.24)→ CLI 默认新引擎、留回退开关(v0.33)→ kimi acp 不再认回退开关(v0.40)→ 到 v0.43.0 旧包与开关都已删除。这条路径正好可以拿来参照。
这不是谁更好,是约束不同。 读源码时若不理解这个前提,会觉得 Kimi Code「过度设计」。
可迁移的经验
不写 agent 也用得上的几条:
提示词是资源文件。 引擎里 63 个源文件以 ?raw 导入 .md,提示词可 diff、可 review、可由非工程同事编辑。
换代要有兼容层。 node-sdk 的 v2 客户端逐方法对齐 v1 语义,差异写进注释并「pinned in the migration tracker」,前端几乎无感地换了引擎。
边界要能被机器检查。 导入边界、无注释区都有脚本把关;AGENTS.md 还专门提醒 flake.nix 的同步检查只覆盖 CLI 的依赖闭包、其余必须手工维护——知道检查的盲区,和有检查同样重要。
配置不静默忽略。 改名的旧键会在启动时提示改名,永远匹配不到任何工具的 allow/deny 条目会告警;llm 设计指南的原则之一就是「No silent fallback」。
vendored 代码的本地改动要登记。 pi-tui 的 AGENTS.md 逐条列出与上游的差异,每条都有守护测试,重新同步上游时不会被悄悄覆盖。
文档也会漂移。 AGENTS.md 因为天天被用而大体准确,但这次复核仍发现它与代码有出入——所以仓库自己强调「代码才是真相」。
这份源码的短板
诚实地说几条:
- 文档与代码有出入:根
AGENTS.md写agent-core-v2有四级LifecycleScope,枚举里只有三级;提到的IWorkspaceLifecycleService.handlerFor在引擎代码里找不到;minidb 的包描述还停在 KV 数据库。 - v1 的残留:
kosong、kaos引擎已不用却仍在仓库(kaos 只剩类型引用);SDK 里还有「the v1 engine throwsnot_implemented」一类已无对应实现的说明。 - 无注释的代价:13 万多行的引擎没有一行注释,新读者只能靠命名、类型和测试建立心智模型。
- 巨型文件:
kimi-tui.ts4190 行、sdk-rpc-client-v2.ts2807 行、loopService.ts2260 行。 - 自维护的底座(minidb、vendored 的 pi-tui、tree-sitter-bash)长期维护成本不低。
继续读什么
- 想对比另一种语言与架构:《Grok Build 源码解读》。
- 想看同为 TypeScript、另一套依赖组织的实现:《OpenCode 源码解读》。
- 想从零理解 agent 该做什么:《从 LLM 到 Coding Agent》。
- 想深入模型侧:《AI 大模型解构》。
回到专栏首页可查任意章节。源码基准 ffa94fa(v0.43.0,2026-09-14)——从 v0.29.1 到这里只隔七周,引擎就完成了换代,读时请以当前 HEAD 为准。