之前在学 Harness Engineering 的时候,我总结过一个六层架构模型。那时候觉得框架本身挺清晰的,但总有一种”纸上谈兵”的感觉——毕竟是从文档和二手资料推导出来的,没有看过真正的生产代码。
后来我做了一次全局走读,对象是一个约 512K 行 TypeScript 的生产级 CLI Agent 系统。这次阅读让六层模型从抽象概念变成了具体的工程决策。
模式 1: 乒乓循环 + Streaming 即时调度
Agent loop 是严格的乒乓模式(推理 -> 工具 -> 推理),但客户端通过 streaming 即时调度做了优化——tool_use block 在 stream 中一完整就开始执行,不等模型输出完。
这让我意识到一个关键区分:模型侧的限制(原子输出)和客户端的优化(提前执行)是两个独立的设计层面。理解 agent 系统必须区分这两层,否则会在概念上混淆”模型能做什么”和”系统能做什么”。
模式 2: 并发安全是工具自声明的
不是调度层去分析工具是否安全,而是每个工具通过 isConcurrencySafe(input) 自己声明。调度层只负责执行这个声明。
一个有趣的案例是:AgentTool 声明自己并发安全且只读,尽管它内部可以执行任何操作。它把安全边界下推给了内部的 query loop。这是一种”信任边界转移”的设计——每一层不去猜测子层的行为,而是要求子层自己保证安全。
模式 3: 统一中间表示消灭特例
系统把所有输入输出统一成 Message 类型(5 种子类型),避免了”用户输入走一条路、工具结果走另一条路、hook 输出再走一条路”的碎片化。
这个模式的收益在简单场景下不明显,但在复杂场景下就显现了:resume 恢复会话、compact 压缩历史、bridge 跨设备同步——这些功能都因为统一的数据结构而变得简单。不统一的话,每增加一种消息来源就要在每个下游功能里加一条特殊处理路径。
模式 4: 动态上下文装配 + 缓存边界
System prompt 不是静态模板,而是运行时拼装的。关键设计是一条分割线:分割线之前的内容可以被 API prompt cache 缓存(跨轮次不变),之后的每轮重算(memory、MCP 指令等)。
这是一个性能和正确性的 tradeoff:缓存节省 token 但可能用旧数据,动态段保证新鲜但每轮都要重算。意识到这个 tradeoff 的存在,比知道具体实现更重要——因为任何 agent 系统都会面对这个选择。
模式 5: 两套预算系统的解耦
Token budget 有两套完全独立的机制:客户端 auto-continue(用户层)和 API task_budget(参数层)。它们计量不同的东西、由不同的层面执行、互不影响。
这种”看起来是同一个概念但实际是两个独立系统”的情况,如果不读代码就会混淆。这也是为什么我觉得读生产代码比读文档更有价值——文档会把它们放在同一个”token 管理”章节里,但代码会告诉你它们根本不在同一个模块。
与 Harness Engineering 六层架构的对照
| 六层模型 | 在这个系统里的对应 |
|---|---|
| Prompt Layer | 动态装配 + 缓存分割线 |
| Tool Layer | Tool 抽象 + StreamingToolExecutor 调度 |
| Memory Layer | 文件系统记忆 + 自动记忆 |
| Safety Layer | 权限检查(嵌在主路径中) |
| Orchestration Layer | while(true) 状态机 |
| Interface Layer | REPL / QueryEngine(壳与内核分离) |
之前的六层模型是从”应该有什么”出发的,这次走读让我看到”实际是怎么做的”。两者并不完全一致——比如 Safety Layer 在这个系统里不是独立的一层,而是嵌入在主路径的每个节点中。这个差异本身就是一个值得思考的设计选择。