构建模型无关的AI Agent控制层

这是使用 Kimi K3 构建你的第一个 AI Agent harness 的完整 0-100 拆解。
这将改变你使用 Kimi K3 以及任何 AI 模型的方式。
选择 Kimi K3,是因为它是最接近前沿的、最丰富的开源智能形态。
太长不看:如果你不想读这 7,050 个单词,直接把这个给你的 Agent:https://github.com/codejunkie99/autoharness
前言
构建一个比你为之构建的模型更长寿的控制层,需要十一个要素,每个要素都配有提示词和可运行的检查项。
如果只取其一:模型是商品,harness 才是资产。构建 harness 时,要像你今年会更换两次模型一样去构建。你确实会的。
趁还没忘记,先把这十一个构建收藏起来:
- Manifest(清单)
- 边界(Boundary)
- 账本(Ledger)
- 状态表(State Table)
- 工作树(Worktree)
- 笼子(Cage)
- 拒绝器(Denier)
- 路由器(Router)
- 编译器(Compiler)
- 检测器(Detector)
- 进化守卫(Evolution Guard)
我构建了一个。它是本指南的参考,而不是本指南的重点。
这个仓库是 Rust 写的、仅支持 macOS,而你很可能两者都不沾。把它当作一种存在性证明来读,证明你需要用你自己的语言实现的十一个形态,然后关掉这个标签页。


现状,2026 年 8 月
今年你已经换了三次主模型,并且每次更换都重建了你的工具链。
每次发生的事情都一样。厂商发布了一款自带方言的 CLI:
- 自己的会话格式
- 自己的恢复(resume)标志
- 自己的审批提示词
- 自己对终端上"完成"长什么样的理解
你写了一个集成。它当时能用。
四个月后,更好的模型在别处出现了,而你的集成成了你六周没有迁移的原因。
什么保持不变
你真正需要的部分从未改变:
- 一个放置工作、使其无法触及你分支的地方
- 一份午休后可以重放的记录
- 任何东西离开机器之前的一道闸门
这部分你也重写了三次,因为它和厂商纠缠在一起。
智能变得便宜且多样。你的控制层却没有。
本指南解决这个问题。
赌注
上面所有内容都是描述。这部分是关于方向的主张,而关于方向的主张,除非你能说出什么能证明它错了,否则一文不值。

正在趋同的
- 价格:同等能力大约每年下降 10 倍
- 开源与闭源的能力差距:目前在 AA 指数上为 3 分
- Agent CLI 的数量:这就是为什么一个仓库要发布二十个 manifest
没有趋同的
- 按领域划分的能力。K3 在 Terminal-Bench 上排名第一,在 FrontierMath Tier 4 上得分约 39%
- 可靠性。最好的开源模型幻觉率为 51%,最好的闭源模型为 54.9%
把这两份清单放在一起,结论就呼之欲出。当模型在价格上趋同、在能力上仍然参差不齐时,唯一剩下的构建空间就在它们之间。
这就是赌注:定制化表面成为产品表面。
三大法则
下面的每个要素都是其中一条的实例。这些不是我发明的。我是从代码中的守卫(guards)里读出来的,法则就住在那里。
- 智能是充裕的。Harness 才是稀缺部分。
十八个月里发布了二十个 Agent CLI,而最好的那个每个季度都在变。它们中没有一个是护城河,也没有一个属于你。
属于你的是那一层:决定工作在哪里发生、什么被记录、什么永远不会离开机器。
为当前模型构建,你就买来了一次重写。为槽位(slot)构建,你就买来了一份期权。
- 薄耦合,厚构建。
充裕带来的直觉是"保持 harness 小巧",但这个直觉是错的。如果智能是便宜的部分,那么 harness 就是昂贵的部分,而昂贵的东西是刻意累积出来的。
薄是接缝(seam)的属性,不是系统的属性。Autoharness 大约有六万行代码,它应该有这么多。
薄的是它与模型接触的表面:两个手写的适配器,其余一切都是 JSON 文件。
- 除了一份"不可调优清单"之外,一切都是可调优的,而那份清单是代码。
没有边界的实时定制化,是一个会拓宽自己笼子的系统。把设置一分为二,并把这份划分放进一个经过审查的文件里。
能重新调整阈值的 harness 是自适应的。能重新调整沙箱规则的 harness 是一个有着良好交互性的负债。
一个推论,而非第四条法则:可替换的智能让信任无处安放,只能存在于 harness 中。
当你计划在三月份把它换成更便宜的模型时,你不能指望模型会小心谨慎。这之后十个构建中的每一次拒绝,都是充裕的下游结果,而不是与充裕相矛盾。

核心概念

这篇文章里真正起作用的是五个词。如果只略读一节,就略读这一节。没有它,后面的构建内容将无法读懂。
Harness
这个词是借来的,而大多数人借错了它的含义。
每一个旧式 harness 都是定制的:
- 马具是为某一匹马裁剪的
- 线束是为某一个底盘织造的
- 测试 harness 是针对某一个被测系统编写的
单一性是这个词与生俱来的。大多数 Agent 工具在不知不觉中继承了这一点:一个厂商、一个 CLI、一个 resume 标志,以及排名变化那个季度的一次重写。
保留这个词。改变它适配的对象。
这里的 harness 适配的是工作,而不是工作者。它针对你的仓库、你的检查、你的审批习惯、以及你对所发生事件的记录而裁剪。
智能只是从中流过的东西。二十个 Agent CLI 流过参考实现,而其中没有一个在其注册表(registry)中被点名。
这就是全部的区别。用一句话就能说清,但要用十一个构建才能赢得。
充裕(Abundance)
智能变便宜的速度超过计算史上任何其他输入。对于同等性能的模型,推理成本大约每年下降 10 倍:
- MMLU-42 能力:2021 年 11 月每百万 Token 60 美元,到 2024 年 11 月为 0.06 美元
- 这是三年内一千倍的下降
- 把质量保持在 GPT-4 水平,同一时间窗口仍然显示 62 倍的下降
摩尔定律的进展比这还慢,而且没有人称它为商品。
多样性(Plural)和便宜同样重要。Kimi K3 于 2026 年 7 月发布:2.8 万亿参数,每个 Token 激活 896 个专家中的 16 个,百万 Token 上下文,输入 3 美元、输出 15 美元。
它在 Artificial Analysis 智能指数上位列 57,比 60 分的 Claude Fable 5 低 3 分,比 59 分的 GPT-5.5 Sol 低 2 分。前沿第三名如今拥有开放权重和一个你可以据此规划的价格标签。
这就是前提。不是"模型很好",而是模型已经可以互换到这种程度:选择哪一个是一个调度决策,而不是架构决策。
不均衡(Unevenness)
充裕并不等于均匀,而这是人们在复述这个论点时搞错的部分。
以 K3 这一个模型为例,跨越四个基准:
- Terminal-Bench:K3 88.3,Fable 5 84.6
- SWE Marathon:K3 42.0,Fable 5 35.0
- Frontend Code Arena:K3 1,679,Fable 5 1,631,GPT-5.6 Sol 1,618
- FrontierMath Tier 4:K3 约 39%,而 OpenAI 和 Anthropic 的模型在某些运行中接近 90
同一个模型,第一名和最后一名,取决于你那一周的时间点。
应该主导你架构的那个数字
在 AA-Omniscience 上,K3 的幻觉率是 51%。准确率是 46%,高于 K2.6 的 33%,这是实打实的进步。
Claude Fable 5 在同一基准上的幻觉率是 54.9%。
最好的开源模型在它一半的回答中都会断言某些错误内容。最好的闭源模型这样做的频率略高一点。
你不是在选择一个值得信赖的智能。你是在选择哪个模型以你能捕获的方式犯错。这是一个 harness 问题,无论多少充裕都无法从模型侧解决它。
接缝(The Seam)
接缝是你的 harness 与模型接触的表面。它是唯一必须保持薄的地方。
接缝之外的一切都是方言:CLI 如何恢复、哪个键表示确认、工作时它的 spinner 打印什么。
把方言放进数据里,接缝就是一个文件格式。把它放进代码里,接缝就变成你未来两个季度的路线图。
在仓库里数一数:注册表里两个引擎名,磁盘上二十个 manifest。这个比例就是整个赌注。当下一个模型胜出时,采用它的成本是一个由非你本人编写的文本文件。
自我扩展(Self-Extension)
自我扩展是 harness 在不重写的情况下吸收充裕的方式。它分为三层,按每一层要求的信任度排序,仓库有意以不同方式实现这三层。
Layer1:数据。 一个新的 Agent 就是一个 JSON manifest。先加载捆绑文件,覆盖目录叠加其上,后出现的 id 替换先出现的,每个文件独立解码,这样一份坏的覆盖文件无法让其他十九个的检测失效。
不需要审批,不需要重新编译,除了会话本身之外不需要重启。
Layer2:提议。 系统可以把自己起草的调优方案作为策略候选。add_policy_candidate 会存储它,而 promoted_policy() 仍然返回空,因为候选策略到达时绝不会被提升。由人来提升,提升是排他的,回滚会恢复精确的先前版本。
第三层:证据。 系统可以学习关于你项目的事实,而没有证据支持的事实会被拒之门外。存储一个标记为已验证、但证据列表为空的 MemoryFact,你会得到一个错误。
把它存为未验证,它会持久化,但永远不会进入提示词。对它调用 verify_memory_fact 也会失败,因为提升需要这个事实从未拥有的证据。
自我扩展并不意味着自我修改。系统可以增长它所知道的,提议它所调优的。它不能授予自己一项能力。
能力边界(The Capability Boundary)
让Layer2和第三层安全的那条线。
候选策略可以修改五个字段:路由器阈值、已批准的图模板、检测器阈值、恢复预算、协调器提示词片段。
十五个它永远不能碰的子串:sandbox、capab、external_write、retention、evaluator、approval、promot、seatbelt、proxy、credential、keychain、token、egress,还有两个。
上限只能降低,绝不能升高。一个能提高 max_turns 的策略可以无限制地花钱。
五个它可以转动的旋钮,十五个它不能提及的名字。这个比例就是设计。
Harness 2.0 原语
Harness 1.0 适配工作者。Harness 2.0 适配工作。
这一处变化重写了部件清单:

七个原语由此得出。十一个构建中的每一个都是其中一个原语的实例,而缺少某个原语的 harness 会有一个洞,你会在负载下而不是在审查时发现它。
槽位(Slot)
它是什么。 一个智能接入的具名位置,在数据中声明。
不变量。 代码中不出现任何厂商名称。注册表中两个名字,磁盘上二十个文件。
它在哪里。 BUILD 00。
单元(Cell)
它是什么。 工作发生的可丢弃、受限的空间:一个工作树、一个假 HOME、一个生成的 profile、一组明确的可写根目录。
不变量。 任何东西都不能在单元之外执行,并且受限性在启动时被证明,而不是被假设。
它在哪里。 BUILD 04 和 05。
运行(Run)
它是什么。 一个携带身份和明确生命周期的目标。
不变量。 非法转换是你可以记录的值。绝不 panic。
它在哪里。 BUILD 03。
线程(Thread)
它是什么。 由 parent 链接的一系列 run,共享一个单元和一次提供商会话。
不变量。 对话累积的一切都以线程根为键,而不是以单个轮次为键。
它在哪里。 BUILD 04。
账本(Ledger)
它是什么。 关于所发生一切的有序、只追加记录。
不变量。 先持久化,后广播。订阅者永远不会看到账本尚未持有的内容。
它在哪里。 BUILD 02。
闸门(Gate)
它是什么。 执行因规则或人而停止的点,并附带原因。
不变量。 在 UI 中强制的闸门只是建议。闸门位于副作用所在之处。
它在哪里。 BUILD 01、06、07、08 和 09。
旋钮(Dial)
它是什么。 系统可以提议更改的一项设置,带版本、可提升,旁边放着一份它不能动的冻结清单。
不变量。 旋钮由系统提议,只能由人提升。上限只降不升。
它在哪里。 BUILD 10。
为什么是七个而不是十一个
构建是一种实现。原语是能在迁移中存活下来的东西。
在你现在运行的任何东西里数一数。你指不出来的那些不是缺失的功能,它们是漏洞:
- 没有 Slot,你的下一次模型迁移就是一个季度
- 没有 Cell,你的 Agent 的爆炸半径就是你的磁盘
- 没有 Ledger,"昨晚发生了什么"就无法回答
- 没有 Dial,每次调优变更都以一次部署的形式发布
- 旋钮旁边没有边界,系统就可以拓宽自己的笼子
主干(The Spine)

要素 0 位于链条之上,因为它决定了引擎到底是什么。从 1 开始的每个要素都以前面的要素为前提。
你不能在笼子之前构建路由器:路由决定什么会执行,而任何东西都不能在不受限的情况下执行。
你不能在工作树之前构建笼子:受限性需要一个可以授予的根目录。
顺序本身就是论证。
同一组要素,按触发的时机排列:
从这里开始,不再有长篇大论。
BUILD 00:Manifest(清单)
一句话原因。 如果你的代码知道模型厂商的名字,那你就是把本季度的赢家硬编码进了一个本应比它更长寿的系统中。
第一性原理
方言应该属于数据。把每个 Agent 放在一个文件里,声明:
- 它的二进制文件及其别名
- 如何恢复会话
- 它的审批提示词长什么样
- 表示工作中、空闲、阻塞的模式
现在添加一个 Agent 就是一个文本文件,而且添加它的人不一定是你。
先加载随附文件,然后在之上叠加一个覆盖目录,后出现的 id 替换先出现的。
这就是Layer1自我扩展,只需一个函数。想要不同阻塞状态检测的用户,可以编写自己的 cursor.json,只需要重启会话即可。
独立解码每个文件。共享解析的失败模式是:为某个工具添加支持,会悄悄地为另外二十个工具移除它。
让逃生舱名副其实。这里有两个引擎拥有手写的结构化适配器,因为它们会如实报告自己做了什么,而不是粉饰。
这种不对称正是关键。快路径是一个 JSON 文件,慢路径始终可用,而注册表中没有任何东西知道二者的区别。
通用提示词
我想驱动十几个不同的 Agent CLI,而且我拒绝写十几个集成。每个 Agent 一个文件,json 格式,声明:二进制路径、别名、如何恢复会话、审批提示词长什么样,以及将终端输出映射为 working / idle / blocked 的正则/子串规则,带优先级。先加载捆绑文件,再叠加覆盖目录,后出现的 id 胜出。独立解码每个文件,这样一份损坏的覆盖文件不会破坏其他所有文件的检测。注册表里不能出现任何一个 Agent 的名字。为我最关心的两个留出手写适配器的空间,而系统的其余部分不需要知道哪个是哪个。告诉我厂商重新设计 CLI 输出后的那一周,什么会坏掉。
参考实现
crates/engines/src/lib.rs、crates/pty/manifests/*.json
/// 通过结构化协议驱动 Codex 和 Claude,以及每个捆绑 manifest 所描述的 Agent,
/// 全部通过终端驱动。
///
/// 两个内置引擎保留手写适配器:它们会如实报告自己做了什么,而不是粉饰,
/// 这种保真度在存在的地方是值得的。其他一切都是 `PtyAdapter`,
/// 这就是为什么添加一个 Agent 就是一个 JSON 文件——这里没有任何代码路径会点名某个 Agent。
pub fn production() -> Self {
Self::production_with(None, None, None)
}
// ...
pub fn production_with(/* ... */) -> Self {
let mut registry = Self::default();
registry.insert(EngineKind::codex(), || Box::new(CodexAdapter::new()));
registry.insert(EngineKind::claude(), || Box::new(ClaudeAdapter::new()));
let (manifests, failed) = autoharness_pty::manifests::load(manifest_overrides.as_deref());
for id in manifests.ids() {
let kind = EngineKind::new(id);
// ...
}
}
注册表里两个名字。manifest 目录里二十个。
EngineKind::from_str 接受来自 [a-z0-9-.] 的 1 到 64 个字符的任意 id。类型系统对存在哪些智能没有任何意见。它唯一的意见是名字不能包含路径段或 shell 元字符。
审查发现:有两条规则是事后补充的,而它们都与用户看到的菜单有关。
通过 id 或别名命名内置引擎的 manifest 会被跳过。claude-code 就是 Claude Code,把两者都注册会让同一个 Agent 在列表中出现两次。
没有二进制文件的 manifest 也会被跳过:shell 和 generic 的命令来自调用者,所以把它们作为引擎提供会是一个永远无法工作的菜单项。注册表循环只有九行,其中两行是拒绝逻辑。
检查
grep -c "registry.insert(EngineKind::" crates/engines/src/lib.rs
ls crates/pty/manifests/*.json | wc -l
通过:第一个输出 2,第二个输出一个更大的数字。失败:这个比例就是下一次某个厂商胜出时你的重写成本。
BUILD 01:边界(The Boundary)
一句话原因。 一个进程拥有所有副作用,否则你将在项目的余生中追问是五个进程中的哪一个写了那一行。
第一性原理
画一条线。一边:渲染、输入、视图状态。另一边:数据库、git 操作、子进程、网络。除了消息之外,什么都不能跨越。
这所防止的失败是隐蔽而持久的。当两个组件都可以写入状态时,每个 bug 都变成一场竞态,每次审计都变成一次考古挖掘。你失去了在有限时间内回答"是什么改变了这个"的能力。
边界的第二个任务是身份验证。本地 socket 在共享机器上就是一个公共 API,任何能连接的东西都能驱动你的 Agent。
第三个任务是版本控制。使用服务器不认识的协议进行通信的客户端,会得到一个结构化错误,而绝不会得到部分解析。一个半能用的旧客户端比一个拒绝启动的客户端更糟。
通用提示词
我有一个 UI 和一个做实际工作的东西。现在 UI 直接打开数据库,还派生进程,我已经后悔了。把它们拆开。一个进程拥有所有副作用,另一个只负责渲染。它们通过一个本机上其他人都无法打开的本地通道通信,每条消息都携带版本号,未知方法返回类型化错误而不是崩溃。UI 必须可以在任何时刻被杀死而不让工作侧察觉。告诉我我会忘记放在边界后面的那件事。
参考实现
crates/protocol/src/lib.rs、crates/daemon/src/lib.rs、crates/daemon/src/auth.rs
pub const PROTOCOL_VERSION: u32 = 1;
// ...
pub struct Event {
pub jsonrpc: String,
pub protocol_version: u32,
/// 存储层在广播之前分配的单调递增序号。
pub sequence: u64,
// ...
}
三个进程:shell、daemon、更新辅助进程。shell 从不打开 SQLite,从不派生引擎。
它通过 mode 0600 的 Unix socket 使用 JSON-RPC 2.0 通信,带有 getpeereid 对端 UID 检查和 Keychain 持有的客户端Token。一个副作用所有者让你只有一个需要审计的地方。

审查发现:Token有两个家,而两个家不一致。auth.rs 在开篇第一行就给出了修复:"跨 Keychain 和 mode-0600 回退文件的统一 daemon-client 密钥。"
锁定的 Keychain 和未锁定的 Keychain 必须得到同一个Token,所以两个存储中有一个是故意认输的。
检查
grep -rn "getpeereid" crates/daemon/src/
grep -n "0o600" crates/daemon/src/lib.rs
通过:两个都返回一行。失败:你的 socket 对本机上每个进程都开放,而你并不知道。
BUILD 02:账本(The Ledger)
一句话原因。 只存在于回滚缓冲区中的 run 是无法重放的 run,而无法重放的 run 只是谣言。
第一性原理
每个事件在到达任何一个订阅者之前,先进入只追加的表。先持久化,再广播。绝不反过来。
这种排序做了一件具体的事:它把重连作为特例删除了。
一个关闭了一小时的客户端请求序号 N 之后的事件,获取积压消息,然后在同一个通道上继续进入实时流。从未断开的客户端和刚刚醒来的客户端通过同一条代码路径汇合。
先广播后持久化会给你一个某些客户端已经看到、而数据库从未记录的事件。你会在一次事件处理中发现这一点,而这正是了解你的审计日志有漏洞的最糟糕时机。
给每个事件一个全局单调序号和一个 per-run 序号。全局序号给整个世界排序。per-run 序号让你无需读取所有内容就能折叠单个 run。
通用提示词
Agent 会产生大量的洪流式事件,我需要能够合上笔记本电脑再回来。只追加日志,每个事件得到一个全局递增编号和一个 per-run 编号,在写入提交之前,任何东西都不会发给监听者。客户端通过说出它看到的最后一个编号来重连,先拿到缺口,然后在同一个通道上收到实时流,不需要单独的补拉 API。另外,我希望能够把整个日志从零折叠回当前状态。在我踩进排序陷阱之前,先警告我。
参考实现
crates/store/src/lib.rs
//! 账本是审计的事实来源。daemon 在向订阅者广播之前,先把每个事件持久化到这里。
// ...
let record = EventRecord {
seq: 0,
run_id: run_id.map(str::to_string),
run_seq,
timestamp_ms: now_ms(),
kind: kind.to_string(),
payload,
};
tx.execute(
"INSERT INTO events(run_id, run_seq, timestamp_ms, kind, payload) VALUES (?1, ?2, ?3, ?4, ?5)",
params![/* ... */],
)?;
let seq = tx.last_insert_rowid();
tx.commit()?;
Ok(EventRecord { seq, ..record })
序号来自已提交的 insert,而不是内存中的计数器。否则,两者之间的崩溃会为一个不存在的行发放一个编号。
审查发现:幂等性结果证明是一个广播问题,而不是写入问题。
update_app_settings 在返回设置的同时返回一个 bool,文档注释说明了原因:重复的、已经应用过的 request_id 会返回 false,"这样调用者就知道不要第二次广播 settings.updated。"
一次写入、两次广播的重试仍然会破坏每个客户端的视图。
检查
cargo test -p autoharness-store -- ledger_sequences_are_monotonic_per_run_and_global
cargo test -p autoharness-store -- replay_from_sequence_returns_exact_suffix
通过:两个测试通过。失败:你的重连路径是实时路径的第二个实现,它们会逐渐偏离。
BUILD 03:状态表(The State Table)
一句话原因。 非法转换是一个你可以记录、展示、持久化的值,而一旦你把它变成 panic,你就是用一个 bug 报告换来了一次宕机。
第一性原理
把所有合法转换写成一张表。全部。然后让转换函数返回一个结果,而不是直接执行迁移。
大多数系统把 run 生命周期编码在零散的 if 中。这在检测器对一个已经结束的 run 触发之前是可行的,然后某个东西会在拥有另外四个活跃 run 的 daemon 里调用 .unwrap()。
一张显式的表还给了你一个穷尽测试:遍历每一对状态,断言合法集合成功、其他每一对都报错。
八个状态就是六十四个断言。你可以说你的生命周期是被证明的,而不是被相信的。还要命名终态,然后证明没有任何东西能离开它们。
通用提示词
run 有一个生命周期,而现在它散落在十几个 if 语句里。把它收拢成一张合法 (from, to) 对的表。转换函数返回成功或类型化错误,绝不抛出、绝不 panic,非法迁移是能写进日志并在 UI 中展示的东西。标记哪些状态是终态,并证明没有任何东西能离开它们。然后给我写一个遍历整个笛卡尔积每一对状态、双向都测的测试。告诉我以后我会忍不住想添加、但不应添加的那一对。
参考实现
crates/core/src/lib.rs
pub fn can_transition_to(self, next: RunState) -> bool {
use RunState::*;
matches!(
(self, next),
(Draft, AwaitingApproval) | (Draft, Running) | (Draft, Blocked) | (Draft, Cancelled)
| (AwaitingApproval, Running) | (AwaitingApproval, Cancelled)
| (Running, Paused) | (Running, Blocked) | (Running, Succeeded)
| (Running, Failed) | (Running, Cancelled)
| (Paused, Running) | (Paused, Blocked) | (Paused, Cancelled)
| (Blocked, AwaitingApproval) | (Blocked, Running)
| (Blocked, Failed) | (Blocked, Cancelled)
)
}
一个 matches! 就是整个生命周期。它之外的任何组合都会返回 TransitionError { machine, from, to },这个错误携带了足够渲染的信息。

审查发现:需要防守的边界是 Blocked → AwaitingApproval。恢复路径想要继续,而恢复一个在恢复期间被重新编译过的图会跳过人工闸门。
恢复不是跳过审批的许可,所以恢复后的 run 会重新进入队列等待一次确认。
检查
cargo test -p autoharness-core -- every_illegal_run_transition_errors
cargo test -p autoharness-core -- run_terminal_states_have_no_outbound_edges
通过:两个都通过。失败:你的代码库某处,一个已结束的 run 可以重新开始。
BUILD 04:工作树(The Worktree)
一句话原因。 一个会编辑你已检出分支的 Agent,是一个你需要全程盯着的 Agent,而全程盯着正是你试图停止做的事情。
第一性原理
每个编辑型 run 都拥有自己的仓库副本和自己的分支。没有任何东西会写入你在编辑器中打开的分支。用 git worktree 做这件事很便宜,而事后改造则很贵。
接下来是每个人都会搞错的部分。一次对话不是一次 run。一次后续追问是一个指向其 parent 的新 run,这保持了账本的只追加性。但对话累积的一切都必须以线程为键,而不是以轮次为键。
有两种方式会搞错,而且都是静默的:
-
以轮次为键创建工作树,每次后续追问都从 base commit 重新分支。上一轮的编辑消失了,Agent 描述的是磁盘上已不存在的工作。
-
以轮次为键创建会话目录,
--resume指向的转录文件所在位置进程看不到。这一轮在产生任何 Token 之前就死了,并报告"轮次失败"。
回收本身就是一种拒绝。工作树只有在干净且不包含 base 之外的提交时才会被回收。其他任何情况都留在磁盘上供人查看。绝不使用 --force。
通用提示词
每次 Agent run 都会编辑代码。它绝不能碰我已检出的分支。给每个 run 自己的树,并从当前提交分出分支。后续追问是指向 parent 的新 run,但整个对话共享一棵树和一个提供商会话目录,以链条的根为键,而不是以单独的轮次为键。resume id 和 HOME 目录必须一起携带,这样调用者就不能交出一对不匹配的组合。清理只移除干净且没有提交的树。绝不强制。告诉我你打赌我会第一个搞坏的情况。
参考实现
crates/daemon/src/worktree.rs、crates/engines/src/adapter.rs
/// 本次对话在 `data_dir` 下 fake HOME 的稳定名称。
///
/// 提供商会在该 HOME 内写入自己的可恢复转录文件,因此每个想要继续同一对话的轮次
/// 必须传入相同的 key。每轮生成一个新的 key,会把上一轮的转录文件归档到
/// `--resume <id>` 看不到的地方:引擎随后在产生一个 Token 之前就退出,
/// 只报告为"轮次失败"。
pub session_key: String,
这段注释就是 bug 报告,就放在导致问题的字段旁边。

审查发现:这个构建发布时就带着缺陷,是一位用户发现的。
HANDOFF-FABLE.md 记录了投诉:在线程中途切换引擎看起来像是重新开始,因为它确实就是重新开始。
工作树以轮次的 run id 为键,所以每次后续追问都得到一棵全新的树,而交接简报却声称之前的工作就在磁盘上。
修复方式是 thread_worktree_key,它会沿着 parent_run_id 一路走到根,再加上一份现在会说明树是否被延续的简报。
检查
启动一个 run,发送一条后续追问,然后:
git -C ~/Library/Application\ Support/dev.autoharness.app/worktrees/* rev-parse --abbrev-ref HEAD
git -C ~/your/repo status --short
通过:两个轮次都报告同一个分支,而且你自己的检出是干净的。失败:你的第二轮在谈论它自己删除的文件。
BUILD 05:笼子(The Cage)
一句话原因。 一个你没有尝试攻破过的沙箱只是一个配置文件,而配置文件从来没有阻止过任何东西。
第一性原理
两类进程,两套规则。
引擎控制进程获得一个经过净化的环境,并依靠厂商 CLI 自带的沙箱。
你自己派生的命令——构建、检查、合并——在你生成的 profile 下运行:独立的进程组、资源限制、仅代理网络、一组明确的可写根目录。
从零构建环境,而不是过滤用户的环境。过滤意味着要永远枚举需要移除的东西。
受控的 PATH、假的 HOME、没有 SSH_AUTH_SOCK、GIT_CONFIG_NOSYSTEM=1,以及一个固定指向空文件的全局 git config,这样 git 永远不会读取用户的凭据。
现在是最重要的部分:在启动时证明它。
通过真实后端运行探测,并对被禁止的操作反转退出码,这样探测成功就代表突破,探测被拒绝才代表通过。如果任何探测失败,就完全拒绝启动 run。
失败即关闭(fail closed)意味着不存在回退路径。不是警告横幅,不是降级模式。而是带有诊断信息的结构化拒绝,什么都不运行。
通用提示词
我正准备让一个模型在我的笔记本电脑上运行 shell 命令。两类:厂商自己的 CLI,以及我自己为构建和检查派生的命令。净化环境从零构建,而不是从我的环境过滤。没有 agent socket,没有全局 git config,假 HOME。worker 拥有自己的进程组、CPU 和内存限制,网络只通过我控制的代理。在启动时运行尝试突破的探测:读取 ~/.ssh、读取 keychain、在允许的根目录之外写入。探测成功就是失败。如果任何探测失败,拒绝启动任何东西并指出是哪一个。没有降级模式。如果你认为回退没问题,来和我争辩。
参考实现
crates/daemon/src/sandbox/canaries.rs
// (b2) worker 无法读取登录 keychain。引擎 CONTROL 会话故意将此目录链接到它们的
// fake HOME 中,以便用户保存的 Codex/Claude 登录能够解析;daemon 拥有的 worker
// 绝不能获得这种访问能力,无论通过绝对路径还是会话 HOME 都不行。
results.push(expect_denied(
"cannot_read_login_keychain",
sandbox.probe(worktree, "/bin/ls",
&[real_home.join("Library").join("Keychains").to_string_lossy().into_owned()]).await,
));
expect_denied 把成功编码为突破。当 /bin/ls 失败时,探测才算通过。
审查发现:Keychain 就是接缝。
CLI 需要它,因为 macOS 相对于 $HOME 解析 keychain 搜索列表,没有它,厂商工具会报告 loggedIn: false。
所以引擎控制会话获得它的符号链接,而 .claude.json 是复制而不是链接,这样 run 就无法改动用户的配置。
Worker 命令两者都得不到。金丝雀(canary)b2 的存在是为了证明这两类进程从未意外合并。
检查
grep -c "expect_denied" crates/daemon/src/sandbox/canaries.rs
通过:三个或更多,而且当你故意破坏 profile 时,应用会以一个具名的金丝雀拒绝 run.start。失败:你有一个从未被人攻击过的沙箱。
BUILD 06:拒绝器(The Denier)
一句话原因。 Agent 的爆炸半径是它能改变而你无法撤销的事物的集合,而其中每一个都存在于你的机器之外。
第一性原理
本地编辑是可恢复的。强制推送、pull request、npm 发布、镜像推送、部署:这些不是。把线画在那里,并在进程启动之前强制执行,而不是在它失败之后。
把它实现为一个对程序和参数的纯函数。纯意味着可测试,可测试意味着你可以在一份审查者能一口气读完的文件中枚举所有情况。
过度拦截也是真实的失败。通过 HTTPS 的 git clone 是读取。没有 body 或变更方法的 curl 是读取。
如果你的拒绝器把这些也吃了,人们会禁用它,而一个被禁用的守卫比没有守卫更糟,因为你已经不再思考它了。
拒绝本身也是一个事件。被拒绝的命令会进入账本,这会把"Agent 试图发布"从一个故事变成一行记录。
通用提示词
给我写一个纯函数:给定程序名和它的参数,返回空,或者返回这个命令会改变我机器之外某个东西的原因。push、remote add、pr create、release create、publish、image push、ssh、scp、远程 rsync、带 body 或非 GET 方法的 http。基于 basename 匹配,这样绝对路径不能蒙混过关。在派生进程之前调用它,并把拒绝记录为一个事件。现在写第二个测试:证明 clone、status、commit 和普通 GET 仍然被允许的那个。我比第一个测试更在乎那个测试。
参考实现
crates/daemon/src/sandbox/policy.rs
pub fn external_write_violation(program: &str, args: &[String]) -> Option<String> {
let base = program.rsplit('/').next().unwrap_or(program);
// ...
match base {
"git" => match first {
"push" => deny("push"),
"remote" if matches!(second, "add" | "set-url") => deny("remote mutation"),
// ...
},
"gh" => match (first, second) {
("pr", "create" | "merge" | "close" | "edit" | "comment" | "review") => deny("pr mutation"),
// ...
},
"npm" | "yarn" | "pnpm" | "bun" if first == "publish" => deny("publish"),
"ssh" | "scp" | "sftp" => deny("remote shell/copy"),
// ... cargo、docker、rsync、带 body 或变更方法的 curl/wget
_ => None,
}
}
基于 basename 匹配正是 /usr/bin/git push 会被抓到的全部原因。
审查发现:第二个测试承担了重任。local_operations_are_allowed 断言 git status、git commit -m x 和 git clone https://x 都能原样通过。一个拦截 clone 的拒绝器会在一周内被关掉,然后什么都不再被执行。
检查
cargo test -p autoharness-daemon -- external_writes_are_detected
cargo test -p autoharness-daemon -- local_operations_are_allowed
通过:两个都通过。失败:要么你的 Agent 可以部署,要么你的开发者已经把你注释掉了。
BUILD 07:路由器(The Router)
一句话原因。 让模型选择花多少个 worker,就是让被监督者自己写预算。
第一性原理
把测量到的事实与模型的观点分开,绝不让后者覆盖前者。
事实是你数过的东西:目标的词数、它提到了多少个文件、是否附带了明确的检查命令、仓库跟踪多少个文件、检出是否脏。观点是模型对自己计划的说法。
路由从事实中得出:
- 短小、原子、有检查:直接运行。
- 短小、原子、无检查:运行有界循环。直接运行没有第二次机会,所以它的正确性依赖于一个不存在的命令。
- 语言暗示广度或多个步骤:至少使用循环。
然后让模型提议。只有模型自信且事实也同意工作可以分解时,提议才值得相信。
低于置信度阈值,你得到有界循环,绝不是蜂群。两种昂贵的形态需要人工确认后才能执行任何东西。
最后一个拒绝是关于钱的。当事实表明工作是顺序性的,跳过规划调用。你会花掉 Token 去被告知你已经测量过的东西,然后拒绝这个答案。
通用提示词
进来一个目标。决定是作为一次性(single shot)、有界重试循环、一组并行 worker,还是依赖图来运行它。根据你 MEASURED(测量)到的东西来决定:长度、它提到多少个文件、是否有检查命令、仓库大小、树是否脏。模型可以提议更丰富的形态,而我的代码决定是否相信它。低置信度向下回落到循环,绝不会向上到图。两种并行形态需要人工确认。每个决策都携带可渲染的理由字符串,以及做出该决策的策略版本。当事实表明是顺序性的时,甚至不要做规划调用。告诉我你会忍不住先调哪个阈值,以及为什么那是一个陷阱。
参考实现
crates/core/src/router.rs
if let Some(graph) = proposal {
if proposal_confidence < thresholds.min_graph_confidence {
reasons.push(format!(
"planning confidence {proposal_confidence:.2} is below {:.2}; running a bounded loop \
instead of an unvalidated graph", thresholds.min_graph_confidence));
} else if !profile.parallelizable_language && graph.nodes.len() > 1 {
reasons.push("the plan proposes parallel branches but the objective does not \
describe independent work".into());
} else {
// ...相信这个提议
}
}
图运行之前必须满足两个独立条件。模型自身的置信度只是其中之一,而且它并不充分。

默认值,这样你可以和它们争论:置信度下限 0.7,直接路由上限为 60 个词和 3 个具名文件,"大仓库"从超过 5,000 个被跟踪文件开始。
审查发现:最尖锐的拒绝是 worth_planning,它决定规划调用是否会发生。
它的文档注释写道:"要求模型分解那些事实已经表明是顺序性的工作,是在花用户的 Token 去听我们已经知道的事情。"
词语标记的作用比它们看起来更大。
"为所有适配器中的每个公共函数添加 docstring"是十个词,描述的却是几十处编辑点,所以 each(每个)和 across(跨所有)这两个词本身就足以否定原子性。
检查
cargo test -p autoharness-core -- a_low_confidence_plan_falls_back_to_a_loop
cargo test -p autoharness-core -- a_plan_the_objective_does_not_support_is_refused
cargo test -p autoharness-core -- planning_is_only_worth_it_for_decomposable_work
通过:三个通过。失败:你的路由器是一个加了额外步骤的直通管道。
BUILD 08:编译器(The Compiler)
一句话原因。 模型提交计划;你的代码决定它是否可以运行,而默认答案是拒绝。
第一性原理
把计划当作不受信任的输入,因为它就是。用纯的、全函数(total functions)来验证它,默认拒绝,并指出具体是哪条规则失败。
每条规则的存在,都是因为违反它会产生一种具体的灾难:
- 循环永远无法终止
- 孤立节点做的工作到不了任何地方,被丢弃
- 两个文件作用域重叠的编辑器在合并时互相破坏
- 没有验证器的编辑分支提交了未经检查的代码
- 过度的宽度或深度把你的 Token 花在一个你从未见过的计划上
宽松地解析模型的词汇,并把歧义解析为最具约束性的选项。模型对某个角色的用词不是契约。无法识别的角色会成为限制最严格的种类,而不是最宽松的,这样拼写错误就不能把节点升级为会编辑文件的东西。
验证在合并之后进行,而不仅仅在合并之前。集成节点把已验证的分支合并到暂存树中,并在此重新运行检查。
两个各自单独通过的变更可能一起失败,而这正是你在乎的情况。当一个节点失败时,把它的依赖节点标记为不可达,而不是让它们针对没人产出的工作运行。
通用提示词
模型给我一个计划:带角色和文件作用域的节点,以及边。写验证器。默认拒绝,每条规则一个具名错误,不 panic,无 IO。规则:无循环、无自环边、边中无未知节点、无孤立节点、恰好一个最终节点、节点数和深度和宽度上限、不能有两个编辑节点共享文件作用域、每个编辑分支都到达验证器、每个编辑节点都声明作用域、每个分支都有真实的检查命令。宽松地解析角色字符串,但把歧义解析为限制最严格(MOST restricted)的角色。一个失败节点让它的依赖节点变得不可达,而不是继续运行它们。每次拒绝都必须用一句人能读懂的话解释自己。在我踩进排序陷阱之前,先警告我。
参考实现
crates/core/src/graph.rs
//! 模型提交计划;**Rust 决定它是否可以运行。** 这里的一切都是纯的、全的,
//! 并且默认拒绝:违反任何规则的提议都会被以具体原因拒绝,
//! 调用者回退到有界循环,而不是运行一个没有人验证过的图。
// ...
pub enum GraphError {
// ... Empty, DuplicateNodeId, UnknownNodeInEdge, SelfEdge
Cycle(Vec<String>),
OrphanNode(String),
NoSingleFinalOutcome { sinks: Vec<String> },
ScopeCollision { first: String, second: String, scope: String },
UnverifiedEditingBranch(String),
MissingFileScope(String),
MissingAcceptanceChecks(String),
// ...
}
每条规则一个变体,意味着 UI 和账本可以指出确切的问题,而不是说"无效计划"。
审查发现:角色解析是我可能搞反的地方。Role::parse 匹配子串,回退值是 Editor,也就是要求最严格的那种:不相交的文件作用域和一条通往验证的路径。
回退为 Reader 看起来会很自然,但会让一个无法识别的节点在没有声明作用域、下游也没有验证器的情况下编辑文件。测试是以属性命名的,而不是以函数命名的:roles_parse_leniently_but_default_to_the_most_constrained。
检查
cargo test -p autoharness-core -- scope_collisions_are_refused
cargo test -p autoharness-core -- an_unverified_editing_branch_is_refused
cargo test -p autoharness-core -- every_refusal_explains_itself
通过:三个通过。失败:你在运行没有人验证过的计划,而你在合并时才会发现。
BUILD 09:检测器(The Detector)
一句话原因。 循环和努力工作从外面看一模一样,这就是为什么困难的部分不是抓循环。
第一性原理
三个信号,每个都有两种解读:
- 同一条失败的测试命令出现两次:卡住了,或者是有意进行红绿重构
- 四分钟没有任何输出:挂起了,或者在编译
- 读取了二十个文件、一个都没改:迷失了,或者在调研
所以让每个触发器都以"明确没有进展"为前提,并让一个进展信号重置计数器。没有进展通道的检测会产生误报,误报会训练你忽略警报,而被忽略的警报就不再是警报了。
对动作做两次指纹化:精确版和归一化版。归一化会折叠不稳定的细节,所以数字变成 #,路径只保留最后一段。
在有界的历史上比较。十二个条目足够长,能捕捉到中间夹着噪音的交替对;也足够短,古老的历史不会触发任何东西。
让它保持纯。没有时钟、没有随机性、没有 IO。这样你的误报测试夹具就是精确的,run 的检测器历史也能完全一致地重放。
恢复是一架有尽头的梯子:轻推、重新规划、从最后一个已验证的检查点重启,然后停下来展示证据。一个永远重试的系统会永远花你的钱。
通用提示词
我需要知道 Agent 什么时候卡住了,什么时候只是工作得很努力。对每个动作做两次指纹化:精确版,以及数字和路径被折叠的归一化版。保留最近十二个。触发器:交替对中同一动作出现两次、同一错误类别出现三次、N 个动作完全没有进展、以及预算耗尽。除预算外,每个触发器都以 NO PROGRESS(没有进展)为前提,进展信号会清除计数器。纯函数,没有时钟、没有随机性,这样我就能重放它。恢复是一架有底板的梯子:轻推、重新规划、从检查点重启,然后阻塞并精确展示它看到了什么。现在先写误报测试:tdd 循环、长构建、调研读取、不稳定基础设施的重试。那些才是我真正在乎的。
参考实现
crates/core/src/detector.rs
//! 困难的部分不是抓循环,而是不要误抓有产出的工作。
//! 红绿重构会有意重复同一条测试命令并得到相同的失败结果;长构建会几分钟没有任何输出;
//! 调研会读取二十个文件而不碰其中一个。因此这里的每个触发器都以明确没有进展为前提,
//! [`Progress`] 会重置计数器。
/// 指纹往回比较多远。足够长,能捕捉交替对以及中间的噪音;
/// 足够短,古老的历史不能触发任何东西。
const HISTORY: usize = 12;
文档注释在文件定义任何一个触发器之前,就先点出了三个误报场景。
审查发现:检测器的十五个测试中有五个断言检测器不做任何事:tdd_red_green_refactor_is_not_a_loop、research_reading_many_sources_is_not_a_loop、a_long_build_is_not_a_stall、flaky_infrastructure_retries_are_not_a_loop_while_the_error_changes、deterministic_reproduction_with_progress_between_runs_is_not_a_loop。文件的三分之一是在防御这个守卫,而不是在测试它。
检查
cargo test -p autoharness-core -- tdd_red_green_refactor_is_not_a_loop
cargo test -p autoharness-core -- a_long_build_is_not_a_stall
cargo test -p autoharness-core -- recovery_ladder_escalates_in_order_then_blocks
通过:三个通过。失败:你的警报会在正常工作的 Agent 上触发,而你已经学会了点击忽略它。
BUILD 10:进化守卫(The Evolution Guard)
一句话原因。 一个能自我调优的系统可以拓宽自己的笼子,所以在让它调优任何东西之前,先把那些它永远不能碰的栏杆写下来。
第一性原理
概念部分的能力边界是一个设计。这里是它的执行,而这个执行有一个值得借鉴的形状。
同时使用允许清单(allow-list)和拒绝清单(deny-list)。只有允许清单,会放过下个季度别人添加的字段。只有拒绝清单,会放过改名的字段。要求两者同时存在,意味着一个新旋钮必须先由人工添加,候选策略才能动它。
给两个检查排序时,你一定会错一次。精确的允许清单必须优先胜出,因为一个合法的字段名可能包含一个被禁止的子串,而先运行的标记检查会拒绝你自己的计划所允许的东西。
发布的版本只观察和提议。提升是人工行为,而一个能自我提升策略的系统,已经移除了最后一个可以说"不"的人。
通用提示词
我希望系统可以提议自我调优,而且我希望这很安全。两份清单。候选策略可以更改的东西:路由阈值、检测器阈值、重试预算、已批准的模板、提示词片段。它永远不能碰(NEVER touch)的东西:任何 sandbox、capability、external-write、retention、evaluator、approval、promotion、credential、proxy、token 相关的东西。同时用精确名称的允许清单和子串的拒绝清单来执行,并且也检查嵌套键,这样被禁止的名字就无法藏在允许的区块里。上限可以降低,绝不能升高。v1 只观察和提议;由人工提升。没有例外。我有一个允许的字段名包含一个被禁止的子串。告诉我这会对我的检查顺序造成什么影响。
参考实现
crates/core/src/policy.rs
// 精确的允许清单优先胜出。下面的子串标记是一个兜底网,
// 用来捕捉没人枚举过的名字,而其中一个允许的字段
// ("approved_graph_templates")合法地包含 "approve"——
// 如果标记检查先运行,就会拒绝 PLAN.md 所允许的字段。
if !MUTABLE_FIELDS.contains(&key.as_str()) {
let lower = key.to_lowercase();
if FORBIDDEN_MARKERS.iter().any(|m| lower.contains(m)) {
violations.push(PolicyViolation::ForbiddenField(key.clone()));
} else {
violations.push(PolicyViolation::ImmutableField(key.clone()));
}
continue;
}
这段注释回答了上面提示词中的对抗性条款。approved_graph_templates 包含 approve,所以先检查标记会拒绝一个计划本身允许的字段。
审查发现:拒绝清单运行着十五个子串:sandbox、capab、external_write、externalwrite、retention、evaluator、approval、approve、promot、seatbelt、proxy、credential、keychain、token、egress。每一个都是一根某人可以通过添加字段而移除的栏杆。
一个专门的测试覆盖了嵌套情况,因为一个被禁止的名字藏在允许区块的下一层,正是聪明的候选策略会采取的形状。
检查
cargo test -p autoharness-core -- capability_boundaries_are_refused_when_nested
cargo test -p autoharness-core -- a_ceiling_may_never_be_raised
cargo test -p autoharness-core -- an_unknown_field_is_refused_rather_than_ignored
通过:三个通过。失败:你的系统可以授予自己权限,而你的测试套件中没有任何东西会注意到。
值得命名的循环
这个仓库为编码 Agent 构建了一个 harness。而编码 Agent 在这个 harness 下运行,构建了这个仓库。
看看根目录:36 KB 的 AGENTS.md、PLAN.md、HANDOFF.md、HANDOFF-FABLE.md,以及 docs/superpowers/plans/ 旁边的 docs/superpowers/specs/。那些不是文档。它们是下一个会话的模型的输入格式,由上一个会话的模型写成。
现在打开 crates/daemon/src/handoff.rs,它在线程切换提供商时为进入的引擎撰写简报。它的文档注释论证了三个属性:
- 免费:它读取账本而不是调用模型
- 确定性:一次重放就能重建第二个引擎被告知的内容
- 始终可用:它不需要之前的进程仍然存活
然后它点名了自己拒绝的替代方案:
让离场模型自我总结,读起来会更好,但要花掉一个轮次、一些 Token 和一个活跃会话去请求。它也必然无法验证:一个模型描述自己的工作,恰恰是交接不应该凭信的那种说辞。
HANDOFF-FABLE.md 是同一个函数,由人工在仓库自身上执行。两者交给下一个 worker 的都是"发生了什么",而不是"上一个 worker 声称它做了什么"。Rust 文件和 Markdown 文件是同一个想法,一个已编译,一个未编译。
方法就是产品。一个不包含产生它的过程版本的仓库只是一件制品。一个包含的则是一个循环。
它的不足之处
我在倡导这种形态。不好听的那一半:
-
没有 CI。零托管流水线。GPUI shell 需要 Metal 工具链,所以四步检查在笔记本电脑上运行。README 大声地说了出来:"徽章是声明的事实,不是 CI 输出。"这很诚实,但也意味着那个页面上的每个数字都来自最后一次运行检查的人。
-
测试数量对不上。徽章声称 606 个通过。我在代码树中数到 604 个
#[test]和#[tokio::test]属性。HANDOFF-FABLE.md在更早的提交中说是 576 个。这些严格来说都不算错,只是没有外部系统来协调它们。 -
仅 macOS,并且锁定了版本。macOS 14+、Rust 1.97+、edition 2024,GPUI 锁定到一个 Zed git 修订版而不是 crates.io 版本。
CONTRIBUTING.md警告不要升级它。这个锁定让 shell 能持续构建,而这意味着一句上游强制推送就能让 shell 的构建变成一个修 bug 的下午。 -
它无法完成最后一步。V1 中完全没有外部写入,意味着 Agent 无法发起 pull request、推送分支或部署。最后一公里每次都靠你手动完成。这是可以辩解的选择,但每天都在付出代价。
-
协议版本被携带,但没有被检查。
docs/ARCHITECTURE.md说信封携带protocol_version,"因此版本偏差是结构化错误,而不是误解析"。在整个工作区中 grep:这个字段在四个地方被写入,在零个地方被比较。Request.protocol_version被反序列化后从未被读取。信封是对的,检查是缺失的,这是最昂贵的一种缺口,因为文档告诉你它已被覆盖。 -
它还没有发布。版本 0.1.0。生产签名、公证和更新源托管都在等待没有人提供的凭据。签名更新路径在代码中已经完成,但在现实世界中从未被使用过。
-
manifest 是有保质期的屏幕抓取。检测规则匹配终端输出:
"contains": "add a follow-up"、"lineRegex": "^\\s*(Generating|Thinking)"。厂商重新设计它的状态行后,规则会静默地停止匹配,而过时的检测器看起来就像一个平静的 Agent。 -
每个 manifest 上盖的
"version": "2026.07.10"是在承认这些文件会过期。二十个 Agent 意味着二十个你无法控制的表面,按照二十个不同的时间表在变化。这是薄耦合的账单,而仓库中没有任何东西会自动支付它。 -
路由器是刻意粗糙的。路由选择基于十七个词上的子串匹配。"refactor"、"migrate"、"audit"、"optimize"。一个目标如果避开了所有这些词,却仍然描述了十个步骤,就会被路由为原子任务。
-
阈值是附带了版本号的初稿,这是诚实的表述。
这些就是损失。赢的是:其中每一个都能从外部、在一个文件中、在十分钟内看到。大多数仓库让你安装之后才知道它不能做什么。
地图
你的文件名会不同。链条不会变。
crates/engines/src/lib.rs BUILD 00 EngineRegistry,两个名字,其余来自 manifests
crates/pty/manifests/*.json BUILD 00 二十个 Agent CLI 作为数据,外加一个覆盖目录
crates/pty/src/detect/manifest.rs BUILD 00 规则模式:区域、优先级、捕获
crates/core/src/lib.rs BUILD 03 run 与 node 状态表,transition -> Result
crates/core/src/router.rs BUILD 07 测量的事实、路由选择、阈值
crates/core/src/graph.rs BUILD 08 计划验证,每条规则一个错误变体
crates/core/src/detector.rs BUILD 09 指纹、触发器、恢复梯子
crates/core/src/policy.rs BUILD 10 允许清单 + 拒绝清单 + 上限守卫
crates/protocol/src/lib.rs BUILD 01 JSON-RPC 信封、protocol_version、分帧
crates/daemon/src/lib.rs BUILD 01 socket 服务器、0600、getpeereid 对端检查
crates/daemon/src/auth.rs BUILD 01 Keychain + mode-0600 Token协调
crates/store/src/lib.rs BUILD 02 SQLite WAL 账本、序号、重放
crates/daemon/src/worktree.rs BUILD 04 每 run 的树 + 分支,仅清理干净的
crates/engines/src/adapter.rs BUILD 04 SessionSpec、session_key、resume 契约
crates/daemon/src/sandbox/ BUILD 05 profiles、代理、HOME、金丝雀
crates/daemon/src/sandbox/policy.rs BUILD 06 external_write_violation
crates/daemon/src/handoff.rs meta 来自账本的确定性跨引擎简报
AGENTS.md HANDOFF.md PLAN.md meta 这个过程,与产品一起检入
承载 BUILD 03 和 BUILD 07 到 10 的 crate 完全不含 IO。这不是为了整洁。纯守卫是可以穷尽测试的,而对拒绝逻辑来说,穷尽测试是唯一有价值的测试方式。
部署计划
第 1 周:manifest。Agent 变成数据。注册表不点名任何人。毕业标准:你通过写一个 JSON 文件添加了一个你从未用过的 Agent CLI,并且在整个源码中 grep 它的名字返回空。
第 2 周:账本和边界。一个进程拥有副作用。每个事件在广播之前持久化。客户端按序号重连。
毕业标准:你关闭 UI 十分钟,重新打开,run 的历史完整,且没有单独的补拉代码路径。
第 3 周:工作树和状态表。每 run 一棵树,以线程为键。转换返回错误。毕业标准:一个线程上的三个后续轮次让你的检出中 git status 保持干净,且你的状态测试遍历了状态的完整笛卡尔积。
第 4 周:笼子和拒绝器。生成的 profile、启动金丝雀、外部写入函数。毕业标准:你故意破坏沙箱 profile,应用拒绝启动 run,并点出金丝雀的名字。
第 5 周:路由器和编译器。事实、阈值、计划验证。毕业标准:一个故意构造的坏计划(循环、两个编辑器共享一个作用域)被拒绝,并在你的 UI 中呈现具体原因。
第 6 周:检测器和守卫。指纹、进展门控、策略允许清单。毕业标准:你的误报测试夹具通过,且一个提高 max_turns 的候选策略在测试中被拒绝。
六周,十一个要素,而且你自始至终都不会在运行一个自己无法解释的东西。
运行手册

规则(打印这页)
- 注册表中不点任何厂商的名。Agent 是一个文件,添加它的快路径是文本。
- 一个进程拥有所有副作用。一个需要审计的地方。
- 先持久化再广播,这样重放和实时流共享一个通道。
- 非法转换返回值。panic 是你自己选择的宕机。
- 你已检出的分支永远不会被碰,只回收干净的东西。绝不使用
--force。 - 对话以线程为键,而不是以轮次为键。
- 从零构建环境。过滤是一份你永远在维护的清单。
- 在启动时用试图突破的探测来证明受限性。
- 失败即关闭。没有可以降级落入的模式。
- 在派生进程之前拒绝外部写入,并记录这次拒绝。
- 测量到的事实决定形态。模型的置信度只是一个输入。
- 不确定性沿梯子向下走,绝不向上。
- 默认拒绝计划,点出失败的规则,把歧义解析为最具约束性的选项。
- 以没有进展为前提触发停滞检测,并且先写误报测试。
- 允许清单和拒绝清单,只降不升的上限,以及一个负责提升的人。
五个预测,以及什么会杀死每一个

那五个中有四个可以在一年内验证。回来给它们打上勾。
反方论证
最强的反驳不是这个论点错了,而是这活儿会被别人干完。
厂商每个季度都在给自己的 CLI 中加入 harness 功能:沙箱、权限提示、会话恢复、子 Agent、计划审批。如果捆绑的 harness 足够好而且免费,独立的 harness 就只是一种爱好。
我认为这个论证在一个结构性要点上站不住脚,而这正是全文所依赖的那个要点。
厂商不可能为它的竞争对手提供一个槽位。Anthropic 不会把 Kimi 的 manifest 放进 Claude Code。OpenAI 不会在事实表明 Gemini 更擅长某件事时,把你的目标路由给 Gemini。
他们的 harness 按定义、按激励,都是为自己的 worker 定制的。
所以捆绑的 harness 在构造上就是 Harness 1.0,无论它变得多好。这意味着跨所有这些 harness 做路由的东西,必须由不卖其中任何一个的人来构建。
那个人就是你,而这扇窗口只会在模型保持不均衡的这段时间内敞开。
模型从来不是重点
Harness 才是智能得以利用之处。
我们可以拥有可定制的 harness,它随着你的使用方式实时进化。
而既然智能是充裕的,制造这样的 harness 才是护城河的大部分所在。
根据我在构建参考仓库时的笔记写成;每个文件都由 Kimi K3 通过 Kimi Code CLI 生成,一个前沿模型编辑了这篇文章并处理了一次升级的构建。
根据作者使用 Kimi K3 和 Kimi Code CLI 构建时的笔记写成,并由 Kimi K3 Code 和 Opus 4.7 编辑。
- 原文链接: x.com/Av1dlive/status/20...
- 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
鸿途知科网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。