锚点之后本章的判定锚定在 ac4ac9e(2026-08-07),在那个版本上依然成立。锚点之后,上游先把执行器的大部分方法做了出来,却没有接进默认 CLI;随后 7fd478a(2026-10-01)把 AgentHarness 连同整套 harness 从 pi-agent-core 删除,随 v1.0.0 发布。本书没有换锚点,变化见本章末尾的 追记。
这是全书最容易被误读的一章,也是本书存在的主要理由。上游有一份 3437 行的设计文档,写得极其详尽——lanes、records、checkpoints、race catalog、drive modes。任何人读完都会以为那就是 pi 的样子。但读者在自己机器上跑的 pi,走的是 第 03 章 那个循环。
更要紧的是别把「空壳」读成「没做完」。空壳是一个已经验收的工作包的产物:设计文档明确要求未完成的方法必须抛错,而不能返回看似合理的空快照。这是一种质量决定,不是进度落后。
全部 packages/*/src 中,构造 agent 的地方只有一处(另一处在 JSDoc 注释里):
packages/agent/src/proxy.ts:92: * const agent = new Agent({ ← JSDoc 示例
packages/coding-agent/src/core/sdk.ts:294: agent = new Agent({ ← 唯一真实构造
AgentHarness 有一个消费者 packages/coding-agent/src/server/create-harness.ts,它确实调用 AgentHarness.create()。create-harness.ts:151 但这个文件只被它自己的测试引用,没有任何生产代码路径导入它:
packages/coding-agent/test/server/create-harness.test.ts:20: } from "../../src/server/create-harness.ts";
(仅此一条)
getTools / getActiveTools / getStreamOptions / getRetryPolicy / getSteeringMode / getFollowUpMode。从不调用 prompt()。create-harness.test.ts:7822 个方法经同一个 unavailable() 抛出 HarnessNotImplemented:prompt、skill、promptFromTemplate、compact、navigateTree、resume、abort、steer、followUp、nextRun、cancelQueued、recordUsage、waitForIdle、runWhenIdle、peekAction、executeAction、runToCompletion、watch、lane、createLane、lanes、watchSession。agent-harness.ts:356
连 create() 也只接受空会话——一旦文件里有 record 就拒绝:agent-harness.ts:351
这些行为不是「还没写」,是工作包 F0 的交付物,且该包已勾选完成:
- [x] **F0 — harden the scaffold.** Dependencies: none.
- Primary files: `packages/agent/src/harness/agent-harness.ts`, `…agent-harness-scaffold.test.ts`.
- Inventory every public method. Preserve only behavior that is genuinely correct
without an operation runtime… Make every other placeholder reject with
`HarnessNotImplemented` instead of returning empty snapshots, idle state, or
no-op drive/wait success.
与执行器相反,存储层是真的(详见 第 07 章):9 种 record 类型、findOpenOperations 的恢复语义、三个后端(memory / JSONL / SQLite)跑同一套一致性测试。conformance.ts:92
所以准确的说法是:durability 的「记什么」已经做完并锁死,「怎么执行」还没开始。
计数取自设计文档 §20 的复选框列表(- [x] / - [ ]),由 溯源索引 的脚本统计,非手写。文档明确划定了范围:本计划不迁移 packages/coding-agent。harness-v2.md:3169 换句话说,即便 41 个包全部完成,CLI 也不会自动切过去——那是之后另一件事。
git log 里那些 docs(agent): reserve I2 / unreserve R3 的提交,来自文档里写死的一套认领流程:
2. Add `**Reserved: <package-id> by @<username>.**` immediately above the package entry.
Land that change alone with commit message `docs(agent): reserve <package-id>`.
The package is claimed only after this commit reaches `main`; if another conflicting
reservation lands first, remove yours and choose again.
快照时文档里有三处活跃占用,署的都是真实 GitHub 用户名:harness-v2.md:3307 harness-v2.md:3330 这套协议的完整分析见 第 19 章。
F0 的验收标准值得任何做大重构的人抄走:未完成的方法必须以可识别的方式失败,不能返回空快照、idle 状态或 no-op 成功。
理由在多 agent 并行开发的语境下特别硬:如果 lanes() 暂时返回 [],另一个包的作者会写出「没有 lane 时走这条路」的逻辑,并让测试通过——一个建立在假象上的实现,等 lanes() 真正实现时才暴露。抛错则让依赖关系在编译期与测试期立刻显形。
代价是 AgentHarness 目前完全不能用,连「试试看」都不行。他们接受了这个代价,并把它写进了验收标准。
存储契约定错,上层全部重写;执行器可以在稳定契约上分包替换。所以顺序是:定义 record → 三后端实现 → 一致性测试锁死 → 才轮到执行器。
这也解释了一个外部观察者容易误判的现象:看起来「一年了 harness 还没做完」,实际是底座做完了而门面还没动。门面(22 个方法)恰恰是最后才动的部分,因为它消费所有其它包。
好处很明显:41 个包的依赖关系、主文件、验收标准全部写在一处,新 agent(人或 AI)读一份文档就能开工,认领冲突用 git 解决。
成本也很明显:一份 3437 行、每次认领都要改的文档,本身就是热点文件。而且它同时承担两种职责——「系统应该是什么样」和「谁在做哪块」。前者应该稳定,后者每天在变。文档里已经能看到两者的摩擦:J 轨的所有权标记写着「这项工作在计划被拆成 J0–J6 之前就开始了」,需要一段专门的散文解释历史。harness-v2.md:3260
ac4ac9e。「11/41」这个数字在你读到时几乎肯定已经变了;不变的是判定方法——数 - [x]、数 unavailable() 调用、grep new Agent(。packages/coding-agent 排除在迁移范围外。harness-v2.md:3169检查过并确认的反面证据:packages/evals/src/pi-harness.ts 里的 "Harness" 是 evals 框架自己的同名类型,与 AgentHarness 无关;packages/protocol/src/schemas.ts 仅在一行注释里提到 AgentHarnessPhase。schemas.ts:37 二者都不构成生产消费者。
这一节带 @后 标记的引用指向上游发布 v1.2.0(06a4669,2026-10-13),不是全书的锚定版本,同样由校验器逐行核对。删除之前的那段历史只给提交链接:那些文件在 v1.2.0 上已经不存在,行号无从核对。本章前面的判定一字未改:它们描述的是 ac4ac9e 上的代码,在那个版本上仍然成立。
先是做完了大半。harness-v2.md 由 85a2060(2026-08-11)并入 harness.md,工作包重新编号;同日 5aeb06b(2026-08-11)清空了 unavailable() 桩。到删除前夕,锚点上抛错的方法大多已有实现,只剩 watchSession 一个桩;coding-agent 里构造 AgentHarness 的代码都在 src/experimental/ 下。确切计数由脚本推导,见 漂移记录 · 上游后续。本书 2026-09 的上一版追记写到这里为止,结论是「大部分已实现,只是默认路径还没切换」——下一段推翻了它。
然后整个删掉了。7fd478a(2026-10-01)把 experimental harness 从 pi-agent-core 移除,随 v1.0.0 发布:CHANGELOG.md:27。删掉的不只是执行器:AgentHarness、会话与会话存储、durable runtime、harness 版的工具与压缩、harness.md 本身都在其中;同一条记录说,包里从此只剩 Agent、agent loop、proxy stream 及其类型。7472cda(2026-10-12)随后删除了 experimental 的会话 worker 和 client / protocol / server 三个包。
后继不是同一套设计。同一条 CHANGELOG 让需要 durable session 的人改用 @earendil-works/pi-durable。这个包的 README 开头就是 README.md:3;它的源码里没有 AgentHarness 和抛错桩,coding-agent 也不依赖它。本书没有核对 pi-durable 的设计,不把它当作 harness-v2 的续作来读。
默认路径没变。默认 CLI 的会话仍然由 sdk.ts 构造 new Agent:sdk.ts:411(锚点时是 sdk.ts:294)。
所以判定二要这样读:在 ac4ac9e 上,执行器是刻意的空壳;之后它被做完了大半,却没有接进默认路径,最终在 v1.0.0 被整体删除。判定一「跑着的不是它」在 v1.2.0 上依然成立。这条追记本身也是本书主线的一个例子:一份被认真消费过的设计文档,最终没有变成默认产品的一部分。
场景你基于锚点版本 ac4ac9e 的 @earendil-works/pi-agent-core 构造 AgentHarness,并调 prompt("hi")。会发生什么?
B。而且 C 是被刻意排除的选项——F0 的验收标准就是「证明没有任何未完成方法报告看似成功」。harness-v2.md:3189
锚点之后这道题只在锚点版本上成立。1.0.0 起这个包不再导出 AgentHarness:CHANGELOG.md:27,在 1.0.0 及之后的版本上,这段代码在 import 这一步就过不去。
场景harness-v2 的 41 个工作包某天全部勾选完成。此时 pi CLI 的 run 变成崩溃可恢复的了吗?
C。「this plan does not migrate packages/coding-agent」。CLI 切换到新 harness 是这份计划之外的另一件工作。harness-v2.md:3169
场景两个贡献者同时想认领工作包 H0,各自提了一个「reserve H0」的提交。谁拿到?
A。协议原文:「The package is claimed only after this commit reaches main; if another conflicting reservation lands first, remove yours and choose again.」——用 git 的线性历史当分布式锁。harness-v2.md:3174
ac4ac9e(2026-08-07 快照)。harness-v2.md §20 的复选框为准重新统计。