Agent Harness 不是 while 循环:omp²《Harness Playbook》深度解读

原文:The Harness Playbook — Stencil | 作者:Can Bölük(@_can1357),omp(Oh My Pi)作者,Stencil Labs 创始人 | 项目:github.com/can1357/oh-my-pi(MIT 协议,26k+ stars,上游为 Mario Zechner 的 pi-mono)
导语
2026 年,AI Coding Agent 的"外壳"——Harness——正在经历一场集体重构。OpenCode、Pi、OpenClaw、omp 不约而同地推倒重来。为什么?
omp 作者 Can Bölük 发布的《Harness Playbook》给出了迄今最系统的回答。这篇长文既是对 omp/Pi 这一代 Harness 的复盘(postmortem),也是其重写版 omp² 的架构蓝图:9 章正文 + 2 个附录,覆盖状态、运行时、控制面、推理层、工具面、界面与技术栈。
它的核心命题只有一句话:
Agent Harness 是系统软件,不是"一个 while 循环套一个 fetch"。
核心命题:复杂性必须有人拥有
文章开篇援引两位先哲,确立全文基调:
作者继而给出一个精妙的类比:Harness 的职责清单与游戏引擎几乎完全重合——维护权威世界状态、记录变更日志、运行不可信动作、向多视图复制状态、调度 actor、解释命令、适配不兼容协议、渲染实时界面。而游戏引擎已经拥有这些复杂性数十年的成熟解法。全文因此大量借用 Source 引擎(Valve)的设计作为参照系。
这一方法论贯穿全文:从真实失败证据出发,找到该拥有复杂性的那一层,让错误在表达层面不可能发生。
设计包线:四个"架构测试"
在设计任何子系统之前,作者要求先想象四种产品同时依赖这个 Harness:
| 测试场景 | 本地/远程 | 交互/自主 | 信任边界 | 并发 |
|---|---|---|---|---|
| 多路复用工作区 | 本地 | 交互 | 基本可信 | 多 Agent 共享一个目录 |
| 远程驾驶 | 远程 | 交互 | 宿主/客户端分裂 | 一或多个 Agent |
| 旁观者 | 远程视图 | 只读观察 | 不可信展示输入 | 大量观众 |
| 软件工厂 | 远程/集群 | 全自动 | 敌意仓库与工具输入 | 大量作业 |
这些不是用户画像,而是架构压力测试。只为第一种场景设计的系统,会把控制器偷运进 TUI、把状态藏进闭包、让扩展跑在引擎进程里。能同时活过四种场景的设计,被迫产生五条架构推论,构成全文的结缔组织:
逐章解读
3.1 状态:唯一权威,否则 rewind 就是谎言
问题。 Pi/omp 的日志只覆盖消息树,而权威状态(todo、重试计数、子 Agent 注册表、流式标志等)活在日志之外——存在两个真相来源,rewind/fork/resume 全部"说谎"。这违反了事件溯源的第一原则:状态必须能仅从事件推导。
证据。 官方 78 个扩展示例中,60 个无状态;17 个有状态的里只有 2 个是正确的。失败模式包括:闭包计数器在 rewind 后错乱、死分支的存档复活、checkpoint 在 fork 前被清空、切换会话误提交工作区等。作者的结论很硬:文档修不好这种 bug 分布。Source 引擎的正确性不来自开发者自觉,而来自"不可重放的状态在表达层面不存在"这一约束。
omp² 方案。 整个会话物化为一棵 DOM 树(XML 表示),日志是属性变更的 patch 流。运行时对象可以缓存或索引它,但不构成第二个真相所在地。由此,一串难题被归约为同一操作:
增加一个有状态的功能,永远不需要给 rewind、fork、resume 或复制增加新的调用点。
3.2 运行时:沙箱只执行,不决策
作者用一段归谬推演说明:把执行器放进 VM,会导致工具被迫分裂、需要双向网关;把驱动应用放进 VM,又会泄露提示词与源码。唯一干净的边界是——宿主拥有状态、推理、策略、审批、限额与日志;沙箱里只放一个"愚忠的 stub",且所有回流数据流必须有界(防止一次误用的 Read 返回 2GB 撑爆宿主)。
其余关键决策:
3.3 控制面:Convar 管值,Director 管行为
值 → Convar。 直接移植 Source 引擎的 convar 体系:每个设置在声明处一次性写清类型、默认值、帮助文本与标志位(REPLICATED / ARCHIVE / SESSION……),持久化、作用域、复制、脏跟踪全部内生于声明,不再有 god object 转手 setter。子 Agent 默认继承父会话的全部值;想钉死就写一行 subagent.cfg。bind、toggle、alias 也是控制台命令,键位绑定不再需要专门 schema。cfg 文件、控制台输入、远程管理、日志回放说同一种语言——定制化不再繁殖一次性 schema。
行为 → Director 栈。 Plan 模式、Goal 模式、/force、todo 提醒都想"拥有循环",各自实现必然互撞(作者实测:两个最流行的同类插件无法共存;私有 mutex 只能在同一作者的插件群内奏效)。omp² 的答案是:候选 yield 沿 Director 栈流动,每个 Director 可:
栈本身是会话 DOM 的子树——于是 rewind 自动移除 Director,resume 自动恢复,远程检视器能看到当前谁拥有 yield。Plan 模式被完整重写为该原语上的一次普通组合,而非特例。
3.4 推理层:兼容性是结构化知识
omp v1 曾有 880 行的 OpenAI 兼容文件,靠 isCerebras、isKimiModel 等布尔量层层嵌套——每个分支都修过真实 bug,但同一知识被编码在五六个地方。omp² 改为三层声明式结构(taxonomy 识别型号 → classes 陈述血统事实 → providers 声明宿主差异,用 KDL 书写),关键在编译器:未知指令报错、同等优先级规则冲突报错、无匹配规则返回 unknown 而非 false。收益不是怪癖变少,而是每个事实有唯一主人、优先级显式、未知可被表达。
其余要点:
3.5 工具面:每个 schema 都对每一轮征税
实测数据:工具名册砍到 5 个,wall-clock 从落后 Codex 近 2 倍反超(36.6s vs 42.2s)——工具语法会实质影响 token 生成过程。动态工具发现虽省税,但换名册即缓存失效,不可取。
长尾藏在稳定表面之后:dyn CLI 给模型一个稳定的发现协议(搜索 → --help 查看合成自 JSON Schema 的用法 → 经 Bash/Eval 调用,支持 @file 与 stdin 传大输入);对开放式 API(浏览器、桌面)则暴露代码表面而非 schema 枚举。有界操作集用 schema,开放操作集用代码表面,两者都不需要改动常驻名册。
内建工具要"深":
3.6 界面:字符串不是渲染原语
性能剖析显示渲染器吃掉会话 CPU 的大头——仅"判断某行是否为图片"的 .includes 就占 20%。同一契约还导致扩展 UI 风格失控,以及未消毒外部输入可用 ANSI 转义覆写整个终端的安全漏洞。性能、安全、一致性问题的共同根源是:一个已渲染的字符串同时被当作布局树、样式树、内容、传输格式和终端程序。
omp² 的三层解法:
另一个务实建议:为 UI 预先定义机器可读的"验证协议"(非破坏、离屏、可多实例的调试接口)。否则 Agent 会自行编造一个"看起来在测试"的旁路,悄悄拉低成功的定义。
3.7 技术栈:语言选择即架构
在大量代码由 Agent 产出的时代,语言的默认值、标准库、规范项目形态、编译器反馈构成生成代码的"先验"。作者尖锐批评 TypeScript:二十种同样"正常"的局部风格,意味着模型在碰到产品问题前要先做二十个决定(Zod 还是 Typebox、ESM 还是 CJS、Array 还是 T[]……)。一个允许二十种局部风格的语言,是在要求模型先做二十次风格决策。
结论:核心用 Rust(std + serde 生态 + 编译器安全网),扩展用 Python(Agent 写得好、AST 自省支撑 @remote、内嵌运行时让 Eval 可靠)。
评价与启示
这篇文章的价值,在于它把"Agent Harness 工程"从经验之谈提升为有名字、有边界、可推理的学科问题:状态权威、信任边界、有界执行、兼容性知识、视图投影——每一章都遵循同一方法论,且辅以扎实的实证材料(78 个扩展示例仅 2 个状态正确、渲染 CPU 剖析、wall-clock 基准),远超一般的架构布道文。
可持保留之处:
对从业者的启示:即便你不打算构建自己的 Harness,文中的多数原则——状态必须有唯一权威、限额应内建于原语、兼容性知识应显式建模、视图只是投影——同样适用于任何需要长时间运行、人机协作、容错恢复的 Agent 应用设计。
结语
《Harness Playbook》最终回答的,是开篇那句"but why?":
Harness 的每个子系统,都是有着数十年先例的软件品类——复制、沙箱、配置、调度、协议兼容、实时渲染、语言与运行时设计。
简单性不能靠分摊复杂性来伪装。每一类不可避免的复杂性,都必须有一个明确的所有者,并被压进能强制执行其不变量的那一层。
