Part II · Kernel — Harness v2

08 Harness v2 一份正在被消费的设计文档

部分落地 设计文档 3437 行 工作包 11 / 41 完成 抛 HarnessNotImplemented 的方法 22 个
§1 Mental Model 一句话心智模型

先记住这一句

Harness v2 不是「pi 的架构」,是「pi 正在长成的架构」。存储底座已经完工并通过三后端一致性测试;执行器是一个刻意做成会抛错的空壳。中间隔着 30 个还没做完的工作包。

锚点之后本章的判定锚定在 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 章 那个循环。

更要紧的是别把「空壳」读成「没做完」。空壳是一个已经验收的工作包的产物:设计文档明确要求未完成的方法必须抛错,而不能返回看似合理的空快照。这是一种质量决定,不是进度落后。

§2 Evidence 代码实证

判定一:跑着的不是它

全部 packages/*/src 中,构造 agent 的地方只有一处(另一处在 JSDoc 注释里):

grep -rn 'new Agent(' packages/*/src/命令输出,非源码
packages/agent/src/proxy.ts:92:  * const agent = new Agent({        ← JSDoc 示例
packages/coding-agent/src/core/sdk.ts:294:  agent = new Agent({           ← 唯一真实构造
CLI 的全部模式(交互 / -p / --mode json / --mode rpc)都经这一个 SDK 入口。sdk.ts:294

AgentHarness 有一个消费者 packages/coding-agent/src/server/create-harness.ts,它确实调用 AgentHarness.create()。create-harness.ts:151 但这个文件只被它自己的测试引用,没有任何生产代码路径导入它:

grep -rn "create-harness" packages/ --include='*.ts'命令输出,非源码
packages/coding-agent/test/server/create-harness.test.ts:20:  } from "../../src/server/create-harness.ts";
(仅此一条)
而那个测试只断言 getter:getTools / getActiveTools / getStreamOptions / getRetryPolicy / getSteeringMode / getFollowUpMode。从不调用 prompt()。create-harness.test.ts:78

判定二:空壳是刻意的,而且已验收

22 个方法经同一个 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 的交付物,且该包已勾选完成:

packages/agent/docs/harness-v2.md原文 L3185–L3189
- [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.
验收标准写得更狠:“a table-driven scaffold test covers every public method and proves no unfinished method reports plausible success.” harness-v2.md:3189

判定三:底座已经完工

与执行器相反,存储层是真的(详见 第 07 章):9 种 record 类型、findOpenOperations 的恢复语义、三个后端(memory / JSONL / SQLite)跑同一套一致性测试。conformance.ts:92

所以准确的说法是:durability 的「记什么」已经做完并锁死,「怎么执行」还没开始。

进度:41 个工作包,完成 11 个

41
工作包总数
11
已勾选完成
9
Track(F/QA/R/J/I/L/H/C-N/O)
3
快照时活跃的 reservation

计数取自设计文档 §20 的复选框列表(- [x] / - [ ]),由 溯源索引 的脚本统计,非手写。文档明确划定了范围:本计划不迁移 packages/coding-agent。harness-v2.md:3169 换句话说,即便 41 个包全部完成,CLI 也不会自动切过去——那是之后另一件事。

reserve 协议:多 agent 在一份文档上并行认领

git log 里那些 docs(agent): reserve I2 / unreserve R3 的提交,来自文档里写死的一套认领流程:

packages/agent/docs/harness-v2.md原文 L3174
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.
冲突解决靠「谁先 merge 到 main 谁赢」——用 git 的线性历史当分布式锁。harness-v2.md:3174

快照时文档里有三处活跃占用,署的都是真实 GitHub 用户名:harness-v2.md:3307 harness-v2.md:3330 这套协议的完整分析见 第 19 章。

§3 Design 设计动机与权衡

「宁可抛错,不要看似成功」

F0 的验收标准值得任何做大重构的人抄走:未完成的方法必须以可识别的方式失败,不能返回空快照、idle 状态或 no-op 成功。

理由在多 agent 并行开发的语境下特别硬:如果 lanes() 暂时返回 [],另一个包的作者会写出「没有 lane 时走这条路」的逻辑,并让测试通过——一个建立在假象上的实现,等 lanes() 真正实现时才暴露。抛错则让依赖关系在编译期与测试期立刻显形。

代价是 AgentHarness 目前完全不能用,连「试试看」都不行。他们接受了这个代价,并把它写进了验收标准。

为什么先做存储再做执行

存储契约定错,上层全部重写;执行器可以在稳定契约上分包替换。所以顺序是:定义 record → 三后端实现 → 一致性测试锁死 → 才轮到执行器。

这也解释了一个外部观察者容易误判的现象:看起来「一年了 harness 还没做完」,实际是底座做完了而门面还没动。门面(22 个方法)恰恰是最后才动的部分,因为它消费所有其它包。

把 spec 当任务队列,值得吗

好处很明显:41 个包的依赖关系、主文件、验收标准全部写在一处,新 agent(人或 AI)读一份文档就能开工,认领冲突用 git 解决。

成本也很明显:一份 3437 行、每次认领都要改的文档,本身就是热点文件。而且它同时承担两种职责——「系统应该是什么样」和「谁在做哪块」。前者应该稳定,后者每天在变。文档里已经能看到两者的摩擦:J 轨的所有权标记写着「这项工作在计划被拆成 J0–J6 之前就开始了」,需要一段专门的散文解释历史。harness-v2.md:3260

§4 Limits 边界与失效条件

读这一章时要小心什么

检查过并确认的反面证据:packages/evals/src/pi-harness.ts 里的 "Harness" 是 evals 框架自己的同名类型,与 AgentHarness 无关;packages/protocol/src/schemas.ts 仅在一行注释里提到 AgentHarnessPhase。schemas.ts:37 二者都不构成生产消费者。

§5 Takeaway 拿走什么
Postscript @v1.2.0

追记:锚点之后,上游改了什么

这一节带 @后 标记的引用指向上游发布 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 上依然成立。这条追记本身也是本书主线的一个例子:一份被认真消费过的设计文档,最终没有变成默认产品的一部分。

§ Self-check 自测

场景你基于锚点版本 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

基于 earendil-works/pi @ ac4ac9e(2026-08-07 快照)。
仓库 commit 速度约 428/月,行号引用仅对该 SHA 有效。本章保鲜期最短:工作包进度请以 harness-v2.md §20 的复选框为准重新统计。